diff --git a/CHANGELOG.md b/CHANGELOG.md
index b4911245..b8f3bf4e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,9 @@
+## [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)
* Improve the sizing of the disclosure item indicator
diff --git a/README.md b/README.md
index 4c3e7f54..7d18b01c 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,49 @@ 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: const [
+ MacosTab(
+ label: 'Tab 1',
+ ),
+ MacosTab(
+ label: 'Tab 2',
+ ),
+ MacosTab(
+ label: 'Tab 3',
+ ),
+ ],
+ children: const [
+ Center(
+ child: Text('Tab 1'),
+ ),
+ Center(
+ child: Text('Tab 2'),
+ ),
+ Center(
+ child: Text('Tab 3'),
+ ),
+ ],
+),
+
+```
# Icons
@@ -584,6 +628,16 @@ MacosSwitch(
),
```
+## MacosSegmentedControl
+
+Displays one or more navigational tabs in a single horizontal group. Used by `MacosTabView` 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..786b11b0
--- /dev/null
+++ b/example/lib/pages/tabview_page.dart
@@ -0,0 +1,59 @@
+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: const [
+ MacosTab(
+ label: 'Tab 1',
+ ),
+ MacosTab(
+ label: 'Tab 2',
+ ),
+ MacosTab(
+ label: 'Tab 3',
+ ),
+ ],
+ 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/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/segmented_control.dart b/lib/src/buttons/segmented_control.dart
new file mode 100644
index 00000000..411fd8d1
--- /dev/null
+++ b/lib/src/buttons/segmented_control.dart
@@ -0,0 +1,119 @@
+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 [MacosTabView] 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(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),
+ ),
+ ),
+ child: Padding(
+ padding: const EdgeInsets.all(0.5),
+ 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);
+ });
+ },
+ 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/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/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/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.dart b/lib/src/layout/tab_view/tab.dart
new file mode 100644
index 00000000..d8edcdd8
--- /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(4.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,
+ this.active = false,
+ });
+
+ /// The display label for this tab.
+ final String label;
+
+ /// 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(0xFF2B2E33) : MacosColors.transparent,
+ borderRadius: _kTabBorderRadius,
+ child: DecoratedBox(
+ decoration: BoxDecoration(
+ borderRadius: _kTabBorderRadius,
+ color: active
+ ? brightness.resolve(
+ MacosColors.white,
+ const Color(0xFF646669),
+ )
+ : 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..c12375c2
--- /dev/null
+++ b/lib/src/layout/tab_view/tab_view.dart
@@ -0,0 +1,191 @@
+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 {
+ /// 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,
+}
+
+/// {@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(0xFFE1E2E4),
+ const Color(0xFF3E4045),
+ );
+
+ return Stack(
+ alignment: Alignment.center,
+ children: [
+ Padding(
+ padding: const EdgeInsets.all(12.0),
+ child: DecoratedBox(
+ decoration: BoxDecoration(
+ color: brightness.resolve(
+ const Color(0xFFE6E9EA),
+ const Color(0xFF2B2E33),
+ ),
+ 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/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_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..9d618128 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
@@ -26,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,
@@ -40,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..e2c084d8 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 {
@@ -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,8 @@ 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 +556,7 @@ class MacosThemeData with Diagnosticable {
PushButtonThemeData? pushButtonTheme,
Color? dividerColor,
HelpButtonThemeData? helpButtonTheme,
- TooltipThemeData? tooltipTheme,
+ MacosTooltipThemeData? tooltipTheme,
VisualDensity? visualDensity,
MacosScrollbarThemeData? scrollbarTheme,
MacosIconButtonThemeData? iconButtonTheme,
@@ -583,6 +588,7 @@ class MacosThemeData with Diagnosticable {
);
}
+ /// Merges this [MacosThemeData] with another.
MacosThemeData merge(MacosThemeData? other) {
if (other == null) return this;
return copyWith(
@@ -624,7 +630,7 @@ class MacosThemeData with Diagnosticable {
helpButtonTheme,
));
properties.add(
- DiagnosticsProperty('tooltipTheme', tooltipTheme),
+ DiagnosticsProperty('tooltipTheme', tooltipTheme),
);
properties.add(
DiagnosticsProperty(
@@ -671,10 +677,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/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/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..b0958ea7 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,36 @@ 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 +72,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 +183,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 +195,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 +211,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 +229,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 +248,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/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
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 ed1c8632..a46af1e1 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"
@@ -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
diff --git a/test/buttons/segmented_control_test.dart b/test/buttons/segmented_control_test.dart
new file mode 100644
index 00000000..8e19831e
--- /dev/null
+++ b/test/buttons/segmented_control_test.dart
@@ -0,0 +1,97 @@
+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: const [
+ MacosTab(
+ label: 'Tab 1',
+ ),
+ MacosTab(
+ label: 'Tab 2',
+ ),
+ MacosTab(
+ label: 'Tab 3',
+ ),
+ ],
+ ),
+ );
+ },
+ ),
+ ],
+ ),
+ ),
+ ),
+ );
+
+ 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: const [
+ MacosTab(
+ label: 'Tab 1',
+ ),
+ MacosTab(
+ label: 'Tab 2',
+ ),
+ MacosTab(
+ label: 'Tab 3',
+ ),
+ ],
+ ),
+ );
+ },
+ ),
+ ],
+ ),
+ ),
+ ),
+ );
+
+ 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..3673f4d1
--- /dev/null
+++ b/test/layout/tab_view_test.dart
@@ -0,0 +1,64 @@
+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: const [
+ MacosTab(
+ label: 'Tab 1',
+ ),
+ MacosTab(
+ label: 'Tab 2',
+ ),
+ MacosTab(
+ label: 'Tab 3',
+ ),
+ ],
+ 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);
+ },
+ );
+ });
+}
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,
),