From 28b1f56488a3258863a53d42a0dfc33f60581e37 Mon Sep 17 00:00:00 2001 From: GroovinChip Date: Mon, 4 Jul 2022 16:51:13 -0400 Subject: [PATCH 01/17] feat: MacosTabView & MacosSegmentedControl --- CHANGELOG.md | 3 + README.md | 57 ++ example/lib/main.dart | 6 + example/lib/pages/buttons_page.dart | 652 +++++++++--------- example/lib/pages/tabview_page.dart | 62 ++ example/pubspec.lock | 2 +- lib/macos_ui.dart | 12 +- lib/src/buttons/segmented_control.dart | 117 ++++ lib/src/fields/search_field.dart | 3 +- lib/src/layout/tab_view/tab.dart | 62 ++ lib/src/layout/tab_view/tab_controller.dart | 51 ++ lib/src/layout/tab_view/tab_view.dart | 184 +++++ .../layout/toolbar/toolbar_overflow_menu.dart | 2 +- lib/src/layout/toolbar/toolbar_popup.dart | 3 +- lib/src/theme/overlay_filter.dart | 1 + pubspec.yaml | 2 +- 16 files changed, 898 insertions(+), 321 deletions(-) create mode 100644 example/lib/pages/tabview_page.dart create mode 100644 lib/src/buttons/segmented_control.dart create mode 100644 lib/src/layout/tab_view/tab.dart create mode 100644 lib/src/layout/tab_view/tab_controller.dart create mode 100644 lib/src/layout/tab_view/tab_view.dart diff --git a/CHANGELOG.md b/CHANGELOG.md index b4911245..a97af5f0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +## [1.6.0] +* New widgets: `MacosTabView` and `MacosTabView` + ## [1.5.1] * Correct the placement of the leading widget in disclosure sidebar items [#268](https://github.com/GroovinChip/macos_ui/issues/268) * Improve the sizing of the disclosure item indicator diff --git a/README.md b/README.md index 4c3e7f54..3e89b932 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,7 @@ Guides, codelabs, and other documentation can be found at https://macosui.dev - [Modern Window Look](#modern-window-look) - [ToolBar](#toolbar) - [MacosListTile](#MacosListTile) + - [MacosTabView](#MacosTabView)
@@ -55,6 +56,7 @@ Guides, codelabs, and other documentation can be found at https://macosui.dev - [PopupButton](#popupbutton) - [PushButton](#pushbutton) - [MacosSwitch](#macosswitch) + - [MacosSegmentedControl](#macossegmentedcontrol)
@@ -354,7 +356,52 @@ MacosListTile( ), ``` +## MacosTabView +A multipage interface that displays one page at a time. Must be used in a `StatefulWidget`. + + +You can control the placement of the tabs using the `position` property. + +Usage: +```dart +final _controller = MacosTabController( + initialIndex: 0, + length: 3, +); + +... + +MacosTabView( + controller: _controller, + tabs: [ + MacosTab( + label: 'Tab 1', + active: _controller.index == 0, + ), + MacosTab( + label: 'Tab 2', + active: _controller.index == 1, + ), + MacosTab( + label: 'Tab 3', + active: _controller.index == 2, + ), + ], + children: const [ + Center( + child: Text('Tab 1'), + ), + Center( + child: Text('Tab 2'), + ), + Center( + child: Text('Tab 3'), + ), + ], +), + +``` # Icons @@ -584,6 +631,16 @@ MacosSwitch( ), ``` +## MacosSegmentedControl + +Displays one or more navigational tabs in a single horizontal group. Used by `MacosTabBar` to navigate between the +different tabs of the tab bar. + + + +The typical usage of this widget is by `MacosTabView`, to control the navigation of its children. You do not need to +specify a `MacosSegmentedControl` with your `MacosTabView`, as it is built by that widget. + # Dialogs and Sheets ## MacosAlertDialog diff --git a/example/lib/main.dart b/example/lib/main.dart index e12ac6d1..83964684 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -4,6 +4,7 @@ import 'package:example/pages/dialogs_page.dart'; import 'package:example/pages/fields_page.dart'; import 'package:example/pages/indicators_page.dart'; import 'package:example/pages/selectors_page.dart'; +import 'package:example/pages/tabview_page.dart'; import 'package:example/pages/toolbar_page.dart'; import 'package:flutter/cupertino.dart'; import 'package:macos_ui/macos_ui.dart'; @@ -68,6 +69,7 @@ class _WidgetGalleryState extends State { const DialogsPage(), const ToolbarPage(), const SelectorsPage(), + const TabViewPage(), ]; @override @@ -213,6 +215,10 @@ class _WidgetGalleryState extends State { leading: MacosIcon(CupertinoIcons.calendar), label: Text('Selectors'), ), + SidebarItem( + leading: MacosIcon(CupertinoIcons.table_fill), + label: Text('TabView'), + ), ], ); }, diff --git a/example/lib/pages/buttons_page.dart b/example/lib/pages/buttons_page.dart index 600c5e89..22b03b57 100644 --- a/example/lib/pages/buttons_page.dart +++ b/example/lib/pages/buttons_page.dart @@ -16,6 +16,7 @@ class _ButtonsPageState extends State { String popupValue = 'One'; String languagePopupValue = 'English'; bool switchValue = false; + final _tabController = MacosTabController(initialIndex: 0, length: 3); @override Widget build(BuildContext context) { @@ -68,325 +69,348 @@ class _ButtonsPageState extends State { ); }, ), - ContentArea(builder: (context, scrollController) { - return SingleChildScrollView( - padding: const EdgeInsets.all(20), - child: Column( - children: [ - const Text('MacosBackButton'), - const SizedBox(height: 8), - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - MacosBackButton( - onPressed: () => debugPrint('click'), - fillColor: Colors.transparent, - ), - const SizedBox(width: 16.0), - MacosBackButton( - onPressed: () => debugPrint('click'), - ), - ], - ), - const SizedBox(height: 20), - const Text('MacosIconButton'), - const SizedBox(height: 8), - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - MacosIconButton( - icon: const MacosIcon( - CupertinoIcons.star_fill, + ContentArea( + builder: (context, scrollController) { + return SingleChildScrollView( + padding: const EdgeInsets.all(20), + child: Column( + children: [ + const Text('MacosBackButton'), + const SizedBox(height: 8), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + MacosBackButton( + onPressed: () => debugPrint('click'), + fillColor: Colors.transparent, ), - shape: BoxShape.rectangle, - borderRadius: BorderRadius.circular(7), - onPressed: () {}, - ), - const SizedBox(width: 8), - const MacosIconButton( - icon: MacosIcon( - CupertinoIcons.plus_app, + const SizedBox(width: 16.0), + MacosBackButton( + onPressed: () => debugPrint('click'), ), - shape: BoxShape.circle, - //onPressed: () {}, - ), - const SizedBox(width: 8), - MacosIconButton( - icon: const MacosIcon( - CupertinoIcons.minus_square, - ), - backgroundColor: Colors.transparent, - onPressed: () {}, - ), - ], - ), - const SizedBox(height: 20), - const Text('PushButton'), - const SizedBox(height: 8), - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - PushButton( - buttonSize: ButtonSize.large, - child: const Text('Large'), - onPressed: () { - MacosWindowScope.of(context).toggleSidebar(); - }, - ), - const SizedBox(width: 20), - PushButton( - buttonSize: ButtonSize.small, - child: const Text('Small'), - onPressed: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (_) { - return MacosScaffold( - toolBar: const ToolBar( - title: Text("New page"), - ), - children: [ - ContentArea( - builder: (context, scrollController) { - return Center( - child: PushButton( - buttonSize: ButtonSize.large, - child: const Text('Go Back'), - onPressed: () { - Navigator.maybePop(context); - }, - ), - ); - }, - ), - ResizablePane( - minWidth: 180, - startWidth: 200, - windowBreakpoint: 700, - resizableSide: ResizableSide.left, - builder: (_, __) { - return const Center( - child: Text('Resizable Pane'), - ); - }, - ), - ], - ); - }, - ), - ); - }, - ), - const SizedBox(width: 20), - PushButton( - buttonSize: ButtonSize.large, - isSecondary: true, - child: const Text('Secondary'), - onPressed: () { - MacosWindowScope.of(context).toggleSidebar(); - }, - ), - ], - ), - const SizedBox(height: 20), - const Text('MacosSwitch'), - const SizedBox(height: 8), - MacosSwitch( - value: switchValue, - onChanged: (value) { - setState(() => switchValue = value); - }, - ), - const SizedBox(height: 20), - const Text('MacosPulldownButton'), - const SizedBox(height: 8), - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - MacosPulldownButton( - title: "PDF", - items: [ - MacosPulldownMenuItem( - title: const Text('Open in Preview'), - onTap: () => debugPrint("Opening in preview..."), - ), - MacosPulldownMenuItem( - title: const Text('Save as PDF...'), - onTap: () => debugPrint("Saving as PDF..."), - ), - MacosPulldownMenuItem( - enabled: false, - title: const Text('Save as Postscript'), - onTap: () => debugPrint("Saving as Postscript..."), - ), - const MacosPulldownMenuDivider(), - MacosPulldownMenuItem( - enabled: false, - title: const Text('Save to iCloud Drive'), - onTap: () => debugPrint("Saving to iCloud..."), - ), - MacosPulldownMenuItem( - enabled: false, - title: const Text('Save to Web Receipts'), - onTap: () => debugPrint("Saving to Web Receipts..."), - ), - MacosPulldownMenuItem( - title: const Text('Send in Mail...'), - onTap: () => debugPrint("Sending via Mail..."), - ), - const MacosPulldownMenuDivider(), - MacosPulldownMenuItem( - title: const Text('Edit Menu...'), - onTap: () => debugPrint("Editing menu..."), - ), - ], - ), - const SizedBox(width: 20), - const MacosPulldownButton( - title: "PDF", - disabledTitle: "Disabled", - items: [], - ), - ], - ), - const SizedBox(height: 8), - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - MacosPulldownButton( - icon: CupertinoIcons.ellipsis_circle, - items: [ - MacosPulldownMenuItem( - title: const Text('New Folder'), - onTap: () => debugPrint("Creating new folder..."), - ), - MacosPulldownMenuItem( - title: const Text('Open'), - onTap: () => debugPrint("Opening..."), - ), - MacosPulldownMenuItem( - title: const Text('Open with...'), - onTap: () => debugPrint("Opening with..."), - ), - MacosPulldownMenuItem( - title: const Text('Import from iPhone...'), - onTap: () => debugPrint("Importing..."), + ], + ), + const SizedBox(height: 20), + const Text('MacosIconButton'), + const SizedBox(height: 8), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + MacosIconButton( + icon: const MacosIcon( + CupertinoIcons.star_fill, ), - const MacosPulldownMenuDivider(), - MacosPulldownMenuItem( - enabled: false, - title: const Text('Remove'), - onTap: () => debugPrint("Deleting..."), - ), - MacosPulldownMenuItem( - title: const Text('Move to Bin'), - onTap: () => debugPrint("Moving to Bin..."), + shape: BoxShape.rectangle, + borderRadius: BorderRadius.circular(7), + onPressed: () {}, + ), + const SizedBox(width: 8), + const MacosIconButton( + icon: MacosIcon( + CupertinoIcons.plus_app, ), - const MacosPulldownMenuDivider(), - MacosPulldownMenuItem( - title: const Text('Tags...'), - onTap: () => debugPrint("Tags..."), + shape: BoxShape.circle, + //onPressed: () {}, + ), + const SizedBox(width: 8), + MacosIconButton( + icon: const MacosIcon( + CupertinoIcons.minus_square, ), - ], - ), - const SizedBox(width: 20), - const MacosPulldownButton( - icon: CupertinoIcons.square_grid_3x2, - items: [], - ), - ], - ), - const SizedBox(height: 20), - const Text('MacosPopupButton'), - const SizedBox(height: 8), - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - MacosPopupButton( - value: popupValue, - onChanged: (String? newValue) { - setState(() => popupValue = newValue!); - }, - items: ['One', 'Two', 'Three', 'Four'] - .map>((String value) { - return MacosPopupMenuItem( - value: value, - child: Text(value), - ); - }).toList(), - ), - const SizedBox(width: 20), - MacosPopupButton( - disabledHint: const Text("Disabled"), - onChanged: null, - items: null, - ), - ], - ), - const SizedBox(height: 20), - MacosPopupButton( - value: languagePopupValue, - onChanged: (String? newValue) { - setState(() => languagePopupValue = newValue!); - }, - items: - languages.map>((String value) { - return MacosPopupMenuItem( - value: value, - child: Text(value), - ); - }).toList(), - ), - const SizedBox(height: 20), - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Text('System Theme'), - const SizedBox(width: 8), - MacosRadioButton( - groupValue: context.watch().mode, - value: ThemeMode.system, - onChanged: (value) { - context.read().mode = value!; - }, - ), - ], - ), - const SizedBox(height: 8), - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Text('Light Theme'), - const SizedBox(width: 24), - MacosRadioButton( - groupValue: context.watch().mode, - value: ThemeMode.light, - onChanged: (value) { - context.read().mode = value!; - }, - ), - ], - ), - const SizedBox(height: 8), - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Text('Dark Theme'), - const SizedBox(width: 26), - MacosRadioButton( - groupValue: context.watch().mode, - value: ThemeMode.dark, - onChanged: (value) { - context.read().mode = value!; - }, - ), - ], - ), - ], - ), - ); - }), + backgroundColor: Colors.transparent, + onPressed: () {}, + ), + ], + ), + const SizedBox(height: 20), + const Text('PushButton'), + const SizedBox(height: 8), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + PushButton( + buttonSize: ButtonSize.large, + child: const Text('Large'), + onPressed: () { + MacosWindowScope.of(context).toggleSidebar(); + }, + ), + const SizedBox(width: 20), + PushButton( + buttonSize: ButtonSize.small, + child: const Text('Small'), + onPressed: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (_) { + return MacosScaffold( + toolBar: const ToolBar( + title: Text("New page"), + ), + children: [ + ContentArea( + builder: (context, scrollController) { + return Center( + child: PushButton( + buttonSize: ButtonSize.large, + child: const Text('Go Back'), + onPressed: () { + Navigator.maybePop(context); + }, + ), + ); + }, + ), + ResizablePane( + minWidth: 180, + startWidth: 200, + windowBreakpoint: 700, + resizableSide: ResizableSide.left, + builder: (_, __) { + return const Center( + child: Text('Resizable Pane'), + ); + }, + ), + ], + ); + }, + ), + ); + }, + ), + const SizedBox(width: 20), + PushButton( + buttonSize: ButtonSize.large, + isSecondary: true, + child: const Text('Secondary'), + onPressed: () { + MacosWindowScope.of(context).toggleSidebar(); + }, + ), + ], + ), + const SizedBox(height: 20), + const Text('MacosSwitch'), + const SizedBox(height: 8), + MacosSwitch( + value: switchValue, + onChanged: (value) { + setState(() => switchValue = value); + }, + ), + const SizedBox(height: 20), + const Text('MacosPulldownButton'), + const SizedBox(height: 8), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + MacosPulldownButton( + title: "PDF", + items: [ + MacosPulldownMenuItem( + title: const Text('Open in Preview'), + onTap: () => debugPrint("Opening in preview..."), + ), + MacosPulldownMenuItem( + title: const Text('Save as PDF...'), + onTap: () => debugPrint("Saving as PDF..."), + ), + MacosPulldownMenuItem( + enabled: false, + title: const Text('Save as Postscript'), + onTap: () => debugPrint("Saving as Postscript..."), + ), + const MacosPulldownMenuDivider(), + MacosPulldownMenuItem( + enabled: false, + title: const Text('Save to iCloud Drive'), + onTap: () => debugPrint("Saving to iCloud..."), + ), + MacosPulldownMenuItem( + enabled: false, + title: const Text('Save to Web Receipts'), + onTap: () => + debugPrint("Saving to Web Receipts..."), + ), + MacosPulldownMenuItem( + title: const Text('Send in Mail...'), + onTap: () => debugPrint("Sending via Mail..."), + ), + const MacosPulldownMenuDivider(), + MacosPulldownMenuItem( + title: const Text('Edit Menu...'), + onTap: () => debugPrint("Editing menu..."), + ), + ], + ), + const SizedBox(width: 20), + const MacosPulldownButton( + title: "PDF", + disabledTitle: "Disabled", + items: [], + ), + ], + ), + const SizedBox(height: 8), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + MacosPulldownButton( + icon: CupertinoIcons.ellipsis_circle, + items: [ + MacosPulldownMenuItem( + title: const Text('New Folder'), + onTap: () => debugPrint("Creating new folder..."), + ), + MacosPulldownMenuItem( + title: const Text('Open'), + onTap: () => debugPrint("Opening..."), + ), + MacosPulldownMenuItem( + title: const Text('Open with...'), + onTap: () => debugPrint("Opening with..."), + ), + MacosPulldownMenuItem( + title: const Text('Import from iPhone...'), + onTap: () => debugPrint("Importing..."), + ), + const MacosPulldownMenuDivider(), + MacosPulldownMenuItem( + enabled: false, + title: const Text('Remove'), + onTap: () => debugPrint("Deleting..."), + ), + MacosPulldownMenuItem( + title: const Text('Move to Bin'), + onTap: () => debugPrint("Moving to Bin..."), + ), + const MacosPulldownMenuDivider(), + MacosPulldownMenuItem( + title: const Text('Tags...'), + onTap: () => debugPrint("Tags..."), + ), + ], + ), + const SizedBox(width: 20), + const MacosPulldownButton( + icon: CupertinoIcons.square_grid_3x2, + items: [], + ), + ], + ), + const SizedBox(height: 20), + const Text('MacosPopupButton'), + const SizedBox(height: 8), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + MacosPopupButton( + value: popupValue, + onChanged: (String? newValue) { + setState(() => popupValue = newValue!); + }, + items: ['One', 'Two', 'Three', 'Four'] + .map>((String value) { + return MacosPopupMenuItem( + value: value, + child: Text(value), + ); + }).toList(), + ), + const SizedBox(width: 20), + MacosPopupButton( + disabledHint: const Text("Disabled"), + onChanged: null, + items: null, + ), + ], + ), + const SizedBox(height: 20), + MacosPopupButton( + value: languagePopupValue, + onChanged: (String? newValue) { + setState(() => languagePopupValue = newValue!); + }, + items: languages + .map>((String value) { + return MacosPopupMenuItem( + value: value, + child: Text(value), + ); + }).toList(), + ), + const SizedBox(height: 20), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Text('System Theme'), + const SizedBox(width: 8), + MacosRadioButton( + groupValue: context.watch().mode, + value: ThemeMode.system, + onChanged: (value) { + context.read().mode = value!; + }, + ), + ], + ), + const SizedBox(height: 8), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Text('Light Theme'), + const SizedBox(width: 24), + MacosRadioButton( + groupValue: context.watch().mode, + value: ThemeMode.light, + onChanged: (value) { + context.read().mode = value!; + }, + ), + ], + ), + const SizedBox(height: 8), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Text('Dark Theme'), + const SizedBox(width: 26), + MacosRadioButton( + groupValue: context.watch().mode, + value: ThemeMode.dark, + onChanged: (value) { + context.read().mode = value!; + }, + ), + ], + ), + const SizedBox(height: 20), + const Text('MacosSegmentedControl'), + const SizedBox(height: 8), + MacosSegmentedControl( + controller: _tabController, + tabs: [ + MacosTab( + label: 'Tab 1', + active: _tabController.index == 0, + ), + MacosTab( + label: 'Tab 2', + active: _tabController.index == 1, + ), + MacosTab( + label: 'Tab 3', + active: _tabController.index == 2, + ), + ], + ), + ], + ), + ); + }, + ), ResizablePane( minWidth: 180, startWidth: 200, diff --git a/example/lib/pages/tabview_page.dart b/example/lib/pages/tabview_page.dart new file mode 100644 index 00000000..86c61bfe --- /dev/null +++ b/example/lib/pages/tabview_page.dart @@ -0,0 +1,62 @@ +import 'package:flutter/cupertino.dart'; +import 'package:macos_ui/macos_ui.dart'; + +class TabViewPage extends StatefulWidget { + const TabViewPage({super.key}); + + @override + State createState() => _TabViewPageState(); +} + +class _TabViewPageState extends State { + final _controller = MacosTabController( + initialIndex: 0, + length: 3, + ); + + @override + Widget build(BuildContext context) { + return MacosScaffold( + toolBar: const ToolBar( + title: Text('TabView'), + ), + children: [ + ContentArea( + builder: (context, scrollController) { + return Padding( + padding: const EdgeInsets.all(24.0), + child: MacosTabView( + controller: _controller, + tabs: [ + MacosTab( + label: 'Tab 1', + active: _controller.index == 0, + ), + MacosTab( + label: 'Tab 2', + active: _controller.index == 1, + ), + MacosTab( + label: 'Tab 3', + active: _controller.index == 2, + ), + ], + children: const [ + Center( + child: Text('Tab 1'), + ), + Center( + child: Text('Tab 2'), + ), + Center( + child: Text('Tab 3'), + ), + ], + ), + ); + }, + ), + ], + ); + } +} diff --git a/example/pubspec.lock b/example/pubspec.lock index a5c70a7e..779cfa22 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -87,7 +87,7 @@ packages: path: ".." relative: true source: path - version: "1.5.1" + version: "1.6.0" matcher: dependency: transitive description: diff --git a/lib/macos_ui.dart b/lib/macos_ui.dart index 8a7c8a4e..08a2ba91 100644 --- a/lib/macos_ui.dart +++ b/lib/macos_ui.dart @@ -6,7 +6,11 @@ /// other Flutter-supported platforms, we encourage the use of the following /// libraries for apps that run on other desktop platforms: /// * For Windows, [fluent_ui](https://pub.dev/packages/fluent_ui) -/// * For Linux, [yaru](https://pub.dev/packages/yaru) +/// * For Linux: +/// * [yaru](https://pub.dev/packages/yaru) +/// * [yaru_widgets](https://pub.dev/packages/yaru_widgets) +/// * [yaru_icons](https://pub.dev/packages/yaru_icons) +/// * [yaru_colors](https://pub.dev/packages/yaru_colors) library macos_ui; @@ -18,13 +22,14 @@ export 'src/buttons/popup_button.dart'; export 'src/buttons/pulldown_button.dart'; export 'src/buttons/push_button.dart'; export 'src/buttons/radio_button.dart'; +export 'src/buttons/segmented_control.dart'; export 'src/buttons/switch.dart'; export 'src/buttons/toolbar/toolbar_icon_button.dart'; export 'src/buttons/toolbar/toolbar_overflow_button.dart'; export 'src/buttons/toolbar/toolbar_pulldown_button.dart'; export 'src/dialogs/macos_alert_dialog.dart'; -export 'src/fields/text_field.dart'; export 'src/fields/search_field.dart'; +export 'src/fields/text_field.dart'; export 'src/icon/macos_icon.dart'; export 'src/indicators/capacity_indicators.dart'; export 'src/indicators/progress_indicators.dart'; @@ -40,6 +45,9 @@ export 'src/layout/scaffold.dart'; export 'src/layout/sidebar/sidebar.dart'; export 'src/layout/sidebar/sidebar_item.dart'; export 'src/layout/sidebar/sidebar_items.dart'; +export 'src/layout/tab_view/tab.dart'; +export 'src/layout/tab_view/tab_controller.dart'; +export 'src/layout/tab_view/tab_view.dart'; export 'src/layout/title_bar.dart'; export 'src/layout/toolbar/custom_toolbar_item.dart'; export 'src/layout/toolbar/toolbar.dart'; diff --git a/lib/src/buttons/segmented_control.dart b/lib/src/buttons/segmented_control.dart new file mode 100644 index 00000000..6b52315c --- /dev/null +++ b/lib/src/buttons/segmented_control.dart @@ -0,0 +1,117 @@ +import 'package:macos_ui/macos_ui.dart'; +import 'package:macos_ui/src/library.dart'; + +/// {@template macosSegmentedControl} +/// Displays one or more navigational tabs in a single horizontal group. +/// +/// Used by [MacosTabBar] to navigate between the different tabs of the tab bar. +/// +/// [MacosSegmentedControl] can be considered somewhat analogous to Flutter's +/// material `TabBar` in that it requires a list of [tabs]. Unlike `TabBar`, +/// however, [MacosSegmentedControl] explicitly requires a [controller]. +/// +/// See also: +/// * [MacosTab], which is a navigational item in a [MacosSegmentedControl]. +/// * [MacosTabView], which is a multi-page navigational view. +/// {@endtemplate} +class MacosSegmentedControl extends StatefulWidget { + /// {@macro macosSegmentedControl} + /// + /// [tabs] and [controller] must not be null. [tabs] must contain at least one + /// tab. + const MacosSegmentedControl({ + super.key, + required this.tabs, + required this.controller, + }) : assert(tabs.length > 0); + + /// The navigational items of this [MacosSegmentedControl]. + final List tabs; + + /// The [MacosTabController] that manages the [tabs] in this + /// [MacosSegmentedControl]. + final MacosTabController controller; + + @override + State createState() => _MacosSegmentedControlState(); +} + +class _MacosSegmentedControlState extends State { + @override + Widget build(BuildContext context) { + final brightness = MacosTheme.brightnessOf(context); + + return DecoratedBox( + decoration: BoxDecoration( + // Background color + color: brightness.resolve( + const Color(0xFFE3DEE8), + const Color(0xFF2D2934), + ), + borderRadius: const BorderRadius.all( + Radius.circular(5.0), + ), + // Outer border + border: Border.all( + color: brightness.resolve( + const Color(0xFFD8D3DC), + const Color(0xFF37333D), + ), + ), + ), + child: Padding( + padding: const EdgeInsets.all(1.0), + child: IntrinsicHeight( + child: IntrinsicWidth( + child: Row( + children: widget.tabs.map((t) { + final row = Row( + children: [ + GestureDetector( + onTap: () { + setState(() { + widget.controller.index = widget.tabs.indexOf(t); + }); + }, + onTapDown: (details) {}, + child: t.copyWith( + active: + widget.controller.index == widget.tabs.indexOf(t), + ), + ), + ], + ); + bool showDividerColor = true; + final last = widget.tabs.indexOf(t) == widget.tabs.length - 1; + if ((widget.controller.index - 1 == widget.tabs.indexOf(t)) || + (widget.controller.index + 1 == + widget.tabs.indexOf(t) + 1) || + last) { + showDividerColor = false; + } + + if (!last) { + row.children.add( + VerticalDivider( + color: showDividerColor + ? brightness.resolve( + const Color(0xFFC9C9C9), + const Color(0xFF26222C), + ) + : MacosColors.transparent, + width: 2.0, + indent: 5.0, + endIndent: 5.0, + ), + ); + } + + return row; + }).toList(growable: false), + ), + ), + ), + ), + ); + } +} diff --git a/lib/src/fields/search_field.dart b/lib/src/fields/search_field.dart index 820f523f..ccece251 100644 --- a/lib/src/fields/search_field.dart +++ b/lib/src/fields/search_field.dart @@ -1,7 +1,8 @@ import 'dart:async'; + +import 'package:flutter/services.dart'; import 'package:macos_ui/macos_ui.dart'; import 'package:macos_ui/src/library.dart'; -import 'package:flutter/services.dart'; const BorderRadius _kBorderRadius = BorderRadius.all(Radius.circular(7.0)); const double _kResultHeight = 20.0; diff --git a/lib/src/layout/tab_view/tab.dart b/lib/src/layout/tab_view/tab.dart new file mode 100644 index 00000000..c51122e0 --- /dev/null +++ b/lib/src/layout/tab_view/tab.dart @@ -0,0 +1,62 @@ +import 'package:macos_ui/src/library.dart'; +import 'package:macos_ui/src/theme/macos_colors.dart'; +import 'package:macos_ui/src/theme/macos_theme.dart'; + +const _kTabBorderRadius = BorderRadius.all( + Radius.circular(5.0), +); + +/// {@template macosTab} +/// A macOS-style navigational button used to move between the views of a +/// [MacosTabView]. +/// {@endtemplate} +class MacosTab extends StatelessWidget { + /// {@macro macosTab} + const MacosTab({ + super.key, + required this.label, + required this.active, + }); + + /// The display label for this tab. + final String label; + + /// Whether this [MacosTab] is currently selected. + final bool active; + + @override + Widget build(BuildContext context) { + final brightness = MacosTheme.brightnessOf(context); + + return PhysicalModel( + color: active ? const Color(0xFF625E66) : MacosColors.transparent, + borderRadius: _kTabBorderRadius, + child: DecoratedBox( + decoration: BoxDecoration( + borderRadius: _kTabBorderRadius, + color: active + ? brightness.resolve( + MacosColors.white, + const Color(0xFF625E66), + ) + : MacosColors.transparent, + ), + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 3), + child: Text(label), + ), + ), + ); + } + + /// Copies this [MacosTab] into another. + MacosTab copyWith({ + String? label, + bool? active, + }) { + return MacosTab( + label: label ?? this.label, + active: active ?? this.active, + ); + } +} diff --git a/lib/src/layout/tab_view/tab_controller.dart b/lib/src/layout/tab_view/tab_controller.dart new file mode 100644 index 00000000..2eab2521 --- /dev/null +++ b/lib/src/layout/tab_view/tab_controller.dart @@ -0,0 +1,51 @@ +import 'package:flutter/widgets.dart'; + +/// {@template macosTabController} +/// Coordinates tab selection for [MacosSegmentedControl] and [MacosTabView]. +/// +/// The [index] property is the index of the selected tab. +/// +/// A stateful widget that builds a [MacosSegmentedControl] and a +/// [MacosTabView] can create a MacosTabController and share it between them. +/// {@endtemplate} +class MacosTabController extends ChangeNotifier { + /// {@macro macosTabController} + MacosTabController({ + int initialIndex = 0, + required this.length, + }) : assert(length >= 0), + assert(initialIndex >= 0 && (length == 0 || initialIndex < length)), + _index = initialIndex, + _previousIndex = initialIndex; + + /// The total number of tabs. + /// + /// Typically greater than one. Must match [MacosTabView.tabs]'s and + /// [MacosTabView.children]'s length. + final int length; + + void _changeIndex(int value) { + assert(value >= 0 && (value < length || length == 0)); + if (value == _index || length < 2) { + return; + } + _previousIndex = index; + _index = value; + notifyListeners(); + } + + /// The index of the currently selected tab. + /// + /// Changing the index also updates [previousIndex] and notifies listeners. + int get index => _index; + int _index; + set index(int value) { + _changeIndex(value); + } + + /// The index of the previously selected tab. + /// + /// Initially the same as index.` + int get previousIndex => _previousIndex; + int _previousIndex; +} diff --git a/lib/src/layout/tab_view/tab_view.dart b/lib/src/layout/tab_view/tab_view.dart new file mode 100644 index 00000000..ba72f638 --- /dev/null +++ b/lib/src/layout/tab_view/tab_view.dart @@ -0,0 +1,184 @@ +import 'package:macos_ui/src/buttons/segmented_control.dart'; +import 'package:macos_ui/src/layout/tab_view/tab.dart'; +import 'package:macos_ui/src/layout/tab_view/tab_controller.dart'; +import 'package:macos_ui/src/library.dart'; +import 'package:macos_ui/src/theme/macos_theme.dart'; + +const _kTabViewRadius = BorderRadius.all( + Radius.circular(5.0), +); + +/// Specifies layout position for [MacosTab] options inside [MacosTabView]. +enum MacosTabPosition { + left, + right, + top, + bottom, +} + +/// {@template macosTabView} +/// A multipage interface that displays one page at a time. +/// +/// {@image } +/// +/// A tab view contains a row of navigational items, [tabs], that move the +/// user through the provided views ([children]). The user selects the desired +/// page by clicking the appropriate tab. +/// +/// The tab controller's [MacosTabController.length] must equal the length of +/// the [children] list and the length of the [tabs] list. +/// {@endtemplate} +class MacosTabView extends StatefulWidget { + /// {@macro macosTabView} + const MacosTabView({ + super.key, + required this.controller, + required this.tabs, + required this.children, + this.position = MacosTabPosition.top, + }) : assert(controller.length == children.length && + controller.length == tabs.length); + + /// This widget's selection state. + final MacosTabController controller; + + /// A list of navigational items, typically a length of two or more. + final List tabs; + + /// The views to navigate between. + /// + /// There must be one widget per tab. + final List children; + + /// The placement of the [tabs], typically [MacosTabPosition.top]. + final MacosTabPosition position; + + @override + State createState() => _MacosTabViewState(); +} + +class _MacosTabViewState extends State { + late List _childrenWithKey; + int? _currentIndex; + + int get _tabRotation { + switch (widget.position) { + case MacosTabPosition.left: + return 3; + case MacosTabPosition.right: + return 1; + case MacosTabPosition.top: + return 0; + case MacosTabPosition.bottom: + return 0; + } + } + + void _updateTabController() { + widget.controller.addListener(_handleTabControllerTick); + } + + void _handleTabControllerTick() { + if (widget.controller.index != _currentIndex) { + _currentIndex = widget.controller.index; + } + setState(() { + // Rebuild the children after an index change + // has completed. + }); + } + + @override + void initState() { + super.initState(); + _updateChildren(); + } + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + _updateTabController(); + _currentIndex = widget.controller.index; + } + + @override + void didUpdateWidget(MacosTabView oldWidget) { + super.didUpdateWidget(oldWidget); + if (widget.controller != oldWidget.controller) { + _updateTabController(); + _currentIndex = widget.controller.index; + } + if (widget.children != oldWidget.children) { + _updateChildren(); + } + } + + @override + void dispose() { + widget.controller.removeListener(_handleTabControllerTick); + super.dispose(); + } + + void _updateChildren() { + _childrenWithKey = KeyedSubtree.ensureUniqueKeysForList(widget.children); + } + + @override + Widget build(BuildContext context) { + assert(() { + if (widget.controller.length != widget.children.length) { + throw FlutterError( + "Controller's length property (${widget.controller.length}) does not match the " + "number of tabs (${widget.children.length}) present in TabBar's tabs property.", + ); + } + return true; + }()); + + final brightness = MacosTheme.brightnessOf(context); + + final outerBorderColor = brightness.resolve( + const Color(0xFFDED9E3), + const Color(0xFF3F3E45), + ); + + return Stack( + alignment: Alignment.center, + children: [ + Padding( + padding: const EdgeInsets.all(12.0), + child: DecoratedBox( + decoration: BoxDecoration( + color: brightness.resolve( + const Color(0xFFE9E4EB), + const Color(0xFF29242F), + ), + border: Border.all( + color: outerBorderColor, + width: 1.0, + ), + borderRadius: _kTabViewRadius, + ), + child: IndexedStack( + index: _currentIndex, + children: _childrenWithKey, + ), + ), + ), + Positioned( + top: widget.position == MacosTabPosition.top ? 0 : null, + bottom: widget.position == MacosTabPosition.bottom ? 0 : null, + left: widget.position == MacosTabPosition.left ? 0 : null, + right: widget.position == MacosTabPosition.right ? 0 : null, + child: RotatedBox( + quarterTurns: _tabRotation, + child: MacosSegmentedControl( + controller: widget.controller, + tabs: widget.tabs, + ), + ), + ), + ], + ); + } +} diff --git a/lib/src/layout/toolbar/toolbar_overflow_menu.dart b/lib/src/layout/toolbar/toolbar_overflow_menu.dart index df45ff8e..d384df66 100644 --- a/lib/src/layout/toolbar/toolbar_overflow_menu.dart +++ b/lib/src/layout/toolbar/toolbar_overflow_menu.dart @@ -1,5 +1,5 @@ -import 'package:macos_ui/src/library.dart'; import 'package:macos_ui/macos_ui.dart'; +import 'package:macos_ui/src/library.dart'; const BorderRadius _kBorderRadius = BorderRadius.all(Radius.circular(5.0)); diff --git a/lib/src/layout/toolbar/toolbar_popup.dart b/lib/src/layout/toolbar/toolbar_popup.dart index 4a04256f..d4e9deab 100644 --- a/lib/src/layout/toolbar/toolbar_popup.dart +++ b/lib/src/layout/toolbar/toolbar_popup.dart @@ -1,6 +1,7 @@ -import 'package:macos_ui/src/library.dart'; import 'dart:math' as math; +import 'package:macos_ui/src/library.dart'; + /// Where the popup will be placed vertically relative to the child enum ToolbarPopupPosition { /// The popup will be above the child, if there is enough space available diff --git a/lib/src/theme/overlay_filter.dart b/lib/src/theme/overlay_filter.dart index 7f0e8da4..2dd9b78e 100644 --- a/lib/src/theme/overlay_filter.dart +++ b/lib/src/theme/overlay_filter.dart @@ -1,4 +1,5 @@ import 'dart:ui'; + import 'package:macos_ui/macos_ui.dart'; import 'package:macos_ui/src/library.dart'; diff --git a/pubspec.yaml b/pubspec.yaml index ed1c8632..7f0bab0e 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: macos_ui description: Flutter widgets and themes implementing the current macOS design language. -version: 1.5.1 +version: 1.6.0 homepage: "https://macosui.dev" repository: "https://github.com/GroovinChip/macos_ui" From fdba08d0535fa3ad332c2ddd244c33db46208a12 Mon Sep 17 00:00:00 2001 From: GroovinChip Date: Mon, 4 Jul 2022 17:12:15 -0400 Subject: [PATCH 02/17] chore: fixup scripts --- pr_prelaunch_tasks.sh | 14 +++++++------- publish_tasks.sh | 4 ++-- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/pr_prelaunch_tasks.sh b/pr_prelaunch_tasks.sh index 049e655f..0d836662 100644 --- a/pr_prelaunch_tasks.sh +++ b/pr_prelaunch_tasks.sh @@ -4,18 +4,18 @@ if [ $? -eq 1 ]; then git add . git commit -m "chore: run flutter format ." echo "push changes? [y/n]" - read pushResponse - if [ $pushResponse = "y" ]; then + read -r pushResponse + if [ "$pushResponse" = "y" ]; then git push origin fi fi echo "Run dart fix --dry-run? [y/n]" -read dryRunResponse +read -r dryRunResponse if [ "$dryRunResponse" = "y" ]; then dart fix --dry-run fi echo "Run dart fix --apply? [y/n]" -read applyResponse +read -r applyResponse if [ "$applyResponse" = "y" ]; then dart fix --apply if [ -z "$(git status --porcelain)" ]; then @@ -24,14 +24,14 @@ if [ "$applyResponse" = "y" ]; then git add . git commit -m "chore: run dart fix --apply" echo "push changes? [y/n]" - read pushResponse - if [ $pushResponse = "y" ]; then + read -r pushResponse + if [ "$pushResponse" = "y" ]; then git push origin fi fi fi echo "Run tests? [y/n]" -read testResponse +read -r testResponse if [ "$testResponse" = "y" ]; then flutter test else diff --git a/publish_tasks.sh b/publish_tasks.sh index 21913030..a6aa33e1 100644 --- a/publish_tasks.sh +++ b/publish_tasks.sh @@ -1,14 +1,14 @@ # MAINTAINER ONLY SCRIPT. DO NOT RUN THIS SCRIPT UNLESS YOU ARE THE MAINTAINER. pana --no-warning echo "Are you ready to dry-run publish macos_ui? [y/n]" -read dryRunResponse +read -r dryRunResponse if [ "$dryRunResponse" = "y" ]; then flutter pub publish --dry-run else exit 0 fi echo "Are you ready to publish macos_ui to pub.dev? [y/n]" -read publishResponse +read -r publishResponse if [ "$publishResponse" = "y" ]; then flutter pub publish else From 572798e9ae87718a9a56ba738c6d107f41ad8f19 Mon Sep 17 00:00:00 2001 From: GroovinChip Date: Mon, 4 Jul 2022 17:27:34 -0400 Subject: [PATCH 03/17] test: tests for segmented control & tab view --- test/buttons/segmented_control_test.dart | 100 +++++++++++++++++++++++ test/layout/tab_view_test.dart | 67 +++++++++++++++ 2 files changed, 167 insertions(+) create mode 100644 test/buttons/segmented_control_test.dart create mode 100644 test/layout/tab_view_test.dart diff --git a/test/buttons/segmented_control_test.dart b/test/buttons/segmented_control_test.dart new file mode 100644 index 00000000..1b228d46 --- /dev/null +++ b/test/buttons/segmented_control_test.dart @@ -0,0 +1,100 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:macos_ui/macos_ui.dart'; +import 'package:macos_ui/src/library.dart'; + +void main() { + group('MacosSegmentedControl tests', () { + testWidgets( + 'Tapping an unselected item changes the currently selected item', + (tester) async { + final controller = MacosTabController(length: 3, initialIndex: 0); + await tester.pumpWidget( + MacosApp( + home: MacosWindow( + child: MacosScaffold( + children: [ + ContentArea( + builder: (context, scrollController) { + return Center( + child: MacosSegmentedControl( + controller: controller, + tabs: [ + MacosTab( + label: 'Tab 1', + active: controller.index == 0, + ), + MacosTab( + label: 'Tab 2', + active: controller.index == 1, + ), + MacosTab( + label: 'Tab 3', + active: controller.index == 2, + ), + ], + ), + ); + }, + ), + ], + ), + ), + ), + ); + + final macosSegmentedControl = find.byType(MacosSegmentedControl); + expect(macosSegmentedControl, findsOneWidget); + final secondTab = find.byType(MacosTab).at(1); + expect(secondTab, findsOneWidget); + await tester.tap(secondTab); + await tester.pumpAndSettle(); + expect(controller.index, 1); + }, + ); + + testWidgets('Tapping the currently selected item does nothing', (tester) async { + final controller = MacosTabController(length: 3, initialIndex: 0); + await tester.pumpWidget( + MacosApp( + home: MacosWindow( + child: MacosScaffold( + children: [ + ContentArea( + builder: (context, scrollController) { + return Center( + child: MacosSegmentedControl( + controller: controller, + tabs: [ + MacosTab( + label: 'Tab 1', + active: controller.index == 0, + ), + MacosTab( + label: 'Tab 2', + active: controller.index == 1, + ), + MacosTab( + label: 'Tab 3', + active: controller.index == 2, + ), + ], + ), + ); + }, + ), + ], + ), + ), + ), + ); + + final macosSegmentedControl = find.byType(MacosSegmentedControl); + expect(macosSegmentedControl, findsOneWidget); + final firstTab = find.byType(MacosTab).first; + expect(firstTab, findsOneWidget); + await tester.tap(firstTab); + await tester.pumpAndSettle(); + expect(controller.index, 0); + },); + }); +} diff --git a/test/layout/tab_view_test.dart b/test/layout/tab_view_test.dart new file mode 100644 index 00000000..96c399fd --- /dev/null +++ b/test/layout/tab_view_test.dart @@ -0,0 +1,67 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:macos_ui/macos_ui.dart'; +import 'package:macos_ui/src/library.dart'; + +void main() { + group('MacosTabView tests', () { + testWidgets( + 'Tapping a tab changes the child in view', + (tester) async { + final controller = MacosTabController(length: 3, initialIndex: 0); + await tester.pumpWidget( + MacosApp( + home: MacosWindow( + child: MacosScaffold( + children: [ + ContentArea( + builder: (context, scrollController) { + return Padding( + padding: const EdgeInsets.all(24.0), + child: MacosTabView( + controller: controller, + tabs: [ + MacosTab( + label: 'Tab 1', + active: controller.index == 0, + ), + MacosTab( + label: 'Tab 2', + active: controller.index == 1, + ), + MacosTab( + label: 'Tab 3', + active: controller.index == 2, + ), + ], + children: const [ + Center( + child: Text('Tab child 1'), + ), + Center( + child: Text('Tab child 2'), + ), + Center( + child: Text('Tab child 3'), + ), + ], + ), + ); + }, + ), + ], + ), + ), + ), + ); + + final segmentedControl = find.byType(MacosSegmentedControl); + expect(segmentedControl, findsOneWidget); + final secondTab = find.byType(MacosTab).at(1); + expect(secondTab, findsOneWidget); + await tester.tap(secondTab); + await tester.pumpAndSettle(); + expect(controller.index, 1); + }, + ); + }); +} From 101289f4e9f388f7d74bdcb84fa05cd949ca2b2a Mon Sep 17 00:00:00 2001 From: GroovinChip Date: Mon, 4 Jul 2022 17:37:40 -0400 Subject: [PATCH 04/17] chore: remove unused code --- lib/src/buttons/segmented_control.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/src/buttons/segmented_control.dart b/lib/src/buttons/segmented_control.dart index 6b52315c..821798dc 100644 --- a/lib/src/buttons/segmented_control.dart +++ b/lib/src/buttons/segmented_control.dart @@ -73,7 +73,6 @@ class _MacosSegmentedControlState extends State { widget.controller.index = widget.tabs.indexOf(t); }); }, - onTapDown: (details) {}, child: t.copyWith( active: widget.controller.index == widget.tabs.indexOf(t), From 827e7586d9fbf5f1ad136909f831253e9e0a0799 Mon Sep 17 00:00:00 2001 From: GroovinChip Date: Mon, 4 Jul 2022 17:43:32 -0400 Subject: [PATCH 05/17] chore: run flutter format . --- test/buttons/segmented_control_test.dart | 85 ++++++++++++------------ 1 file changed, 44 insertions(+), 41 deletions(-) diff --git a/test/buttons/segmented_control_test.dart b/test/buttons/segmented_control_test.dart index 1b228d46..d598eb7a 100644 --- a/test/buttons/segmented_control_test.dart +++ b/test/buttons/segmented_control_test.dart @@ -52,49 +52,52 @@ void main() { }, ); - testWidgets('Tapping the currently selected item does nothing', (tester) async { - final controller = MacosTabController(length: 3, initialIndex: 0); - await tester.pumpWidget( - MacosApp( - home: MacosWindow( - child: MacosScaffold( - children: [ - ContentArea( - builder: (context, scrollController) { - return Center( - child: MacosSegmentedControl( - controller: controller, - tabs: [ - MacosTab( - label: 'Tab 1', - active: controller.index == 0, - ), - MacosTab( - label: 'Tab 2', - active: controller.index == 1, - ), - MacosTab( - label: 'Tab 3', - active: controller.index == 2, - ), - ], - ), - ); - }, - ), - ], + testWidgets( + 'Tapping the currently selected item does nothing', + (tester) async { + final controller = MacosTabController(length: 3, initialIndex: 0); + await tester.pumpWidget( + MacosApp( + home: MacosWindow( + child: MacosScaffold( + children: [ + ContentArea( + builder: (context, scrollController) { + return Center( + child: MacosSegmentedControl( + controller: controller, + tabs: [ + MacosTab( + label: 'Tab 1', + active: controller.index == 0, + ), + MacosTab( + label: 'Tab 2', + active: controller.index == 1, + ), + MacosTab( + label: 'Tab 3', + active: controller.index == 2, + ), + ], + ), + ); + }, + ), + ], + ), ), ), - ), - ); + ); - final macosSegmentedControl = find.byType(MacosSegmentedControl); - expect(macosSegmentedControl, findsOneWidget); - final firstTab = find.byType(MacosTab).first; - expect(firstTab, findsOneWidget); - await tester.tap(firstTab); - await tester.pumpAndSettle(); - expect(controller.index, 0); - },); + final macosSegmentedControl = find.byType(MacosSegmentedControl); + expect(macosSegmentedControl, findsOneWidget); + final firstTab = find.byType(MacosTab).first; + expect(firstTab, findsOneWidget); + await tester.tap(firstTab); + await tester.pumpAndSettle(); + expect(controller.index, 0); + }, + ); }); } From d28dbc18940ceb9b6f98dc0fd00d96b5a54818fc Mon Sep 17 00:00:00 2001 From: GroovinChip Date: Mon, 4 Jul 2022 17:45:35 -0400 Subject: [PATCH 06/17] chore: bump code metrics --- pubspec.lock | 2 +- pubspec.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pubspec.lock b/pubspec.lock index 95cc4dea..810a1738 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -119,7 +119,7 @@ packages: name: dart_code_metrics url: "https://pub.dartlang.org" source: hosted - version: "4.15.0" + version: "4.16.0" dart_style: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 7f0bab0e..a46af1e1 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -15,7 +15,7 @@ dependencies: dev_dependencies: flutter_test: sdk: flutter - dart_code_metrics: ^4.14.0 + dart_code_metrics: ^4.16.0 flutter_lints: ^2.0.1 mocktail: ^0.3.0 From 477498b2b8b22dc3c7395f5e5c368d12f1a3eee1 Mon Sep 17 00:00:00 2001 From: GroovinChip Date: Mon, 4 Jul 2022 18:23:01 -0400 Subject: [PATCH 07/17] docs: dartdoc updates --- CHANGELOG.md | 3 ++ lib/src/buttons/push_button.dart | 7 +++ lib/src/buttons/radio_button.dart | 1 + lib/src/buttons/switch.dart | 3 ++ lib/src/indicators/capacity_indicators.dart | 6 +++ lib/src/labels/label.dart | 7 +-- lib/src/labels/tooltip.dart | 4 +- lib/src/layout/resizable_pane.dart | 25 ++++++--- lib/src/layout/tab_view/tab_view.dart | 7 +++ lib/src/layout/toolbar/overflow_handler.dart | 28 ++++++++++ lib/src/layout/toolbar/toolbar.dart | 3 ++ lib/src/layout/toolbar/toolbar_popup.dart | 23 +++++++- lib/src/selectors/caret_painters.dart | 14 +++++ lib/src/selectors/date_picker.dart | 5 ++ lib/src/sheets/macos_sheet.dart | 3 ++ lib/src/theme/date_picker_theme.dart | 42 +++++++++++++++ lib/src/theme/help_button_theme.dart | 1 + lib/src/theme/icon_button_theme.dart | 1 + lib/src/theme/macos_colors.dart | 5 ++ lib/src/theme/macos_dynamic_color.dart | 1 + lib/src/theme/macos_theme.dart | 19 ++++--- lib/src/theme/popup_button_theme.dart | 2 + lib/src/theme/pulldown_button_theme.dart | 2 + lib/src/theme/push_button_theme.dart | 2 + lib/src/theme/scrollbar_theme.dart | 1 + lib/src/theme/search_field_theme.dart | 1 + lib/src/theme/time_picker_theme.dart | 41 ++++++++++++++ lib/src/theme/tooltip_theme.dart | 57 ++++++++++++-------- test/theme/tooltip_theme_test.dart | 18 +++---- 29 files changed, 280 insertions(+), 52 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a97af5f0..b8f3bf4e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ ## [1.6.0] * New widgets: `MacosTabView` and `MacosTabView` +* BREAKING CHANGE: `Label.yAxis` has been renamed to `Label.crossAxisAlignment` +* BREAKING CHANGE: `TooltipTheme` and `TooltipThemeData` have been renamed to `MacosTooltipTheme` and +`MacosTooltipThemeData` ## [1.5.1] * Correct the placement of the leading widget in disclosure sidebar items [#268](https://github.com/GroovinChip/macos_ui/issues/268) diff --git a/lib/src/buttons/push_button.dart b/lib/src/buttons/push_button.dart index 639b60ad..27759e1c 100644 --- a/lib/src/buttons/push_button.dart +++ b/lib/src/buttons/push_button.dart @@ -5,8 +5,12 @@ import 'package:flutter/rendering.dart'; import 'package:macos_ui/macos_ui.dart'; import 'package:macos_ui/src/library.dart'; +/// The sizes a [PushButton] can be. enum ButtonSize { + /// A large [PushButton]. large, + + /// A small [PushButton]. small, } @@ -22,8 +26,11 @@ const EdgeInsetsGeometry _kLargeButtonPadding = EdgeInsets.symmetric( const BorderRadius _kSmallButtonRadius = BorderRadius.all(Radius.circular(5.0)); const BorderRadius _kLargeButtonRadius = BorderRadius.all(Radius.circular(7.0)); +/// {@template pushButton} /// A macOS-style button. +/// {@endtemplate} class PushButton extends StatefulWidget { + /// {@macro pushButton} const PushButton({ super.key, required this.child, diff --git a/lib/src/buttons/radio_button.dart b/lib/src/buttons/radio_button.dart index aa053543..57097758 100644 --- a/lib/src/buttons/radio_button.dart +++ b/lib/src/buttons/radio_button.dart @@ -63,6 +63,7 @@ class MacosRadioButton extends StatelessWidget { /// Whether the button is disabled or not bool get isDisabled => onChanged == null; + /// Whether the button is selected or not. bool get selected => value == groupValue; @override diff --git a/lib/src/buttons/switch.dart b/lib/src/buttons/switch.dart index 53538de1..6dd4375e 100644 --- a/lib/src/buttons/switch.dart +++ b/lib/src/buttons/switch.dart @@ -4,10 +4,13 @@ import 'package:flutter/gestures.dart'; import 'package:macos_ui/macos_ui.dart'; import 'package:macos_ui/src/library.dart'; +/// {@template macosSwitch} /// A switch is a visual toggle between two mutually exclusive /// states — on and off. A switch shows that it's on when the /// accent color is visible and off when the switch appears colorless. +/// {@endtemplate} class MacosSwitch extends StatelessWidget { + /// {@macro macosSwitch} const MacosSwitch({ super.key, required this.value, diff --git a/lib/src/indicators/capacity_indicators.dart b/lib/src/indicators/capacity_indicators.dart index bcdb33eb..461f3055 100644 --- a/lib/src/indicators/capacity_indicators.dart +++ b/lib/src/indicators/capacity_indicators.dart @@ -174,10 +174,16 @@ class CapacityIndicatorCell extends StatelessWidget { this.backgroundColor = CupertinoColors.tertiarySystemGroupedBackground, }) : assert(value >= 0 && value <= 100); + /// The color of the cell. final Color color; + + /// The background color of the cell. final Color backgroundColor; + + /// The border color of the cell. final Color borderColor; + /// The current value of the cell. final double value; @override diff --git a/lib/src/labels/label.dart b/lib/src/labels/label.dart index 34fcea42..78bdb84e 100644 --- a/lib/src/labels/label.dart +++ b/lib/src/labels/label.dart @@ -13,7 +13,7 @@ class Label extends StatelessWidget { this.icon, required this.text, this.child, - this.yAlignment = CrossAxisAlignment.start, + this.crossAxisAlignment = CrossAxisAlignment.start, }); /// The icon used by the label. If non-null, it's rendered horizontally @@ -29,7 +29,8 @@ class Label extends StatelessWidget { /// The widget at the right of [text]. final Widget? child; - final CrossAxisAlignment yAlignment; + /// The cross-axis alignment of the label. + final CrossAxisAlignment crossAxisAlignment; @override Widget build(BuildContext context) { @@ -43,7 +44,7 @@ class Label extends StatelessWidget { child: this.text, ); return Row( - crossAxisAlignment: yAlignment, + crossAxisAlignment: crossAxisAlignment, mainAxisSize: MainAxisSize.min, children: [ if (icon != null) diff --git a/lib/src/labels/tooltip.dart b/lib/src/labels/tooltip.dart index e055b75b..dbc530b3 100644 --- a/lib/src/labels/tooltip.dart +++ b/lib/src/labels/tooltip.dart @@ -17,7 +17,7 @@ import 'package:macos_ui/src/library.dart'; /// ![Tooltip Preview](https://developer.apple.com/design/human-interface-guidelines/macos/images/help_Tooltip.png) /// /// See also: -/// * [TooltipThemeData], used to define how the tooltip will look like +/// * [MacosTooltipThemeData], used to define how the tooltip will look like class MacosTooltip extends StatefulWidget { /// Creates a tooltip. /// @@ -257,7 +257,7 @@ class _MacosTooltipState extends State Widget build(BuildContext context) { assert(debugCheckHasMacosTheme(context)); assert(Overlay.of(context, debugRequiredFor: widget) != null); - final tooltipTheme = TooltipTheme.of(context); + final tooltipTheme = MacosTooltipTheme.of(context); height = tooltipTheme.height!; padding = tooltipTheme.padding!; diff --git a/lib/src/layout/resizable_pane.dart b/lib/src/layout/resizable_pane.dart index 91e09f4b..4c6c78f2 100644 --- a/lib/src/layout/resizable_pane.dart +++ b/lib/src/layout/resizable_pane.dart @@ -9,16 +9,25 @@ import 'package:macos_ui/src/theme/macos_theme.dart'; const EdgeInsets kResizablePaneSafeArea = EdgeInsets.only(top: 52); /// Indicates the draggable side of the [ResizablePane] for resizing -enum ResizableSide { left, right } +enum ResizableSide { + /// The left side of the [ResizablePane]. + left, + /// The right side of the [ResizablePane]. + right, +} + +/// {@template resizablePane} +/// A widget that can be resized horizontally. +/// +/// The [builder], [minWidth] and [resizableSide] can not be null. +/// The [maxWidth] and the [windowBreakpoint] default to `500.00`. +/// [isResizable] defaults to `true`. +/// +/// The [startWidth] is the initial width. +/// {@endtemplate} class ResizablePane extends StatefulWidget { - /// Creates a widget that can be resized horizontally. - /// - /// The [builder], [minWidth] and [resizableSide] can not be null. - /// The [maxWidth] and the [windowBreakpoint] default to `500.00`. - /// [isResizable] defaults to `true`. - /// - /// The [startWidth] is the initial width. + /// {@macro resizablePane} const ResizablePane({ super.key, required this.builder, diff --git a/lib/src/layout/tab_view/tab_view.dart b/lib/src/layout/tab_view/tab_view.dart index ba72f638..02dbf37f 100644 --- a/lib/src/layout/tab_view/tab_view.dart +++ b/lib/src/layout/tab_view/tab_view.dart @@ -10,9 +10,16 @@ const _kTabViewRadius = BorderRadius.all( /// Specifies layout position for [MacosTab] options inside [MacosTabView]. enum MacosTabPosition { + /// The left side of the [MacosTabView]. left, + + /// The right side of the [MacosTabView]. right, + + /// The top side of the [MacosTabView]. top, + + /// The bottom side of the [MacosTabView]. bottom, } diff --git a/lib/src/layout/toolbar/overflow_handler.dart b/lib/src/layout/toolbar/overflow_handler.dart index b6b2241b..dd1a1d5a 100644 --- a/lib/src/layout/toolbar/overflow_handler.dart +++ b/lib/src/layout/toolbar/overflow_handler.dart @@ -11,6 +11,7 @@ typedef OverflowHandlerChangedCallback = void Function( List hiddenChildren, ); +/// {@template overflowHandler} /// Lays out children widgets in a single run, and if there is not /// room to display them all, it will hide widgets that don't fit, /// and display the "overflow widget" at the end. Optionally, the @@ -18,7 +19,9 @@ typedef OverflowHandlerChangedCallback = void Function( /// overflow widget will take precedence over any children widgets. /// /// Adapted from [Wrap]. +/// {@endtemplate} class OverflowHandler extends MultiChildRenderObjectWidget { + /// {@macro overflowHandler} OverflowHandler({ super.key, this.alignment = MainAxisAlignment.start, @@ -47,18 +50,25 @@ class OverflowHandler extends MultiChildRenderObjectWidget { /// Defaults to [Clip.none]. final Clip clipBehavior; + /// The breakpoint at which the items should overflow. final double overflowBreakpoint; + /// {@template overflowWidgetAlignment} /// The alignment of the overflow widget between the end of the /// visible regular children and the end of the container. + /// {@endtemplate} final MainAxisAlignment overflowWidgetAlignment; + /// {@template alwaysDisplayOverflowWidget} /// Whether or not to always display the overflowWidget, even if /// all other widgets are able to be displayed. + /// {@endtemplate} final bool alwaysDisplayOverflowWidget; + /// {@template overflowChangedCallback} /// Function that is called when the list of children that are /// hidden because of the dynamic overflow has changed. + /// {@endtemplate} final OverflowHandlerChangedCallback? overflowChangedCallback; @override @@ -124,12 +134,15 @@ class OverflowHandlerParentData extends ContainerBoxParentData { bool _isHidden = false; } +/// {@template renderOverflowHandler} /// Rendering logic for [OverflowHandler] widget. /// Adapted from [RenderWrap]. +/// {@endtemplate} class RenderOverflowHandler extends RenderBox with ContainerRenderObjectMixin, RenderBoxContainerDefaultsMixin { + /// {@macro renderOverflowHandler} RenderOverflowHandler({ required MainAxisAlignment alignment, required CrossAxisAlignment crossAxisAlignment, @@ -148,6 +161,8 @@ class RenderOverflowHandler extends RenderBox _alwaysDisplayOverflowWidget = alwaysDisplayOverflowWidget; double _overflowBreakpoint; + + /// The breakpoint at which the items should overflow. double get overflowBreakpoint => _overflowBreakpoint; set overflowBreakpoint(double value) { if (_overflowBreakpoint != value) { @@ -157,6 +172,8 @@ class RenderOverflowHandler extends RenderBox } MainAxisAlignment _alignment; + + /// {@macro flutter.widgets.wrap.alignment} MainAxisAlignment get alignment => _alignment; set alignment(MainAxisAlignment value) { if (_alignment != value) { @@ -166,6 +183,8 @@ class RenderOverflowHandler extends RenderBox } CrossAxisAlignment _crossAxisAlignment; + + /// {@macro flutter.widgets.wrap.crossAxisAlignment} CrossAxisAlignment get crossAxisAlignment => _crossAxisAlignment; set crossAxisAlignment(CrossAxisAlignment value) { if (_crossAxisAlignment != value) { @@ -175,6 +194,8 @@ class RenderOverflowHandler extends RenderBox } TextDirection? _textDirection; + + /// {@macro flutter.widgets.wrap.textDirection} TextDirection? get textDirection => _textDirection; set textDirection(TextDirection? value) { if (_textDirection != value) { @@ -184,6 +205,8 @@ class RenderOverflowHandler extends RenderBox } Clip _clipBehavior; + + /// {@macro flutter.material.Material.clipBehavior} Clip get clipBehavior => _clipBehavior; set clipBehavior(Clip value) { if (_clipBehavior != value) { @@ -194,6 +217,8 @@ class RenderOverflowHandler extends RenderBox } MainAxisAlignment _overflowWidgetAlignment; + + /// {@macro overflowWidgetAlignment} MainAxisAlignment get overflowWidgetAlignment => _overflowWidgetAlignment; set overflowWidgetAlignment(MainAxisAlignment value) { if (_overflowWidgetAlignment != value) { @@ -203,6 +228,8 @@ class RenderOverflowHandler extends RenderBox } bool _alwaysDisplayOverflowWidget; + + /// {@macro alwaysDisplayOverflowWidget} bool get alwaysDisplayOverflowWidget => _alwaysDisplayOverflowWidget; set alwaysDisplayOverflowWidget(bool value) { if (_alwaysDisplayOverflowWidget != value) { @@ -211,6 +238,7 @@ class RenderOverflowHandler extends RenderBox } } + /// {@macro overflowChangedCallback} OverflowHandlerChangedCallback? overflowChangedCallback; bool get _debugHasNecessaryDirections { diff --git a/lib/src/layout/toolbar/toolbar.dart b/lib/src/layout/toolbar/toolbar.dart index 8d98f0a6..01a3c12d 100644 --- a/lib/src/layout/toolbar/toolbar.dart +++ b/lib/src/layout/toolbar/toolbar.dart @@ -275,11 +275,14 @@ enum ToolbarItemDisplayMode { overflowed, } +/// {@template toolbarItem} /// An individual action displayed within a [Toolbar]. Sub-class this /// to build a new type of widget that appears inside of a toolbar. /// It knows how to build an appropriate widget for the given /// [ToolbarItemDisplayMode] during build time. +/// {@endtemplate} abstract class ToolbarItem with Diagnosticable { + /// {@macro toolbarItem} const ToolbarItem({required this.key}); final Key? key; diff --git a/lib/src/layout/toolbar/toolbar_popup.dart b/lib/src/layout/toolbar/toolbar_popup.dart index d4e9deab..9d618128 100644 --- a/lib/src/layout/toolbar/toolbar_popup.dart +++ b/lib/src/layout/toolbar/toolbar_popup.dart @@ -27,10 +27,14 @@ enum ToolbarPopupPlacement { end, } +/// {@template toolbarPopup} /// A popup widget for the toolbar. +/// +/// Used for the menu that encapsulates the overflowed toolbar actions and +/// its possible submenus. +/// {@endtemplate} class ToolbarPopup extends StatefulWidget { - /// Creates a popup for the toolbar. Used for the menu that encapsulates - /// the overflowed toolbar actions and its possible submenus. + /// {@macro toolbarPopup} const ToolbarPopup({ super.key, required this.child, @@ -41,11 +45,26 @@ class ToolbarPopup extends StatefulWidget { this.position = ToolbarPopupPosition.above, }); + /// The child widget to show in the popup final Widget child; + + /// The content of the popup final WidgetBuilder content; + + /// The vertical offset of the popup final double verticalOffset; + + /// The horizontal offset of the popup final double horizontalOffset; + + /// The placement of the popup. + /// + /// Defaults to [ToolbarPopupPlacement.center]. final ToolbarPopupPlacement placement; + + /// The position of the popup. + /// + /// Defaults to [ToolbarPopupPosition.above]. final ToolbarPopupPosition position; @override diff --git a/lib/src/selectors/caret_painters.dart b/lib/src/selectors/caret_painters.dart index fc9eddd7..b3259e13 100644 --- a/lib/src/selectors/caret_painters.dart +++ b/lib/src/selectors/caret_painters.dart @@ -2,13 +2,20 @@ import 'package:flutter/widgets.dart'; const _buttonRadius = 5.0; +/// {@template downCaretPainter} +/// A painter that draws a caret pointing down. +/// {@endtemplate} class DownCaretPainter extends CustomPainter { + /// {@macro downCaretPainter} const DownCaretPainter({ required this.color, required this.backgroundColor, }); + /// The color of the caret. final Color color; + + /// The background color of the caret. final Color backgroundColor; @override @@ -44,13 +51,20 @@ class DownCaretPainter extends CustomPainter { bool shouldRebuildSemantics(DownCaretPainter oldDelegate) => false; } +/// {@template upCaretPainter} +/// A painter that draws a caret pointing up. +/// {@endtemplate} class UpCaretPainter extends CustomPainter { + /// {@macro upCaretPainter} const UpCaretPainter({ required this.color, required this.backgroundColor, }); + /// The color of the caret. final Color color; + + /// The background color of the caret. final Color backgroundColor; @override diff --git a/lib/src/selectors/date_picker.dart b/lib/src/selectors/date_picker.dart index 74455245..32c1501d 100644 --- a/lib/src/selectors/date_picker.dart +++ b/lib/src/selectors/date_picker.dart @@ -8,8 +8,13 @@ import 'package:macos_ui/src/selectors/keyboard_shortcut_runner.dart'; /// Defines the possibles [MacosDatePicker] styles. enum DatePickerStyle { + /// A text-only date picker. textual, + + /// A graphical-only date picker. graphical, + + /// A text-and-graphical date picker. combined, } diff --git a/lib/src/sheets/macos_sheet.dart b/lib/src/sheets/macos_sheet.dart index 46112c85..9e19c691 100644 --- a/lib/src/sheets/macos_sheet.dart +++ b/lib/src/sheets/macos_sheet.dart @@ -6,9 +6,12 @@ const _kSheetBorderRadius = BorderRadius.all(Radius.circular(12.0)); const EdgeInsets _defaultInsetPadding = EdgeInsets.symmetric(horizontal: 140.0, vertical: 48.0); +/// {@template macosSheet} /// A modal dialog that’s attached to a particular window and prevents further /// interaction with the window until the sheet is dismissed. +/// {@endtemplate} class MacosSheet extends StatelessWidget { + /// {@macro macosSheet} const MacosSheet({ super.key, required this.child, diff --git a/lib/src/theme/date_picker_theme.dart b/lib/src/theme/date_picker_theme.dart index 59f73f28..e83f2917 100644 --- a/lib/src/theme/date_picker_theme.dart +++ b/lib/src/theme/date_picker_theme.dart @@ -46,7 +46,19 @@ class MacosDatePickerTheme extends InheritedTheme { data != oldWidget.data; } +/// {@template macosDatePickerThemeData} +/// A style that overrides the default appearance of +/// [MacosDatePicker]s when it's used with [MacosDatePickerTheme] or with the +/// overall [MacosTheme]'s [MacosThemeData.datePickerTheme]. +/// +/// See also: +/// +/// * [MacosDatePickerTheme], the theme which is configured with this class. +/// * [MacosThemeData.datePickerTheme], which can be used to override +/// the default style for [MacosDatePicker]s below the overall [MacosTheme]. +/// {@endtemplate} class MacosDatePickerThemeData with Diagnosticable { + /// {@macro macosDatePickerThemeData} MacosDatePickerThemeData({ this.backgroundColor, this.selectedElementColor, @@ -65,20 +77,49 @@ class MacosDatePickerThemeData with Diagnosticable { this.shadowColor, }); + /// The background color of the date picker. final Color? backgroundColor; + + /// The color of the selected element in the textual picker. final Color? selectedElementColor; + + /// The text color of the selected element in the textual picker. final Color? selectedElementTextColor; + + /// The color of the caret in the textual picker. final Color? caretColor; + + /// The color of the controls in the textual picker. final Color? caretControlsBackgroundColor; + + /// The color of the controls separator in the textual picker. final Color? caretControlsSeparatorColor; + + /// The color of the month view controls. final Color? monthViewControlsColor; + + /// The color of the month view header. final Color? monthViewHeaderColor; + + /// The color of the selected date in the month view. final Color? monthViewSelectedDateColor; + + /// The text color of the selected date in the month view. final Color? monthViewSelectedDateTextColor; + + /// The color of the current date in the month view. final Color? monthViewCurrentDateColor; + + /// The color of the weekday header in the month view. final Color? monthViewWeekdayHeaderColor; + + /// The color of the header divider in the month view. final Color? monthViewHeaderDividerColor; + + /// The color of the date in the month view. final Color? monthViewDateColor; + + /// The color of the shadow in the month view. final Color? shadowColor; /// Copies this [MacosDatePickerThemeData] into another. @@ -187,6 +228,7 @@ class MacosDatePickerThemeData with Diagnosticable { ); } + /// Merges this [MacosDatePickerThemeData] with another. MacosDatePickerThemeData merge(MacosDatePickerThemeData? other) { if (other == null) return this; return copyWith( diff --git a/lib/src/theme/help_button_theme.dart b/lib/src/theme/help_button_theme.dart index e11e2da8..6c3cfae6 100644 --- a/lib/src/theme/help_button_theme.dart +++ b/lib/src/theme/help_button_theme.dart @@ -113,6 +113,7 @@ class HelpButtonThemeData with Diagnosticable { properties.add(ColorProperty('disabledColor', disabledColor)); } + /// Merges this [HelpButtonThemeData] with another. HelpButtonThemeData merge(HelpButtonThemeData? other) { if (other == null) return this; return copyWith( diff --git a/lib/src/theme/icon_button_theme.dart b/lib/src/theme/icon_button_theme.dart index 19f439de..95e13c6c 100644 --- a/lib/src/theme/icon_button_theme.dart +++ b/lib/src/theme/icon_button_theme.dart @@ -129,6 +129,7 @@ class MacosIconButtonThemeData with Diagnosticable { ); } + /// Merges this [MacosIconButtonThemeData] with another. MacosIconButtonThemeData merge(MacosIconButtonThemeData? other) { if (other == null) return this; return copyWith( diff --git a/lib/src/theme/macos_colors.dart b/lib/src/theme/macos_colors.dart index 49daca37..59bad194 100644 --- a/lib/src/theme/macos_colors.dart +++ b/lib/src/theme/macos_colors.dart @@ -89,8 +89,13 @@ class MacosColor extends Color { /// A collection of color values lifted from the macOS system color picker. class MacosColors { + /// A fully transparent color. static const Color transparent = MacosColor(0x00000000); + + /// A fully opaque black color. static const black = MacosColor(0xff000000); + + /// A fully opaque white color. static const white = MacosColor(0xffffffff); /// The text of a label containing primary content. diff --git a/lib/src/theme/macos_dynamic_color.dart b/lib/src/theme/macos_dynamic_color.dart index 36399a0b..4a23b159 100644 --- a/lib/src/theme/macos_dynamic_color.dart +++ b/lib/src/theme/macos_dynamic_color.dart @@ -2,6 +2,7 @@ import 'package:flutter/cupertino.dart'; import 'macos_theme.dart'; +/// Extension methods on [CupertinoDynamicColor]. extension MacosDynamicColor on CupertinoDynamicColor { /// Resolves the given [Color] by calling [resolveFrom]. /// diff --git a/lib/src/theme/macos_theme.dart b/lib/src/theme/macos_theme.dart index 2560cc6b..013c716a 100644 --- a/lib/src/theme/macos_theme.dart +++ b/lib/src/theme/macos_theme.dart @@ -193,7 +193,7 @@ class MacosThemeData with Diagnosticable { PushButtonThemeData? pushButtonTheme, Color? dividerColor, HelpButtonThemeData? helpButtonTheme, - TooltipThemeData? tooltipTheme, + MacosTooltipThemeData? tooltipTheme, VisualDensity? visualDensity, MacosScrollbarThemeData? scrollbarTheme, MacosIconButtonThemeData? macosIconButtonTheme, @@ -234,7 +234,7 @@ class MacosThemeData with Diagnosticable { ? const Color.fromRGBO(255, 255, 255, 0.1) : const Color.fromRGBO(244, 245, 245, 1.0), ); - tooltipTheme ??= TooltipThemeData.standard( + tooltipTheme ??= MacosTooltipThemeData.standard( brightness: _brightness, textStyle: typography.callout, ); @@ -465,7 +465,7 @@ class MacosThemeData with Diagnosticable { final HelpButtonThemeData helpButtonTheme; /// The default style for [MacosTooltip]s below the overall [MacosTheme] - final TooltipThemeData tooltipTheme; + final MacosTooltipThemeData tooltipTheme; /// The density value for specifying the compactness of various UI components. /// @@ -484,12 +484,16 @@ class MacosThemeData with Diagnosticable { /// The default style for [MacosPopupButton]s below the overall [MacosTheme] final MacosPopupButtonThemeData popupButtonTheme; + /// The default style for [MacosPulldownButton]s below the overall [MacosTheme] final MacosPulldownButtonThemeData pulldownButtonTheme; + /// The default style for [MacosDatePicker]s below the overall [MacosTheme] final MacosDatePickerThemeData datePickerTheme; + /// The default style for [MacosTimePicker]s below the overall [MacosTheme] final MacosTimePickerThemeData timePickerTheme; + /// The default style for [MacosSearchField]s below the overall [MacosTheme] final MacosSearchFieldThemeData searchFieldTheme; /// Linearly interpolate between two themes. @@ -504,7 +508,7 @@ class MacosThemeData with Diagnosticable { HelpButtonThemeData.lerp(a.helpButtonTheme, b.helpButtonTheme, t), pushButtonTheme: PushButtonThemeData.lerp(a.pushButtonTheme, b.pushButtonTheme, t), - tooltipTheme: TooltipThemeData.lerp(a.tooltipTheme, b.tooltipTheme, t), + tooltipTheme: MacosTooltipThemeData.lerp(a.tooltipTheme, b.tooltipTheme, t), visualDensity: VisualDensity.lerp(a.visualDensity, b.visualDensity, t), scrollbarTheme: MacosScrollbarThemeData.lerp(a.scrollbarTheme, b.scrollbarTheme, t), @@ -551,7 +555,7 @@ class MacosThemeData with Diagnosticable { PushButtonThemeData? pushButtonTheme, Color? dividerColor, HelpButtonThemeData? helpButtonTheme, - TooltipThemeData? tooltipTheme, + MacosTooltipThemeData? tooltipTheme, VisualDensity? visualDensity, MacosScrollbarThemeData? scrollbarTheme, MacosIconButtonThemeData? iconButtonTheme, @@ -583,6 +587,7 @@ class MacosThemeData with Diagnosticable { ); } + /// Merges this [MacosThemeData] with another. MacosThemeData merge(MacosThemeData? other) { if (other == null) return this; return copyWith( @@ -624,7 +629,7 @@ class MacosThemeData with Diagnosticable { helpButtonTheme, )); properties.add( - DiagnosticsProperty('tooltipTheme', tooltipTheme), + DiagnosticsProperty('tooltipTheme', tooltipTheme), ); properties.add( DiagnosticsProperty( @@ -671,10 +676,12 @@ class MacosThemeData with Diagnosticable { } } +/// Brightness extensions extension BrightnessX on Brightness { /// Check if the brightness is dark or not. bool get isDark => this == Brightness.dark; + /// Resolves the given colors based on the current brightness. T resolve(T light, T dark) { if (isDark) return dark; return light; diff --git a/lib/src/theme/popup_button_theme.dart b/lib/src/theme/popup_button_theme.dart index 81540bd6..41c710d0 100644 --- a/lib/src/theme/popup_button_theme.dart +++ b/lib/src/theme/popup_button_theme.dart @@ -74,6 +74,7 @@ class MacosPopupButtonThemeData with Diagnosticable { /// The default popup menu color for [MacosPopupButton] final Color? popupColor; + /// Copies this [MacosPopupButtonThemeData] into another. MacosPopupButtonThemeData copyWith({ Color? highlightColor, Color? backgroundColor, @@ -121,6 +122,7 @@ class MacosPopupButtonThemeData with Diagnosticable { properties.add(ColorProperty('popupColor', popupColor)); } + /// Merges this [MacosPopupButtonThemeData] with another. MacosPopupButtonThemeData merge(MacosPopupButtonThemeData? other) { if (other == null) return this; return copyWith( diff --git a/lib/src/theme/pulldown_button_theme.dart b/lib/src/theme/pulldown_button_theme.dart index 44c5748a..49e82700 100644 --- a/lib/src/theme/pulldown_button_theme.dart +++ b/lib/src/theme/pulldown_button_theme.dart @@ -78,6 +78,7 @@ class MacosPulldownButtonThemeData with Diagnosticable { /// The default color for a [MacosPulldownButton]'s icon. final Color? iconColor; + /// Copies this [MacosPulldownButtonThemeData] into another. MacosPulldownButtonThemeData copyWith({ Color? highlightColor, Color? backgroundColor, @@ -130,6 +131,7 @@ class MacosPulldownButtonThemeData with Diagnosticable { properties.add(ColorProperty('iconColor', iconColor)); } + /// Merges this [MacosPulldownButtonThemeData] with another. MacosPulldownButtonThemeData merge(MacosPulldownButtonThemeData? other) { if (other == null) return this; return copyWith( diff --git a/lib/src/theme/push_button_theme.dart b/lib/src/theme/push_button_theme.dart index e7cea5ff..c7d27039 100644 --- a/lib/src/theme/push_button_theme.dart +++ b/lib/src/theme/push_button_theme.dart @@ -71,6 +71,7 @@ class PushButtonThemeData with Diagnosticable { /// The default secondary color (e.g. Cancel/Go back buttons) for [PushButton] final Color? secondaryColor; + /// Copies this [PushButtonThemeData] into another. PushButtonThemeData copyWith({ Color? color, Color? disabledColor, @@ -118,6 +119,7 @@ class PushButtonThemeData with Diagnosticable { properties.add(ColorProperty('secondaryColor', secondaryColor)); } + /// Merges this [PushButtonThemeData] with another. PushButtonThemeData merge(PushButtonThemeData? other) { if (other == null) return this; return copyWith( diff --git a/lib/src/theme/scrollbar_theme.dart b/lib/src/theme/scrollbar_theme.dart index fffc3b26..08329b69 100644 --- a/lib/src/theme/scrollbar_theme.dart +++ b/lib/src/theme/scrollbar_theme.dart @@ -366,6 +366,7 @@ class MacosScrollbarThemeData with Diagnosticable { )); } + /// Merges this [MacosScrollbarThemeData] with another. MacosScrollbarThemeData merge(MacosScrollbarThemeData? other) { if (other == null) return this; return copyWith( diff --git a/lib/src/theme/search_field_theme.dart b/lib/src/theme/search_field_theme.dart index 443215bb..0f49d9e0 100644 --- a/lib/src/theme/search_field_theme.dart +++ b/lib/src/theme/search_field_theme.dart @@ -125,6 +125,7 @@ class MacosSearchFieldThemeData with Diagnosticable { )); } + /// Merges this [MacosSearchFieldThemeData] with another. MacosSearchFieldThemeData merge(MacosSearchFieldThemeData? other) { if (other == null) return this; return copyWith( diff --git a/lib/src/theme/time_picker_theme.dart b/lib/src/theme/time_picker_theme.dart index 8cd99e73..47777a7d 100644 --- a/lib/src/theme/time_picker_theme.dart +++ b/lib/src/theme/time_picker_theme.dart @@ -46,7 +46,19 @@ class MacosTimePickerTheme extends InheritedTheme { data != oldWidget.data; } +/// {@template macosTimePickerThemeData} +/// A style that overrides the default appearance of +/// [MacosTimePicker]s when it's used with [MacosTimePickerTheme] or with the +/// overall [MacosTheme]'s [MacosThemeData.timePickerTheme]. +/// +/// See also: +/// +/// * [MacosTimePickerTheme], the theme which is configured with this class. +/// * [MacosThemeData.timePickerTheme], which can be used to override +/// the default style for [MacosTimePicker]s below the overall [MacosTheme]. +/// {@endtemplate} class MacosTimePickerThemeData with Diagnosticable { + /// {@macro macosTimePickerThemeData} MacosTimePickerThemeData({ this.backgroundColor, this.selectedElementColor, @@ -64,19 +76,47 @@ class MacosTimePickerThemeData with Diagnosticable { this.shadowColor, }); + /// The background color of the time picker. final Color? backgroundColor; + + /// The color of the selected element in the textual time picker. final Color? selectedElementColor; + + /// The text color of the selected element in the textual time picker. final Color? selectedElementTextColor; + + /// The color of the caret in the textual time picker controls. final Color? caretColor; + + /// The background color of the caret controls in the textual time picker. final Color? caretControlsBackgroundColor; + + /// The color of the separator between the caret controls in the textual + /// time picker. final Color? caretControlsSeparatorColor; + + /// The background color of the graphical time picker. final Color? clockViewBackgroundColor; + + /// The color of the hour hand in the graphical time picker. final Color? hourHandColor; + + /// The color of the minute hand in the graphical time picker. final Color? minuteHandColor; + + /// The color of the second hand in the graphical time picker. final Color? secondHandColor; + + /// The color of the hour text in the graphical time picker. final Color? hourTextColor; + + /// The color of the day period text in the graphical time picker. final Color? dayPeriodTextColor; + + /// The color of the clock's outer border in the graphical time picker. final Color? clockViewBorderColor; + + /// The color of the shadow in the graphical time picker. final Color? shadowColor; /// Copies this [MacosTimePickerThemeData] into another. @@ -175,6 +215,7 @@ class MacosTimePickerThemeData with Diagnosticable { ); } + /// Merges this [MacosTimePickerThemeData] with another. MacosTimePickerThemeData merge(MacosTimePickerThemeData? other) { if (other == null) return this; return copyWith( diff --git a/lib/src/theme/tooltip_theme.dart b/lib/src/theme/tooltip_theme.dart index 943114b3..db5f4d3e 100644 --- a/lib/src/theme/tooltip_theme.dart +++ b/lib/src/theme/tooltip_theme.dart @@ -7,20 +7,20 @@ import 'package:macos_ui/src/library.dart'; /// See also: /// /// * [MacosTooltipThemeData], which is used to configure this theme. -class TooltipTheme extends InheritedTheme { +class MacosTooltipTheme extends InheritedTheme { /// Builds a [MacosTooltipTheme]. /// /// The data argument must not be null. - const TooltipTheme({ + const MacosTooltipTheme({ super.key, required this.data, required super.child, }); /// The configuration for this theme - final TooltipThemeData data; + final MacosTooltipThemeData data; - /// Returns the [data] from the closest [TooltipTheme] ancestor. If there is + /// Returns the [data] from the closest [MacosTooltipTheme] ancestor. If there is /// no ancestor, it returns [MacosThemeData.tooltipTheme]. /// /// Typical usage is as follows: @@ -28,23 +28,35 @@ class TooltipTheme extends InheritedTheme { /// ```dart /// TooltipThemeData theme = TooltipTheme.of(context); /// ``` - static TooltipThemeData of(BuildContext context) { - final TooltipTheme? tooltipTheme = - context.dependOnInheritedWidgetOfExactType(); + static MacosTooltipThemeData of(BuildContext context) { + final MacosTooltipTheme? tooltipTheme = + context.dependOnInheritedWidgetOfExactType(); return tooltipTheme?.data ?? MacosTheme.of(context).tooltipTheme; } @override Widget wrap(BuildContext context, Widget child) { - return TooltipTheme(data: data, child: child); + return MacosTooltipTheme(data: data, child: child); } @override - bool updateShouldNotify(TooltipTheme oldWidget) => data != oldWidget.data; + bool updateShouldNotify(MacosTooltipTheme oldWidget) => data != oldWidget.data; } -class TooltipThemeData with Diagnosticable { - const TooltipThemeData({ +/// {@template macosTooltipThemeData} +/// A style that overrides the default appearance of +/// [MacosTooltip]s when it's used with [MacosTooltipTheme] or with the +/// overall [MacosTheme]'s [MacosThemeData.tooltipTheme]. +/// +/// See also: +/// +/// * [MacosTooltipTheme], the theme which is configured with this class. +/// * [MacosThemeData.tooltipTheme], which can be used to override +/// the default style for [MacosTooltip]s below the overall [MacosTheme]. +/// {@endtemplate} +class MacosTooltipThemeData with Diagnosticable { + /// {@macro macosTooltipThemeData} + const MacosTooltipThemeData({ this.height, this.verticalOffset, this.padding, @@ -59,11 +71,11 @@ class TooltipThemeData with Diagnosticable { /// Creates a default tooltip theme. /// /// [textStyle] is usually [MacosTypography.callout] - factory TooltipThemeData.standard({ + factory MacosTooltipThemeData.standard({ required Brightness brightness, required TextStyle textStyle, }) { - return TooltipThemeData( + return MacosTooltipThemeData( height: 20.0, verticalOffset: 18.0, preferBelow: true, @@ -170,8 +182,8 @@ class TooltipThemeData with Diagnosticable { /// If null, [MacosTypography.callout] is used final TextStyle? textStyle; - /// Copies this [TooltipThemeData] into another. - TooltipThemeData copyWith({ + /// Copies this [MacosTooltipThemeData] into another. + MacosTooltipThemeData copyWith({ Decoration? decoration, double? height, EdgeInsetsGeometry? margin, @@ -182,7 +194,7 @@ class TooltipThemeData with Diagnosticable { double? verticalOffset, Duration? waitDuration, }) { - return TooltipThemeData( + return MacosTooltipThemeData( decoration: decoration ?? this.decoration, height: height ?? this.height, margin: margin ?? this.margin, @@ -198,12 +210,12 @@ class TooltipThemeData with Diagnosticable { /// Linearly interpolate between two tooltip themes. /// /// All the properties must be non-null. - static TooltipThemeData lerp( - TooltipThemeData a, - TooltipThemeData b, + static MacosTooltipThemeData lerp( + MacosTooltipThemeData a, + MacosTooltipThemeData b, double t, ) { - return TooltipThemeData( + return MacosTooltipThemeData( decoration: Decoration.lerp(a.decoration, b.decoration, t), height: t < 0.5 ? a.height : b.height, margin: EdgeInsetsGeometry.lerp(a.margin, b.margin, t), @@ -216,7 +228,8 @@ class TooltipThemeData with Diagnosticable { ); } - TooltipThemeData merge(TooltipThemeData? other) { + /// Merges this [MacosTooltipThemeData] with another. + MacosTooltipThemeData merge(MacosTooltipThemeData? other) { if (other == null) return this; return copyWith( decoration: other.decoration, @@ -234,7 +247,7 @@ class TooltipThemeData with Diagnosticable { @override bool operator ==(Object other) => identical(this, other) || - other is TooltipThemeData && + other is MacosTooltipThemeData && runtimeType == other.runtimeType && height == other.height && verticalOffset == other.verticalOffset && diff --git a/test/theme/tooltip_theme_test.dart b/test/theme/tooltip_theme_test.dart index 7f506851..cb4bcb79 100644 --- a/test/theme/tooltip_theme_test.dart +++ b/test/theme/tooltip_theme_test.dart @@ -6,17 +6,17 @@ import 'package:macos_ui/src/library.dart'; void main() { test('==, hashCode, copyWith basics', () { expect( - const TooltipThemeData(), - const TooltipThemeData().copyWith(), + const MacosTooltipThemeData(), + const MacosTooltipThemeData().copyWith(), ); expect( - const TooltipThemeData().hashCode, - const TooltipThemeData().copyWith().hashCode, + const MacosTooltipThemeData().hashCode, + const MacosTooltipThemeData().copyWith().hashCode, ); }); test('lerps from light to dark', () { - final actual = TooltipThemeData.lerp( + final actual = MacosTooltipThemeData.lerp( _tooltipThemeData, _tooltipThemeDataDark, 1, @@ -26,7 +26,7 @@ void main() { }); test('lerps from dark to light', () { - final actual = TooltipThemeData.lerp( + final actual = MacosTooltipThemeData.lerp( _tooltipThemeDataDark, _tooltipThemeData, 1, @@ -37,7 +37,7 @@ void main() { testWidgets('debugFillProperties', (tester) async { final builder = DiagnosticPropertiesBuilder(); - const TooltipThemeData().debugFillProperties(builder); + const MacosTooltipThemeData().debugFillProperties(builder); final description = builder.properties .where((node) => !node.isFiltered(DiagnosticLevel.info)) @@ -60,7 +60,7 @@ void main() { }); } -const _tooltipThemeData = TooltipThemeData( +const _tooltipThemeData = MacosTooltipThemeData( decoration: BoxDecoration( color: Colors.red, ), @@ -71,7 +71,7 @@ const _tooltipThemeData = TooltipThemeData( ), ); -const _tooltipThemeDataDark = TooltipThemeData( +const _tooltipThemeDataDark = MacosTooltipThemeData( decoration: BoxDecoration( color: Colors.blue, ), From 18ac443967cce71f37fe8d42939edecb660a517a Mon Sep 17 00:00:00 2001 From: GroovinChip Date: Tue, 5 Jul 2022 18:30:47 -0400 Subject: [PATCH 08/17] docs: fix a documentation error --- lib/src/theme/macos_theme.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/theme/macos_theme.dart b/lib/src/theme/macos_theme.dart index 013c716a..92ca4850 100644 --- a/lib/src/theme/macos_theme.dart +++ b/lib/src/theme/macos_theme.dart @@ -13,7 +13,7 @@ import 'package:macos_ui/src/library.dart'; /// /// See also: /// -/// * [MacosThemeData], specifies the theme's visual styling +/// * [MacosThemeData], which specifies the theme's visual styling /// * [MacosApp], which will automatically add a [MacosTheme] based on the /// value of [MacosApp.theme]. class MacosTheme extends StatelessWidget { From 96a3e516096db8ca3bbef3b93cd1fcb3bfd2c2b8 Mon Sep 17 00:00:00 2001 From: GroovinChip Date: Tue, 5 Jul 2022 19:10:43 -0400 Subject: [PATCH 09/17] chore: run flutter format . --- lib/src/theme/macos_theme.dart | 3 ++- lib/src/theme/tooltip_theme.dart | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/src/theme/macos_theme.dart b/lib/src/theme/macos_theme.dart index 92ca4850..e2c084d8 100644 --- a/lib/src/theme/macos_theme.dart +++ b/lib/src/theme/macos_theme.dart @@ -508,7 +508,8 @@ class MacosThemeData with Diagnosticable { HelpButtonThemeData.lerp(a.helpButtonTheme, b.helpButtonTheme, t), pushButtonTheme: PushButtonThemeData.lerp(a.pushButtonTheme, b.pushButtonTheme, t), - tooltipTheme: MacosTooltipThemeData.lerp(a.tooltipTheme, b.tooltipTheme, t), + tooltipTheme: + MacosTooltipThemeData.lerp(a.tooltipTheme, b.tooltipTheme, t), visualDensity: VisualDensity.lerp(a.visualDensity, b.visualDensity, t), scrollbarTheme: MacosScrollbarThemeData.lerp(a.scrollbarTheme, b.scrollbarTheme, t), diff --git a/lib/src/theme/tooltip_theme.dart b/lib/src/theme/tooltip_theme.dart index db5f4d3e..b0958ea7 100644 --- a/lib/src/theme/tooltip_theme.dart +++ b/lib/src/theme/tooltip_theme.dart @@ -40,7 +40,8 @@ class MacosTooltipTheme extends InheritedTheme { } @override - bool updateShouldNotify(MacosTooltipTheme oldWidget) => data != oldWidget.data; + bool updateShouldNotify(MacosTooltipTheme oldWidget) => + data != oldWidget.data; } /// {@template macosTooltipThemeData} From 0114f0ff8db77f70d15308d26b481581f314d3ac Mon Sep 17 00:00:00 2001 From: GroovinChip Date: Thu, 7 Jul 2022 14:59:00 -0400 Subject: [PATCH 10/17] feat: MacosImageIcon & sidebar updates --- CHANGELOG.md | 12 ++ .../sf_symbols/button_programmable_2x.png | Bin 0 -> 1611 bytes .../sf_symbols/character_cursor_ibeam_2x.png | Bin 0 -> 969 bytes .../sf_symbols/filemenu_and_selection_2x.png | Bin 0 -> 847 bytes .../lines_measurement_horizontal_2x.png | Bin 0 -> 686 bytes .../sf_symbols/rectangle_3_group_2x.png | Bin 0 -> 1033 bytes example/lib/main.dart | 68 +++++++---- example/pubspec.lock | 6 +- example/pubspec.yaml | 8 +- lib/macos_ui.dart | 1 + lib/src/icon/image_icon.dart | 102 +++++++++++++++++ lib/src/layout/sidebar/sidebar_item.dart | 9 ++ lib/src/layout/sidebar/sidebar_items.dart | 107 ++++++++++++++++-- pubspec.yaml | 2 +- 14 files changed, 280 insertions(+), 35 deletions(-) create mode 100644 example/assets/sf_symbols/button_programmable_2x.png create mode 100644 example/assets/sf_symbols/character_cursor_ibeam_2x.png create mode 100644 example/assets/sf_symbols/filemenu_and_selection_2x.png create mode 100644 example/assets/sf_symbols/lines_measurement_horizontal_2x.png create mode 100644 example/assets/sf_symbols/rectangle_3_group_2x.png create mode 100644 lib/src/icon/image_icon.dart diff --git a/CHANGELOG.md b/CHANGELOG.md index b8f3bf4e..0e4d1e39 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,15 @@ +## [1.7.0] +* ✨ New + * `MacosImageIcon` widget. Identical to the `ImageIcon` from `flutter/widgets.dart` except it will obey a +`MacosIconThemeData` instead of an `IconThemeData` + * `SidebarItemSize` enum, which determines the height of sidebar items and the maximum size their `leading` widgets. + * `SidebarItem` now accepts an optional `trailing` widget. +* 🔄 Updated + * `SidebarItems` now supports `SidebarItemSize` via the `itemSize` property, which defaults to +`SidebarItemSize.medium`. The widget has been updated to manage the item's height, the maximum size of the item's +leading widget, and the font size of the item's label widget according to the given `SidebarItemSize`. + * The example app has been tweaked to use some icons from the SF Symbols 4 Beta via the new `MacosImageIcon` widget. + ## [1.6.0] * New widgets: `MacosTabView` and `MacosTabView` * BREAKING CHANGE: `Label.yAxis` has been renamed to `Label.crossAxisAlignment` diff --git a/example/assets/sf_symbols/button_programmable_2x.png b/example/assets/sf_symbols/button_programmable_2x.png new file mode 100644 index 0000000000000000000000000000000000000000..1a4ee84d7ced8e7df32c5ce4dc0d10bf9a51d913 GIT binary patch literal 1611 zcmV-R2DJH!P)Px#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91D4+uX1ONa40RR91C;$Ke0D9(TtN;K7wMj%lR9FeUn0ts+bri>EHZ9Yw z$P&xSToEfp&1|c!{Kq1&BCV7%Rged`Pw2&9IH&@r zfZm`3{0V*mJHb}4DVOQ+?C1ybBd+#d%+I>Ta4ooE8hU=KJ5jscb63@{W72H9u> z%7-ZQ$81K^onZpCpX=wSiT_&MVnU_BG4sGLNOei*L;lfbYH$2R0yaJbvj~r%U0dcrD`ya27ZL z90vP=PVkq+=rqSfj8@|=FdbCHhE(bid{;U-u-ytIK^+z?tI=+lI)cA91wJ|`Q-MzN zX{OIHHlkkz){)~M^C8Ej@Xvxikxtjsz4&|*$zkYHAVC+ReFQaLRkxvJ3Ij~#@;IGFgJ~(lKI3I^mM_)@N(q%gt=g>LqTE0b~29!ce z30_EEbxc3{a)uXx)$#?lX~=>69?-t9;$)oTiaQZ`1t_&R<<&g%DJ&Gk9O~#|86v8| zc8v+4KolpTxXqM|pAr}@Qfyl8=fFA6UMj1-d%sJv1U{@(q?^vXp*HKFj=A5g3tVtL z{6$VK7h6veVMS<$4$qS=aTWZPlC8iSol(!q7Re+Sz@P$?qPfuurI>R)lWcbrg_J*IxzN^&%ht>ZH5zXpx`G*U(1=s>Md(@e>JNZ}y}{)sc_!!+rTkS=ME=F$d35n>x;d0_+(WK6T|PheMw+y*f)9-~nK?2uA;3DL1X5xDS;y+V?Zu1nTU2lC{Lz zZ(8D2;vo-PyVTJEn z?0u0JgHm52){*03D}cPikY5G%-nk8a5_WC9E{{1Cc?CKZVYCCL7U%s^VDG_j54am> zD-=Wfa9%*Jc)ycxAi9;nnxbu{FUcmxKye36LC`Gt+f8O{mP}CSr1baihrz literal 0 HcmV?d00001 diff --git a/example/assets/sf_symbols/character_cursor_ibeam_2x.png b/example/assets/sf_symbols/character_cursor_ibeam_2x.png new file mode 100644 index 0000000000000000000000000000000000000000..f52311f8fad39ab4756e7299111d596ed9d15cc4 GIT binary patch literal 969 zcmV;)12+7LP)Px#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91C!hlW1ONa40RR91DF6Tf0D%O2$p8QYGf6~2R9Fe^n8|AtQ4q%MxPW-j z5O*;OCI~Kwkb@pHaU&iQaXCg0g5cf1z>|0tyb5~Iqr@mK5k@o+MbVQ85e;~92`-2` z>iDa%pM1XQ>F$~C9t`-P=2g|JSM^oB?wU6fOeuJj%jIc=I0khuW-LVtaA56ZY~Sp0cb3w^-mc$;0^K(=tHQt7h_k*=RVa0M@1GBZG%L-U_1c7 zGF=h9CLrqUY)x>u)}SAeOA=0xb|s0dlC|8}IB>fq$~7wXMP$1%=q+NOtmVc=f};|C zZ>t=siwe*({6!}-_pxV&$hokQ;P9+1;wAFkh-eCU4(mM;Hknja;HZG#+lHvrPd^H} zC+HrDUK0{^<7^c;TxoAJ&-(EDk#ub{iOuy5vdB^3^er8uLXTqtTw={tHTRzP7dSHa zd)r_PFza~}tdELKu19M>+jMBvOy36JgK7}LTT^zU6Za*$acmy-de6n=o@Q#3GE>9wYBWi=nY)I z7`}qPBXYg09@|jpK6uxfOz@3C%K)0b&P}Xr2 z2jkFF=nORZ2G8UF|K|!jHurT?SO9a(5pHB!;RUgah8w-u*k5aI*h~2!a8gk-LFeOp rqyhgE&8XZvfR&(5C~Yz9f0@!R;H%&yB*sKI00000NkvXXu0mjfsT!x! literal 0 HcmV?d00001 diff --git a/example/assets/sf_symbols/filemenu_and_selection_2x.png b/example/assets/sf_symbols/filemenu_and_selection_2x.png new file mode 100644 index 0000000000000000000000000000000000000000..7321b4bd93ddd47efe337d116959113ec7ea9a4d GIT binary patch literal 847 zcmeAS@N?(olHy`uVBq!ia0vp^dO)ng!3HG1DRBe>DaPU;cPEB*=VV?2IWDOYo@u_m z3|c@o2Loe!CIbsd2@p#GF#`kh0!9XAAk7F8TfhXD)my*}XRCk|N+{VZWnf_1>FMGa z62WN0H;POr6uTvU=1wSOcS(&2CKeSlPfcE?krlP!9Wd?epiWvc|iuAKxoF{`QXdzM1Cv_h2(77J z;;(owF#9z$D=?*cwa%_*w0^)lqi<10^EZ7%k9fy=mRl)25p@UODn41R>`{KXk}Zl^ zEhqT04o}w`--O9^UjJDu*qtUMPk6z8z0oUNLi}M|LP}rej&!TWEw4XBEHSdX+;@DA z-nIo9ic*z%Qy8LW-wqVK{w^`)wg%(2XfyTQ+fu_HrnhA7>iX#O%*pVu`JLyQM_C$~ zoKyo{5%cos2g9W9FaWA z^G5mKL5m}6|J3Z$yuPcvJXZOam#hA=bAKK$@NJY&Q;D*TN#ZCyT2_3DTbo-=FOj_o059tXa@buYx|YsJJ0Cr-T?8?!TuJCC>M zUo8^WE@Rkhc;w^-smT53dB5(9=zj6SApA5#S&pgXbw1-{_SOa2g>_pyWVU~~5c2!* z)%oVqwTDcLZUQ49O5Uw3RLlE8Nsi>gc}rg{UjEnNaZSE(lCo!Dtu6_t95R#zQVRg%n{xC6N3aCU9ClRLUgVk z6Zt0)w8V&OksI&p^2cjz{_XYc5*dGQ7Ly-v8fz`=#gB81IOWGT~bD z^-syKfaoXFELMMvI>g--7n0a{^OdCT&ujYPa(^=87@l9@Y4HBBoa2hsit~%7FRJ57 zd1x7}n|)cSqu^NB4K@C~?w`)(6n&Mk-`*Z~{Za(b-Zdw#w!fXPx#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91H=qLm1ONa40RR91Bme*a0B*%01poj8b4f%&R9Fe^nLTI}K@`V56E#sm zBck{b4QL^LM2H9mBvmXdv``eQfQT40tq6ix+NF>*K|zV6F=DUr3qdVJQ9+1;T1hNK z(LnqN=KTG2=9uI5yv*+Gx)C4z@4cCQ^WOYtc6Mg>3Nw&mu{aae0k(jJpa7D}Bmq8x zhu~hJP$(u1=-a*kv3WE)588Yid-y-V_Xq^Pd9+FFsu?>5(j=x%wu8emvJ}nS4H7n+ zq>s>7!533bqUO?gKWH^UUNCdJs|BPVO*G6MJh^VEnW+G^j06*JIS__CFv}me_bmV(Jj_-qLZIl0VJF9wapT{n2TC49+mDq&Ut(%V zoA0O#z|MG6UdXxo>bN=^M<6I-rR^p!RS%Mt2It)PZH-ECj3Nl}|8< zoH?TJax%Qcz6QQJbv~wutOq+yqt)n*;F&3>rYguWs(Nu;>0~@;wh5=o-z2WqZM$jN zj82_g)a@a{wtg%GF+<2bTj~Q&>S9S$OQa{=w3S-qrY$jbJTK&QxVEy~8ot`BYkldK zOjJBje%ELIJL+USK_?ec_WknRK%2czoOe;=CLM=H64mig3Gq5K--+wyQ1vj-{|XY# z-05FdvY+7!av}mD{h^>&AAJJvp?nD)_cMXEmQA1&SSfo2BIjLl3~;f_IwaIjXy504 z3@ybYQ6GiSM=u*+XUpvn(g2nTd0VOI082r|z?V5!7Ib$h=3{V2lT?k|S{yC|{reJH zdP&?LI%rh9NOE#y6jz}Rfe>#6I=a0ALlD#dftkopOq9)Gy2j_*00000NkvXXu0mjf DQ5?R} literal 0 HcmV?d00001 diff --git a/example/lib/main.dart b/example/lib/main.dart index 83964684..5fd0a221 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -176,47 +176,77 @@ class _WidgetGalleryState extends State { currentIndex: pageIndex, onChanged: (i) => setState(() => pageIndex = i), scrollController: controller, - items: const [ - SidebarItem( - leading: MacosIcon(CupertinoIcons.square_on_circle), + itemSize: SidebarItemSize.large, + items: [ + const SidebarItem( + // leading: MacosIcon(CupertinoIcons.square_on_circle), + leading: MacosImageIcon( + AssetImage( + 'assets/sf_symbols/button_programmable_2x.png', + ), + ), label: Text('Buttons'), ), - SidebarItem( - leading: MacosIcon(CupertinoIcons.arrow_2_circlepath), + const SidebarItem( + leading: MacosImageIcon( + AssetImage( + 'assets/sf_symbols/lines_measurement_horizontal_2x.png', + ), + ), label: Text('Indicators'), ), - SidebarItem( - leading: MacosIcon(CupertinoIcons.textbox), + const SidebarItem( + leading: MacosImageIcon( + AssetImage( + 'assets/sf_symbols/character_cursor_ibeam_2x.png', + ), + ), label: Text('Fields'), ), SidebarItem( - leading: Icon(CupertinoIcons.folder), - label: Text('Disclosure'), + leading: const MacosIcon(CupertinoIcons.folder), + label: const Text('Disclosure'), + trailing: Text( + '2', + style: TextStyle( + color: MacosTheme.brightnessOf(context) == Brightness.dark + ? MacosColors.tertiaryLabelColor.darkColor + : MacosColors.tertiaryLabelColor, + ), + ), disclosureItems: [ - SidebarItem( - leading: MacosIcon(CupertinoIcons.infinite), + const SidebarItem( + leading: MacosImageIcon( + AssetImage( + 'assets/sf_symbols/rectangle_3_group_2x.png', + ), + ), label: Text('Colors'), ), - SidebarItem( + const SidebarItem( leading: MacosIcon(CupertinoIcons.infinite), label: Text('Item 3'), ), ], ), - SidebarItem( - leading: MacosIcon(CupertinoIcons.rectangle), + const SidebarItem( + leading: MacosIcon(CupertinoIcons.square_on_square), label: Text('Dialogs & Sheets'), ), - SidebarItem( + const SidebarItem( leading: MacosIcon(CupertinoIcons.macwindow), label: Text('Toolbar'), ), - SidebarItem( - leading: MacosIcon(CupertinoIcons.calendar), + const SidebarItem( + leading: MacosImageIcon( + AssetImage( + 'assets/sf_symbols/filemenu_and_selection_2x.png', + ), + ), label: Text('Selectors'), ), - SidebarItem( - leading: MacosIcon(CupertinoIcons.table_fill), + const SidebarItem( + leading: MacosIcon(CupertinoIcons.uiwindow_split_2x1), label: Text('TabView'), ), ], diff --git a/example/pubspec.lock b/example/pubspec.lock index 779cfa22..a3e1e90b 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -49,7 +49,7 @@ packages: name: cupertino_icons url: "https://pub.dartlang.org" source: hosted - version: "1.0.4" + version: "1.0.5" fake_async: dependency: transitive description: @@ -87,7 +87,7 @@ packages: path: ".." relative: true source: path - version: "1.6.0" + version: "1.7.0" matcher: dependency: transitive description: @@ -129,7 +129,7 @@ packages: name: provider url: "https://pub.dartlang.org" source: hosted - version: "6.0.2" + version: "6.0.3" sky_engine: dependency: transitive description: flutter diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 6b4d9e84..e68c5535 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -10,12 +10,16 @@ dependencies: flutter: sdk: flutter - cupertino_icons: ^1.0.4 + cupertino_icons: ^1.0.5 macos_ui: path: .. - provider: ^6.0.2 + provider: ^6.0.3 dev_dependencies: flutter_test: sdk: flutter flutter_lints: ^2.0.1 + +flutter: + assets: + - assets/sf_symbols/ diff --git a/lib/macos_ui.dart b/lib/macos_ui.dart index 08a2ba91..b783a572 100644 --- a/lib/macos_ui.dart +++ b/lib/macos_ui.dart @@ -30,6 +30,7 @@ export 'src/buttons/toolbar/toolbar_pulldown_button.dart'; export 'src/dialogs/macos_alert_dialog.dart'; export 'src/fields/search_field.dart'; export 'src/fields/text_field.dart'; +export 'src/icon/image_icon.dart'; export 'src/icon/macos_icon.dart'; export 'src/indicators/capacity_indicators.dart'; export 'src/indicators/progress_indicators.dart'; diff --git a/lib/src/icon/image_icon.dart b/lib/src/icon/image_icon.dart new file mode 100644 index 00000000..55986dd1 --- /dev/null +++ b/lib/src/icon/image_icon.dart @@ -0,0 +1,102 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/foundation.dart'; +import 'package:macos_ui/macos_ui.dart'; + +/// {@template macosImageIcon} +/// An icon that comes from an [ImageProvider], e.g. an [AssetImage]. +/// +/// The [size] and [color] default to the value given by the current [MacosIconTheme]. +/// +/// See also: +/// * [MacosIconButton], for interactive icons. +/// * [MacosIconTheme], which provides ambient configuration for icons. +/// * [MacosIcon], for icons based on glyphs from fonts instead of images. +/// {@endtemplate} +class MacosImageIcon extends StatelessWidget { + /// {@macro macosImageIcon} + const MacosImageIcon( + this.image, { + super.key, + this.size, + this.color, + this.semanticLabel, + }); + + /// The image to display as the icon. + /// + /// The icon can be null, in which case the widget will render as an empty + /// space of the specified [size]. + final ImageProvider? image; + + /// The size of the icon in logical pixels. + /// + /// Icons occupy a square with width and height equal to size. + /// + /// Defaults to the current [MacosIconTheme] size, if any. If there is no + /// [MacosIconTheme], or it does not specify an explicit size, then it + /// defaults to 24.0. + final double? size; + + /// The color to use when drawing the icon. + /// + /// Defaults to the current [MacosIconTheme] color, if any. If there is + /// no [MacosIconTheme], then it defaults to not recolorizing the image. + /// + /// The image will be additionally adjusted by the opacity of the current + /// [MacosIconTheme], if any. + final Color? color; + + /// Semantic label for the icon. + /// + /// Announced in accessibility modes (e.g TalkBack/VoiceOver). + /// This label does not show in the UI. + /// + /// * [SemanticsProperties.label], which is set to [semanticLabel] in the + /// underlying [Semantics] widget. + final String? semanticLabel; + + @override + Widget build(BuildContext context) { + final iconTheme = MacosIconTheme.of(context); + final iconSize = size ?? iconTheme.size; + + if (image == null) { + return Semantics( + label: semanticLabel, + child: SizedBox(width: iconSize, height: iconSize), + ); + } + + final iconOpacity = iconTheme.opacity; + Color iconColor = color ?? iconTheme.color!; + + if (iconOpacity != null && iconOpacity != 1.0) { + iconColor = iconColor.withOpacity(iconColor.opacity * iconOpacity); + } + + return Semantics( + label: semanticLabel, + child: Image( + image: image!, + width: iconSize, + height: iconSize, + color: iconColor, + fit: BoxFit.scaleDown, + excludeFromSemantics: true, + ), + ); + } + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties.add(DiagnosticsProperty( + 'image', + image, + ifNull: '', + showName: false, + )); + properties.add(DoubleProperty('size', size, defaultValue: null)); + properties.add(ColorProperty('color', color, defaultValue: null)); + } +} diff --git a/lib/src/layout/sidebar/sidebar_item.dart b/lib/src/layout/sidebar/sidebar_item.dart index 8a98a930..b7e2d487 100644 --- a/lib/src/layout/sidebar/sidebar_item.dart +++ b/lib/src/layout/sidebar/sidebar_item.dart @@ -19,6 +19,7 @@ class SidebarItem with Diagnosticable { this.focusNode, this.semanticLabel, this.disclosureItems, + this.trailing, }); /// The widget before [label]. @@ -57,6 +58,13 @@ class SidebarItem with Diagnosticable { /// If non-null and [leading] is null, a local animated icon is created final List? disclosureItems; + /// An optional trailing widget. + /// + /// Typically a text indicator of a count of items, like in this + /// screenshots from the Apple Notes app: + /// {@image } + final Widget? trailing; + @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); @@ -69,5 +77,6 @@ class SidebarItem with Diagnosticable { 'disclosure items', disclosureItems, )); + properties.add(DiagnosticsProperty('trailing', trailing)); } } diff --git a/lib/src/layout/sidebar/sidebar_items.dart b/lib/src/layout/sidebar/sidebar_items.dart index e05ddb07..1f00eeba 100644 --- a/lib/src/layout/sidebar/sidebar_items.dart +++ b/lib/src/layout/sidebar/sidebar_items.dart @@ -3,9 +3,39 @@ import 'package:macos_ui/src/library.dart'; const Duration _kExpand = Duration(milliseconds: 200); const ShapeBorder _defaultShape = RoundedRectangleBorder( - borderRadius: BorderRadius.all(Radius.circular(7.0)), + //TODO: consider changing to 4.0 or 5.0 - App Store, Notes and Mail seem to use 4.0 or 5.0 + borderRadius: BorderRadius.all(Radius.circular(5.0)), ); +/// {@template sidebarItemSize} +/// Enumerates the size specifications of [SidebarItem]s +/// +/// Values were adapted from https://developer.apple.com/design/human-interface-guidelines/components/navigation-and-search/sidebars/#platform-considerations +/// and were eyeballed against apps like App Store, Notes, and Mail. +/// {@endtemplate} +enum SidebarItemSize { + /// A small [SidebarItem]. Has a [height] of 24 and an [iconSize] of 12. + small(24.0, 12.0), + + /// A medium [SidebarItem]. Has a [height] of 28 and an [iconSize] of 16. + medium(29.0, 16.0), + + /// A large [SidebarItem]. Has a [height] of 32 and an [iconSize] of 20.0. + large(36.0, 18.0); + + /// {@macro sidebarItemSize} + const SidebarItemSize( + this.height, + this.iconSize, + ); + + /// The height of the [SidebarItem]. + final double height; + + /// The maximum size of the [SidebarItem]'s leading icon. + final double iconSize; +} + /// A scrollable widget that renders [SidebarItem]s. /// /// See also: @@ -16,9 +46,10 @@ class SidebarItems extends StatelessWidget { /// Creates a scrollable widget that renders [SidebarItem]s. const SidebarItems({ super.key, + required this.items, required this.currentIndex, required this.onChanged, - required this.items, + this.itemSize = SidebarItemSize.medium, this.scrollController, this.selectedColor, this.unselectedColor, @@ -37,6 +68,11 @@ class SidebarItems extends StatelessWidget { /// Called when the current selected index should be changed. final ValueChanged onChanged; + /// The size specifications for all [items]. + /// + /// Defaults to [SidebarItemSize.medium]. + final SidebarItemSize itemSize; + /// The scroll controller used by this sidebar. If null, a local scroll /// controller is created. final ScrollController? scrollController; @@ -85,6 +121,7 @@ class SidebarItems extends StatelessWidget { selectedColor: selectedColor ?? theme.primaryColor, unselectedColor: unselectedColor ?? MacosColors.transparent, shape: shape ?? _defaultShape, + itemSize: itemSize, child: ListView( controller: scrollController, physics: const ClampingScrollPhysics(), @@ -126,11 +163,13 @@ class _SidebarItemsConfiguration extends InheritedWidget { this.selectedColor = MacosColors.transparent, this.unselectedColor = MacosColors.transparent, this.shape = _defaultShape, + this.itemSize = SidebarItemSize.medium, }) : super(key: key); final Color selectedColor; final Color unselectedColor; final ShapeBorder shape; + final SidebarItemSize itemSize; static _SidebarItemsConfiguration of(BuildContext context) { return context @@ -181,6 +220,7 @@ class _SidebarItem extends StatelessWidget { }; bool get hasLeading => item.leading != null; + bool get hasTrailing => item.trailing != null; @override Widget build(BuildContext context) { @@ -199,6 +239,19 @@ class _SidebarItem extends StatelessWidget { ); final double spacing = 10.0 + theme.visualDensity.horizontal; + final itemSize = _SidebarItemsConfiguration.of(context).itemSize; + TextStyle? labelStyle; + switch (itemSize) { + case SidebarItemSize.small: + labelStyle = theme.typography.subheadline; + break; + case SidebarItemSize.medium: + labelStyle = theme.typography.body; + break; + case SidebarItemSize.large: + labelStyle = theme.typography.title3; + break; + } return Semantics( label: item.semanticLabel, @@ -217,7 +270,7 @@ class _SidebarItem extends StatelessWidget { actions: _actionMap, child: Container( width: 134.0 + theme.visualDensity.horizontal, - height: 38.0 + theme.visualDensity.vertical, + height: itemSize.height + theme.visualDensity.vertical, decoration: ShapeDecoration( color: selected ? selectedColor : unselectedColor, shape: item.shape ?? _SidebarItemsConfiguration.of(context).shape, @@ -236,16 +289,26 @@ class _SidebarItem extends StatelessWidget { color: selected ? MacosColors.white : MacosColors.controlAccentColor, + size: itemSize.iconSize, ), child: item.leading!, ), ), DefaultTextStyle( - style: theme.typography.title3.copyWith( + style: labelStyle.copyWith( color: selected ? textLuminance(selectedColor) : null, ), child: item.label, ), + if (hasTrailing) ...[ + const Spacer(), + DefaultTextStyle( + style: labelStyle.copyWith( + color: selected ? textLuminance(selectedColor) : null, + ), + child: item.trailing!, + ), + ], ], ), ), @@ -291,6 +354,8 @@ class __DisclosureSidebarItemState extends State<_DisclosureSidebarItem> bool _isExpanded = false; + bool get hasLeading => widget.item.leading != null; + @override void initState() { super.initState(); @@ -327,6 +392,20 @@ class __DisclosureSidebarItemState extends State<_DisclosureSidebarItem> final theme = MacosTheme.of(context); final double spacing = 10.0 + theme.visualDensity.horizontal; + final itemSize = _SidebarItemsConfiguration.of(context).itemSize; + TextStyle? labelStyle; + switch (itemSize) { + case SidebarItemSize.small: + labelStyle = theme.typography.subheadline; + break; + case SidebarItemSize.medium: + labelStyle = theme.typography.body; + break; + case SidebarItemSize.large: + labelStyle = theme.typography.title3; + break; + } + return Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, @@ -348,10 +427,14 @@ class __DisclosureSidebarItemState extends State<_DisclosureSidebarItem> : MacosColors.white, ), ), - if (widget.item.leading != null) + if (hasLeading) Padding( padding: EdgeInsets.only(left: spacing), - child: widget.item.leading!, + //child: widget.item.leading!, + child: MacosIconTheme.merge( + data: MacosIconThemeData(size: itemSize.iconSize), + child: widget.item.leading!, + ), ), ], ), @@ -359,16 +442,20 @@ class __DisclosureSidebarItemState extends State<_DisclosureSidebarItem> focusNode: widget.item.focusNode, semanticLabel: widget.item.semanticLabel, shape: widget.item.shape, + trailing: widget.item.trailing, ), onClick: _handleTap, selected: false, ), ), ClipRect( - child: Align( - alignment: Alignment.centerLeft, - heightFactor: _heightFactor.value, - child: child, + child: DefaultTextStyle( + style: labelStyle, + child: Align( + alignment: Alignment.centerLeft, + heightFactor: _heightFactor.value, + child: child, + ), ), ), ], diff --git a/pubspec.yaml b/pubspec.yaml index a46af1e1..d53c1721 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: macos_ui description: Flutter widgets and themes implementing the current macOS design language. -version: 1.6.0 +version: 1.7.0 homepage: "https://macosui.dev" repository: "https://github.com/GroovinChip/macos_ui" From f00c4cf9de779d19689fb6da8165cb7c9142a4d0 Mon Sep 17 00:00:00 2001 From: GroovinChip Date: Thu, 7 Jul 2022 15:21:04 -0400 Subject: [PATCH 11/17] test: fix issues with date_picker_test * Account for cases where the month and the day are the same * Fix offstage warnings by removing tester taps that disabled the caret controls --- test/selectors/date_picker_test.dart | 21 +++++++-------------- 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/test/selectors/date_picker_test.dart b/test/selectors/date_picker_test.dart index fedf0fac..98086010 100644 --- a/test/selectors/date_picker_test.dart +++ b/test/selectors/date_picker_test.dart @@ -31,9 +31,14 @@ void main() { ); expect(find.text('/'), findsNWidgets(2)); - expect(find.text('${today.day}'), findsOneWidget); - expect(find.text('${today.month}'), findsOneWidget); expect(find.text('${today.year}'), findsOneWidget); + if (today.month == today.day) { + expect(find.text('${today.day}'), findsNWidgets(2)); + expect(find.text('${today.month}'), findsNWidgets(2)); + } else { + expect(find.text('${today.day}'), findsOneWidget); + expect(find.text('${today.month}'), findsOneWidget); + } }, ); @@ -71,14 +76,10 @@ void main() { await tester.pumpAndSettle(); day++; expect(day, today.day + 1); - await tester.tap(dayFieldElement); - await tester.pumpAndSettle(); await tester.tap(downCaretControl); await tester.pumpAndSettle(); day--; expect(day, today.day); - await tester.tap(dayFieldElement); - await tester.pumpAndSettle(); await tester.tap(downCaretControl); await tester.pumpAndSettle(); day--; @@ -120,14 +121,10 @@ void main() { await tester.pumpAndSettle(); month++; expect(month, today.month + 1); - await tester.tap(monthFieldElement); - await tester.pumpAndSettle(); await tester.tap(downCaretControl); await tester.pumpAndSettle(); month--; expect(month, today.month); - await tester.tap(monthFieldElement); - await tester.pumpAndSettle(); await tester.tap(downCaretControl); await tester.pumpAndSettle(); month--; @@ -169,14 +166,10 @@ void main() { await tester.pumpAndSettle(); year++; expect(year, today.year + 1); - await tester.tap(yearFieldElement); - await tester.pumpAndSettle(); await tester.tap(downCaretControl); await tester.pumpAndSettle(); year--; expect(year, today.year); - await tester.tap(yearFieldElement); - await tester.pumpAndSettle(); await tester.tap(downCaretControl); await tester.pumpAndSettle(); year--; From e1056ec5d072f1bd426d154955e171e76d82385b Mon Sep 17 00:00:00 2001 From: Minas Giannekas Date: Sat, 9 Jul 2022 22:12:29 +0300 Subject: [PATCH 12/17] refactor: make active property of MacosTab optional, since it is handled via MacosSegmentedControl --- example/lib/pages/tabview_page.dart | 7 ++----- lib/src/layout/tab_view/tab.dart | 6 +++--- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/example/lib/pages/tabview_page.dart b/example/lib/pages/tabview_page.dart index 86c61bfe..00555d4c 100644 --- a/example/lib/pages/tabview_page.dart +++ b/example/lib/pages/tabview_page.dart @@ -1,4 +1,4 @@ -import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; import 'package:macos_ui/macos_ui.dart'; class TabViewPage extends StatefulWidget { @@ -27,18 +27,15 @@ class _TabViewPageState extends State { padding: const EdgeInsets.all(24.0), child: MacosTabView( controller: _controller, - tabs: [ + tabs: const [ MacosTab( label: 'Tab 1', - active: _controller.index == 0, ), MacosTab( label: 'Tab 2', - active: _controller.index == 1, ), MacosTab( label: 'Tab 3', - active: _controller.index == 2, ), ], children: const [ diff --git a/lib/src/layout/tab_view/tab.dart b/lib/src/layout/tab_view/tab.dart index c51122e0..0ad7ce2c 100644 --- a/lib/src/layout/tab_view/tab.dart +++ b/lib/src/layout/tab_view/tab.dart @@ -15,19 +15,19 @@ class MacosTab extends StatelessWidget { const MacosTab({ super.key, required this.label, - required this.active, + this.active = false, }); /// The display label for this tab. final String label; - /// Whether this [MacosTab] is currently selected. + /// Whether this [MacosTab] is currently selected. Handled internally by + /// [MacosSegmentedControl]'s build function. final bool active; @override Widget build(BuildContext context) { final brightness = MacosTheme.brightnessOf(context); - return PhysicalModel( color: active ? const Color(0xFF625E66) : MacosColors.transparent, borderRadius: _kTabBorderRadius, From af54b8feaa9a0c823df513615e7c2ba6b704d658 Mon Sep 17 00:00:00 2001 From: Minas Giannekas Date: Sat, 9 Jul 2022 22:22:09 +0300 Subject: [PATCH 13/17] chore: fix import change --- example/lib/pages/tabview_page.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/example/lib/pages/tabview_page.dart b/example/lib/pages/tabview_page.dart index 00555d4c..786b11b0 100644 --- a/example/lib/pages/tabview_page.dart +++ b/example/lib/pages/tabview_page.dart @@ -1,4 +1,4 @@ -import 'package:flutter/material.dart'; +import 'package:flutter/cupertino.dart'; import 'package:macos_ui/macos_ui.dart'; class TabViewPage extends StatefulWidget { From 79691069c11a21a5fb8298cebe1ac46759025fc1 Mon Sep 17 00:00:00 2001 From: Minas Giannekas Date: Sat, 9 Jul 2022 22:53:26 +0300 Subject: [PATCH 14/17] refactor: change colors to match default native app design --- lib/src/buttons/segmented_control.dart | 23 +++++++++++++---------- lib/src/layout/tab_view/tab.dart | 6 +++--- lib/src/layout/tab_view/tab_view.dart | 8 ++++---- 3 files changed, 20 insertions(+), 17 deletions(-) diff --git a/lib/src/buttons/segmented_control.dart b/lib/src/buttons/segmented_control.dart index 821798dc..ad33be2d 100644 --- a/lib/src/buttons/segmented_control.dart +++ b/lib/src/buttons/segmented_control.dart @@ -45,22 +45,25 @@ class _MacosSegmentedControlState extends State { decoration: BoxDecoration( // Background color color: brightness.resolve( - const Color(0xFFE3DEE8), - const Color(0xFF2D2934), + const Color(0xFFE2E3E6), + const Color(0xFF2B2E33), ), + boxShadow: [ + BoxShadow( + color: brightness.resolve( + const Color(0xFFDBDCDE), + const Color(0xFF4F5155), + ), + offset: const Offset(0, .5), + spreadRadius: .5, + ), + ], borderRadius: const BorderRadius.all( Radius.circular(5.0), ), - // Outer border - border: Border.all( - color: brightness.resolve( - const Color(0xFFD8D3DC), - const Color(0xFF37333D), - ), - ), ), child: Padding( - padding: const EdgeInsets.all(1.0), + padding: const EdgeInsets.all(0.5), child: IntrinsicHeight( child: IntrinsicWidth( child: Row( diff --git a/lib/src/layout/tab_view/tab.dart b/lib/src/layout/tab_view/tab.dart index 0ad7ce2c..d8edcdd8 100644 --- a/lib/src/layout/tab_view/tab.dart +++ b/lib/src/layout/tab_view/tab.dart @@ -3,7 +3,7 @@ import 'package:macos_ui/src/theme/macos_colors.dart'; import 'package:macos_ui/src/theme/macos_theme.dart'; const _kTabBorderRadius = BorderRadius.all( - Radius.circular(5.0), + Radius.circular(4.0), ); /// {@template macosTab} @@ -29,7 +29,7 @@ class MacosTab extends StatelessWidget { Widget build(BuildContext context) { final brightness = MacosTheme.brightnessOf(context); return PhysicalModel( - color: active ? const Color(0xFF625E66) : MacosColors.transparent, + color: active ? const Color(0xFF2B2E33) : MacosColors.transparent, borderRadius: _kTabBorderRadius, child: DecoratedBox( decoration: BoxDecoration( @@ -37,7 +37,7 @@ class MacosTab extends StatelessWidget { color: active ? brightness.resolve( MacosColors.white, - const Color(0xFF625E66), + const Color(0xFF646669), ) : MacosColors.transparent, ), diff --git a/lib/src/layout/tab_view/tab_view.dart b/lib/src/layout/tab_view/tab_view.dart index 02dbf37f..c12375c2 100644 --- a/lib/src/layout/tab_view/tab_view.dart +++ b/lib/src/layout/tab_view/tab_view.dart @@ -145,8 +145,8 @@ class _MacosTabViewState extends State { final brightness = MacosTheme.brightnessOf(context); final outerBorderColor = brightness.resolve( - const Color(0xFFDED9E3), - const Color(0xFF3F3E45), + const Color(0xFFE1E2E4), + const Color(0xFF3E4045), ); return Stack( @@ -157,8 +157,8 @@ class _MacosTabViewState extends State { child: DecoratedBox( decoration: BoxDecoration( color: brightness.resolve( - const Color(0xFFE9E4EB), - const Color(0xFF29242F), + const Color(0xFFE6E9EA), + const Color(0xFF2B2E33), ), border: Border.all( color: outerBorderColor, From 37581e140b24fe00c0383bd491d6f3b80813b255 Mon Sep 17 00:00:00 2001 From: GroovinChip Date: Sat, 9 Jul 2022 15:59:01 -0400 Subject: [PATCH 15/17] docs: update tab view screenshot in readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3e89b932..863f83cd 100644 --- a/README.md +++ b/README.md @@ -359,7 +359,7 @@ MacosListTile( ## MacosTabView A multipage interface that displays one page at a time. Must be used in a `StatefulWidget`. - + You can control the placement of the tabs using the `position` property. From 34b3b7409033f3f6a5792278b2d8c09b03e5e75d Mon Sep 17 00:00:00 2001 From: Minas Giannekas MBP Date: Sun, 10 Jul 2022 18:39:12 +0300 Subject: [PATCH 16/17] chore: fix README --- README.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/README.md b/README.md index def32afa..7d18b01c 100644 --- a/README.md +++ b/README.md @@ -630,11 +630,7 @@ MacosSwitch( ## MacosSegmentedControl -<<<<<<< HEAD -Displays one or more navigational tabs in a single horizontal group. Used by `MacosTabBar` to navigate between the -======= Displays one or more navigational tabs in a single horizontal group. Used by `MacosTabView` to navigate between the ->>>>>>> d534f79a8efc6190a233cc058c4643ae06aa7e3b different tabs of the tab bar. From 177207f6a78af78635a1119b6dde6ef6bd0d6ab0 Mon Sep 17 00:00:00 2001 From: Minas Giannekas MBP Date: Sun, 10 Jul 2022 19:26:29 +0300 Subject: [PATCH 17/17] test: fix date picker test --- test/selectors/date_picker_test.dart | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/test/selectors/date_picker_test.dart b/test/selectors/date_picker_test.dart index 98086010..18e10768 100644 --- a/test/selectors/date_picker_test.dart +++ b/test/selectors/date_picker_test.dart @@ -180,6 +180,7 @@ void main() { testWidgets( 'The selected calendar day matches the expected value', (tester) async { + final today = DateTime.now(); int selectedDay = 0; await tester.pumpWidget( MacosApp( @@ -203,10 +204,11 @@ void main() { ), ); - final dayToSelect = find.text('10'); + int dayToFind = today.day == 21 ? 22 : 21; + final dayToSelect = find.text(dayToFind.toString()); await tester.tap(dayToSelect); await tester.pumpAndSettle(); - expect(selectedDay, 10); + expect(selectedDay, dayToFind); }, );