From 278dc11237b70ef5720a8fc238d37d81c176a912 Mon Sep 17 00:00:00 2001 From: Reuben Turner Date: Fri, 3 Mar 2023 10:58:38 -0500 Subject: [PATCH 1/2] Version 12.2.2 (#382) When toolbar item is clicked, first pop the route and then call its callback - Fixes #346 (#381) * fix: when a toolbar item is clicked, remove route first and then call its callback - fixes #346 * chore: update version and changelog Co-authored-by: Minas Giannekas --- CHANGELOG.md | 3 +++ example/pubspec.lock | 2 +- lib/src/layout/toolbar/toolbar_overflow_menu_item.dart | 2 +- pubspec.yaml | 2 +- 4 files changed, 6 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a944e6cd..50742fa2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +## [1.12.2] +* Fixed a bug where clicking on a overflowed toolbar item with a navigation callback wouldn't work ([#346](https://github.com/GroovinChip/macos_ui/issues/346)). + ## [1.12.1+1] * Fixed a typo in the December abbreviation displayed in the `MacosDatePicker`. diff --git a/example/pubspec.lock b/example/pubspec.lock index 06d01493..cf8e59b8 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -97,7 +97,7 @@ packages: path: ".." relative: true source: path - version: "1.12.1+1" + version: "1.12.2" matcher: dependency: transitive description: diff --git a/lib/src/layout/toolbar/toolbar_overflow_menu_item.dart b/lib/src/layout/toolbar/toolbar_overflow_menu_item.dart index ed4ff69f..e92c57c2 100644 --- a/lib/src/layout/toolbar/toolbar_overflow_menu_item.dart +++ b/lib/src/layout/toolbar/toolbar_overflow_menu_item.dart @@ -54,8 +54,8 @@ class _ToolbarOverflowMenuItemState extends State { } void _handleOnTap() { - widget.onPressed?.call(); Navigator.pop(context); + widget.onPressed?.call(); } bool get _isHighlighted => _isHovered || widget.isSelected == true; diff --git a/pubspec.yaml b/pubspec.yaml index 2971ef05..4d660503 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.12.1+1 +version: 1.12.2 homepage: "https://macosui.dev" repository: "https://github.com/GroovinChip/macos_ui" From cbdf95b81e219be371ccd2bc0ef4c5ddaa1cd75c Mon Sep 17 00:00:00 2001 From: Reuben Turner Date: Wed, 19 Jul 2023 16:02:11 -0400 Subject: [PATCH 2/2] Version `2.0.0` (#463) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore: account for latest Flutter:master * fix(example): incorrect variable usage * Version 1.11.1 (#366) * Version 1.7.1 (#287) * Sidebar top (#244) * chore: refactor dir structure * feat: Sidebar top & updated default control color * feat(example): search results in top * chore: bump version, changelog * chore: run flutter pub upgrade * Update CHANGELOG.md * Update lib/src/layout/sidebar/sidebar.dart Co-authored-by: Minas Giannekas * chore: update issue templates * chore: update pr_prelaunch script * Flutter 3 upgrade & MacosColor update (#248) * chore: Update pubspec.yaml files to support Flutter 3 * chore: run dart fix --apply * chore: migrate Scrollbar to Flutter 3 * chore: update flutter_lints & subsequent fixes * feat: add missing functions to MacosColor the Color class has a number of functions that MacosColor had not implemented * chore: use super parameters * chore: update changelog * chore: tweak example app Uses the new PlatformMenuBar. Also update product name. * chore: small changelog tweak * chore: run flutter format . * chore: run dart fix --apply * chore: run flutter format . * chore: remove unused code in example * chore: remove unused import * chore: remove unused code * Starter app (#251) * chore: Update pubspec.yaml files to support Flutter 3 * chore: run dart fix --apply * chore: migrate Scrollbar to Flutter 3 * chore: update flutter_lints & subsequent fixes * feat: add missing functions to MacosColor the Color class has a number of functions that MacosColor had not implemented * chore: use super parameters * chore: update changelog * chore: tweak example app Uses the new PlatformMenuBar. Also update product name. * chore: small changelog tweak * chore: run flutter format . * chore: run dart fix --apply * chore: run flutter format . * chore: remove unused code in example * chore: remove unused import * feat: first pass at starter app brick * chore: improve starter app brick * chore: fix widget test in starter app * feat: conditional prompts & running pub get * chore: finalize brick * chore: run flutter format * chore: exclude starter app from analyzer * Full screen opaque toolbar issue (closes #249) (#252) * fix: don't show app window toolbar when in full screen * chore: update README * chore: update brick app window code * chore: update pubspec and changelog * chore: update actions Co-authored-by: Reuben Turner * Version 1.4.1 (#255) * Sidebar top (#244) * chore: refactor dir structure * feat: Sidebar top & updated default control color * feat(example): search results in top * chore: bump version, changelog * chore: run flutter pub upgrade * Update CHANGELOG.md * Update lib/src/layout/sidebar/sidebar.dart Co-authored-by: Minas Giannekas * chore: update issue templates * chore: update pr_prelaunch script * Flutter 3 upgrade & MacosColor update (#248) * chore: Update pubspec.yaml files to support Flutter 3 * chore: run dart fix --apply * chore: migrate Scrollbar to Flutter 3 * chore: update flutter_lints & subsequent fixes * feat: add missing functions to MacosColor the Color class has a number of functions that MacosColor had not implemented * chore: use super parameters * chore: update changelog * chore: tweak example app Uses the new PlatformMenuBar. Also update product name. * chore: small changelog tweak * chore: run flutter format . * chore: run dart fix --apply * chore: run flutter format . * chore: remove unused code in example * chore: remove unused import * chore: remove unused code * Starter app (#251) * chore: Update pubspec.yaml files to support Flutter 3 * chore: run dart fix --apply * chore: migrate Scrollbar to Flutter 3 * chore: update flutter_lints & subsequent fixes * feat: add missing functions to MacosColor the Color class has a number of functions that MacosColor had not implemented * chore: use super parameters * chore: update changelog * chore: tweak example app Uses the new PlatformMenuBar. Also update product name. * chore: small changelog tweak * chore: run flutter format . * chore: run dart fix --apply * chore: run flutter format . * chore: remove unused code in example * chore: remove unused import * feat: first pass at starter app brick * chore: improve starter app brick * chore: fix widget test in starter app * feat: conditional prompts & running pub get * chore: finalize brick * chore: run flutter format * chore: exclude starter app from analyzer * Full screen opaque toolbar issue (closes #249) (#252) * fix: don't show app window toolbar when in full screen * chore: update README * chore: update brick app window code * chore: update pubspec and changelog * chore: update actions Co-authored-by: Reuben Turner Co-authored-by: Minas Giannekas * chore: repository, homepage fields * chore: update readme * feat(starter_app): Version 1.1.0 * feat(starter_app): multi-window support * feat: starter_app 1.2.1 * chore: move brick to its own repo & go back to old pana action * Expand remaining part of row in MacosListTile (#265) * Expand remaining part of row https://github.com/GroovinChip/macos_ui/issues/264 * Increment to version 1.4.2 * End sidebar (#267) * chore: add missing trailing comma * chore: improve MacosIconButton animation curve * chore: remove false_secrets from pubspec.yaml * feat: end sidebar Also fixes the tests portion of the pr_prelaunch_tasks script * feat: add "isEndSidebarShown" to MacosWindowScope * fix: Correct the placement of the leading widget in disclosure sidebar items (#272) * chore: Update changelog * 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 * Update Actions (#279) * Update flutter_analysis.yml * Update dart_code_metrics.yaml * Update gh_pages.yml * Update pana_analysis.yml * Update codecov.yaml * fix syntax issue * Version 1.6.0 - `MacosTabView` & `MacosSegmentedControl` (#273) * feat: MacosTabView & MacosSegmentedControl * chore: fixup scripts * test: tests for segmented control & tab view * chore: remove unused code * chore: run flutter format . * chore: bump code metrics * docs: dartdoc updates * docs: fix a documentation error * chore: run flutter format . * refactor: make active property of MacosTab optional, since it is handled via MacosSegmentedControl * chore: fix import change * refactor: change colors to match default native app design * docs: update tab view screenshot in readme * chore: update README example for MacosTabView * chore: fix typo in MacosSegmentedControl * chore: fix typo in MacosSegmentedControl docstring * test: remove explicitly setting active property of MacosTabs Co-authored-by: Minas Giannekas * Version 1.7.0: `MacosImageIcon` & sidebar updates (#274) * feat: MacosTabView & MacosSegmentedControl * chore: fixup scripts * test: tests for segmented control & tab view * chore: remove unused code * chore: run flutter format . * chore: bump code metrics * docs: dartdoc updates * docs: fix a documentation error * chore: run flutter format . * feat: MacosImageIcon & sidebar updates * 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 * refactor: make active property of MacosTab optional, since it is handled via MacosSegmentedControl * chore: fix import change * refactor: change colors to match default native app design * docs: update tab view screenshot in readme * chore: fix README * test: fix date picker test Co-authored-by: Minas Giannekas * chore: fix typo in pr template * feat: gh action to auto-generate releases on push to stable * chore: update release action with latest from stable * feat: add action to publish to pub * docs: update readme * Addresses #237 * Adds MacosColorWell to selectors section * Adds code snippets for date & time pickers * fix: 1.7.1 Fixes issue where end sidebar window breakpoint wasn’t being respected Co-authored-by: Minas Giannekas Co-authored-by: Jon Saw Co-authored-by: Minas Giannekas * Version 1.7.3 (#293) * Version 1.7.4 (#295) * Sidebar top (#244) * chore: refactor dir structure * feat: Sidebar top & updated default control color * feat(example): search results in top * chore: bump version, changelog * chore: run flutter pub upgrade * Update CHANGELOG.md * Update lib/src/layout/sidebar/sidebar.dart Co-authored-by: Minas Giannekas * chore: update issue templates * chore: update pr_prelaunch script * Flutter 3 upgrade & MacosColor update (#248) * chore: Update pubspec.yaml files to support Flutter 3 * chore: run dart fix --apply * chore: migrate Scrollbar to Flutter 3 * chore: update flutter_lints & subsequent fixes * feat: add missing functions to MacosColor the Color class has a number of functions that MacosColor had not implemented * chore: use super parameters * chore: update changelog * chore: tweak example app Uses the new PlatformMenuBar. Also update product name. * chore: small changelog tweak * chore: run flutter format . * chore: run dart fix --apply * chore: run flutter format . * chore: remove unused code in example * chore: remove unused import * chore: remove unused code * Starter app (#251) * chore: Update pubspec.yaml files to support Flutter 3 * chore: run dart fix --apply * chore: migrate Scrollbar to Flutter 3 * chore: update flutter_lints & subsequent fixes * feat: add missing functions to MacosColor the Color class has a number of functions that MacosColor had not implemented * chore: use super parameters * chore: update changelog * chore: tweak example app Uses the new PlatformMenuBar. Also update product name. * chore: small changelog tweak * chore: run flutter format . * chore: run dart fix --apply * chore: run flutter format . * chore: remove unused code in example * chore: remove unused import * feat: first pass at starter app brick * chore: improve starter app brick * chore: fix widget test in starter app * feat: conditional prompts & running pub get * chore: finalize brick * chore: run flutter format * chore: exclude starter app from analyzer * Full screen opaque toolbar issue (closes #249) (#252) * fix: don't show app window toolbar when in full screen * chore: update README * chore: update brick app window code * chore: update pubspec and changelog * chore: update actions Co-authored-by: Reuben Turner * Version 1.4.1 (#255) * Sidebar top (#244) * chore: refactor dir structure * feat: Sidebar top & updated default control color * feat(example): search results in top * chore: bump version, changelog * chore: run flutter pub upgrade * Update CHANGELOG.md * Update lib/src/layout/sidebar/sidebar.dart Co-authored-by: Minas Giannekas * chore: update issue templates * chore: update pr_prelaunch script * Flutter 3 upgrade & MacosColor update (#248) * chore: Update pubspec.yaml files to support Flutter 3 * chore: run dart fix --apply * chore: migrate Scrollbar to Flutter 3 * chore: update flutter_lints & subsequent fixes * feat: add missing functions to MacosColor the Color class has a number of functions that MacosColor had not implemented * chore: use super parameters * chore: update changelog * chore: tweak example app Uses the new PlatformMenuBar. Also update product name. * chore: small changelog tweak * chore: run flutter format . * chore: run dart fix --apply * chore: run flutter format . * chore: remove unused code in example * chore: remove unused import * chore: remove unused code * Starter app (#251) * chore: Update pubspec.yaml files to support Flutter 3 * chore: run dart fix --apply * chore: migrate Scrollbar to Flutter 3 * chore: update flutter_lints & subsequent fixes * feat: add missing functions to MacosColor the Color class has a number of functions that MacosColor had not implemented * chore: use super parameters * chore: update changelog * chore: tweak example app Uses the new PlatformMenuBar. Also update product name. * chore: small changelog tweak * chore: run flutter format . * chore: run dart fix --apply * chore: run flutter format . * chore: remove unused code in example * chore: remove unused import * feat: first pass at starter app brick * chore: improve starter app brick * chore: fix widget test in starter app * feat: conditional prompts & running pub get * chore: finalize brick * chore: run flutter format * chore: exclude starter app from analyzer * Full screen opaque toolbar issue (closes #249) (#252) * fix: don't show app window toolbar when in full screen * chore: update README * chore: update brick app window code * chore: update pubspec and changelog * chore: update actions Co-authored-by: Reuben Turner Co-authored-by: Minas Giannekas * chore: repository, homepage fields * chore: update readme * feat(starter_app): Version 1.1.0 * feat(starter_app): multi-window support * feat: starter_app 1.2.1 * chore: move brick to its own repo & go back to old pana action * Expand remaining part of row in MacosListTile (#265) * Expand remaining part of row https://github.com/GroovinChip/macos_ui/issues/264 * Increment to version 1.4.2 * End sidebar (#267) * chore: add missing trailing comma * chore: improve MacosIconButton animation curve * chore: remove false_secrets from pubspec.yaml * feat: end sidebar Also fixes the tests portion of the pr_prelaunch_tasks script * feat: add "isEndSidebarShown" to MacosWindowScope * fix: Correct the placement of the leading widget in disclosure sidebar items (#272) * chore: Update changelog * 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 * Update Actions (#279) * Update flutter_analysis.yml * Update dart_code_metrics.yaml * Update gh_pages.yml * Update pana_analysis.yml * Update codecov.yaml * fix syntax issue * Version 1.6.0 - `MacosTabView` & `MacosSegmentedControl` (#273) * feat: MacosTabView & MacosSegmentedControl * chore: fixup scripts * test: tests for segmented control & tab view * chore: remove unused code * chore: run flutter format . * chore: bump code metrics * docs: dartdoc updates * docs: fix a documentation error * chore: run flutter format . * refactor: make active property of MacosTab optional, since it is handled via MacosSegmentedControl * chore: fix import change * refactor: change colors to match default native app design * docs: update tab view screenshot in readme * chore: update README example for MacosTabView * chore: fix typo in MacosSegmentedControl * chore: fix typo in MacosSegmentedControl docstring * test: remove explicitly setting active property of MacosTabs Co-authored-by: Minas Giannekas * Version 1.7.0: `MacosImageIcon` & sidebar updates (#274) * feat: MacosTabView & MacosSegmentedControl * chore: fixup scripts * test: tests for segmented control & tab view * chore: remove unused code * chore: run flutter format . * chore: bump code metrics * docs: dartdoc updates * docs: fix a documentation error * chore: run flutter format . * feat: MacosImageIcon & sidebar updates * 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 * refactor: make active property of MacosTab optional, since it is handled via MacosSegmentedControl * chore: fix import change * refactor: change colors to match default native app design * docs: update tab view screenshot in readme * chore: fix README * test: fix date picker test Co-authored-by: Minas Giannekas * chore: fix typo in pr template * feat: gh action to auto-generate releases on push to stable * chore: update release action with latest from stable * feat: add action to publish to pub * docs: update readme * Addresses #237 * Adds MacosColorWell to selectors section * Adds code snippets for date & time pickers * fix: 1.7.1 Fixes issue where end sidebar window breakpoint wasn’t being respected * Tab view padding (#285) * fix: use prepared title wrapped with a DefaultTextStyle instead of the raw title (#289) * chore: fix typo Closes #290 * feat: add `backgroundColor` to `MacosSheet` (#291) Co-authored-by: Minas Giannekas Co-authored-by: Jon Saw Co-authored-by: Minas Giannekas Co-authored-by: st merlhin <77164238+stMerlHin@users.noreply.github.com> Co-authored-by: jtdLab <72231111+jtdLab@users.noreply.github.com> * Version 1.7.5 (#299) * chore: fix Flutter 3.3 warnings * Version 1.7.6 (#327) * Sidebar top (#244) * chore: refactor dir structure * feat: Sidebar top & updated default control color * feat(example): search results in top * chore: bump version, changelog * chore: run flutter pub upgrade * Update CHANGELOG.md * Update lib/src/layout/sidebar/sidebar.dart Co-authored-by: Minas Giannekas * chore: update issue templates * chore: update pr_prelaunch script * Flutter 3 upgrade & MacosColor update (#248) * chore: Update pubspec.yaml files to support Flutter 3 * chore: run dart fix --apply * chore: migrate Scrollbar to Flutter 3 * chore: update flutter_lints & subsequent fixes * feat: add missing functions to MacosColor the Color class has a number of functions that MacosColor had not implemented * chore: use super parameters * chore: update changelog * chore: tweak example app Uses the new PlatformMenuBar. Also update product name. * chore: small changelog tweak * chore: run flutter format . * chore: run dart fix --apply * chore: run flutter format . * chore: remove unused code in example * chore: remove unused import * chore: remove unused code * Starter app (#251) * chore: Update pubspec.yaml files to support Flutter 3 * chore: run dart fix --apply * chore: migrate Scrollbar to Flutter 3 * chore: update flutter_lints & subsequent fixes * feat: add missing functions to MacosColor the Color class has a number of functions that MacosColor had not implemented * chore: use super parameters * chore: update changelog * chore: tweak example app Uses the new PlatformMenuBar. Also update product name. * chore: small changelog tweak * chore: run flutter format . * chore: run dart fix --apply * chore: run flutter format . * chore: remove unused code in example * chore: remove unused import * feat: first pass at starter app brick * chore: improve starter app brick * chore: fix widget test in starter app * feat: conditional prompts & running pub get * chore: finalize brick * chore: run flutter format * chore: exclude starter app from analyzer * Full screen opaque toolbar issue (closes #249) (#252) * fix: don't show app window toolbar when in full screen * chore: update README * chore: update brick app window code * chore: update pubspec and changelog * chore: update actions Co-authored-by: Reuben Turner * Version 1.4.1 (#255) * Sidebar top (#244) * chore: refactor dir structure * feat: Sidebar top & updated default control color * feat(example): search results in top * chore: bump version, changelog * chore: run flutter pub upgrade * Update CHANGELOG.md * Update lib/src/layout/sidebar/sidebar.dart Co-authored-by: Minas Giannekas * chore: update issue templates * chore: update pr_prelaunch script * Flutter 3 upgrade & MacosColor update (#248) * chore: Update pubspec.yaml files to support Flutter 3 * chore: run dart fix --apply * chore: migrate Scrollbar to Flutter 3 * chore: update flutter_lints & subsequent fixes * feat: add missing functions to MacosColor the Color class has a number of functions that MacosColor had not implemented * chore: use super parameters * chore: update changelog * chore: tweak example app Uses the new PlatformMenuBar. Also update product name. * chore: small changelog tweak * chore: run flutter format . * chore: run dart fix --apply * chore: run flutter format . * chore: remove unused code in example * chore: remove unused import * chore: remove unused code * Starter app (#251) * chore: Update pubspec.yaml files to support Flutter 3 * chore: run dart fix --apply * chore: migrate Scrollbar to Flutter 3 * chore: update flutter_lints & subsequent fixes * feat: add missing functions to MacosColor the Color class has a number of functions that MacosColor had not implemented * chore: use super parameters * chore: update changelog * chore: tweak example app Uses the new PlatformMenuBar. Also update product name. * chore: small changelog tweak * chore: run flutter format . * chore: run dart fix --apply * chore: run flutter format . * chore: remove unused code in example * chore: remove unused import * feat: first pass at starter app brick * chore: improve starter app brick * chore: fix widget test in starter app * feat: conditional prompts & running pub get * chore: finalize brick * chore: run flutter format * chore: exclude starter app from analyzer * Full screen opaque toolbar issue (closes #249) (#252) * fix: don't show app window toolbar when in full screen * chore: update README * chore: update brick app window code * chore: update pubspec and changelog * chore: update actions Co-authored-by: Reuben Turner Co-authored-by: Minas Giannekas * chore: repository, homepage fields * chore: update readme * feat(starter_app): Version 1.1.0 * feat(starter_app): multi-window support * feat: starter_app 1.2.1 * chore: move brick to its own repo & go back to old pana action * Expand remaining part of row in MacosListTile (#265) * Expand remaining part of row https://github.com/GroovinChip/macos_ui/issues/264 * Increment to version 1.4.2 * End sidebar (#267) * chore: add missing trailing comma * chore: improve MacosIconButton animation curve * chore: remove false_secrets from pubspec.yaml * feat: end sidebar Also fixes the tests portion of the pr_prelaunch_tasks script * feat: add "isEndSidebarShown" to MacosWindowScope * fix: Correct the placement of the leading widget in disclosure sidebar items (#272) * chore: Update changelog * 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 * Update Actions (#279) * Update flutter_analysis.yml * Update dart_code_metrics.yaml * Update gh_pages.yml * Update pana_analysis.yml * Update codecov.yaml * fix syntax issue * Version 1.6.0 - `MacosTabView` & `MacosSegmentedControl` (#273) * feat: MacosTabView & MacosSegmentedControl * chore: fixup scripts * test: tests for segmented control & tab view * chore: remove unused code * chore: run flutter format . * chore: bump code metrics * docs: dartdoc updates * docs: fix a documentation error * chore: run flutter format . * refactor: make active property of MacosTab optional, since it is handled via MacosSegmentedControl * chore: fix import change * refactor: change colors to match default native app design * docs: update tab view screenshot in readme * chore: update README example for MacosTabView * chore: fix typo in MacosSegmentedControl * chore: fix typo in MacosSegmentedControl docstring * test: remove explicitly setting active property of MacosTabs Co-authored-by: Minas Giannekas * Version 1.7.0: `MacosImageIcon` & sidebar updates (#274) * feat: MacosTabView & MacosSegmentedControl * chore: fixup scripts * test: tests for segmented control & tab view * chore: remove unused code * chore: run flutter format . * chore: bump code metrics * docs: dartdoc updates * docs: fix a documentation error * chore: run flutter format . * feat: MacosImageIcon & sidebar updates * 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 * refactor: make active property of MacosTab optional, since it is handled via MacosSegmentedControl * chore: fix import change * refactor: change colors to match default native app design * docs: update tab view screenshot in readme * chore: fix README * test: fix date picker test Co-authored-by: Minas Giannekas * chore: fix typo in pr template * feat: gh action to auto-generate releases on push to stable * chore: update release action with latest from stable * feat: add action to publish to pub * docs: update readme * Addresses #237 * Adds MacosColorWell to selectors section * Adds code snippets for date & time pickers * fix: 1.7.1 Fixes issue where end sidebar window breakpoint wasn’t being respected * Tab view padding (#285) * fix: use prepared title wrapped with a DefaultTextStyle instead of the raw title (#289) * chore: fix typo Closes #290 * feat: add `backgroundColor` to `MacosSheet` (#291) * chore: fix Flutter 3.3 warnings * fix: address ScrollController bug in MacosPopupButton (#300) * fix(tests): account for Jan -> Dec & Dec -> Jan date_picker_test.dart was failing due to not accounting for going from January to December and vice-versa. Co-authored-by: Minas Giannekas Co-authored-by: Jon Saw Co-authored-by: Minas Giannekas Co-authored-by: st merlhin <77164238+stMerlHin@users.noreply.github.com> Co-authored-by: jtdLab <72231111+jtdLab@users.noreply.github.com> * Version 1.9.0 (#339) * Sidebar top (#244) * chore: refactor dir structure * feat: Sidebar top & updated default control color * feat(example): search results in top * chore: bump version, changelog * chore: run flutter pub upgrade * Update CHANGELOG.md * Update lib/src/layout/sidebar/sidebar.dart Co-authored-by: Minas Giannekas * chore: update issue templates * chore: update pr_prelaunch script * Flutter 3 upgrade & MacosColor update (#248) * chore: Update pubspec.yaml files to support Flutter 3 * chore: run dart fix --apply * chore: migrate Scrollbar to Flutter 3 * chore: update flutter_lints & subsequent fixes * feat: add missing functions to MacosColor the Color class has a number of functions that MacosColor had not implemented * chore: use super parameters * chore: update changelog * chore: tweak example app Uses the new PlatformMenuBar. Also update product name. * chore: small changelog tweak * chore: run flutter format . * chore: run dart fix --apply * chore: run flutter format . * chore: remove unused code in example * chore: remove unused import * chore: remove unused code * Starter app (#251) * chore: Update pubspec.yaml files to support Flutter 3 * chore: run dart fix --apply * chore: migrate Scrollbar to Flutter 3 * chore: update flutter_lints & subsequent fixes * feat: add missing functions to MacosColor the Color class has a number of functions that MacosColor had not implemented * chore: use super parameters * chore: update changelog * chore: tweak example app Uses the new PlatformMenuBar. Also update product name. * chore: small changelog tweak * chore: run flutter format . * chore: run dart fix --apply * chore: run flutter format . * chore: remove unused code in example * chore: remove unused import * feat: first pass at starter app brick * chore: improve starter app brick * chore: fix widget test in starter app * feat: conditional prompts & running pub get * chore: finalize brick * chore: run flutter format * chore: exclude starter app from analyzer * Full screen opaque toolbar issue (closes #249) (#252) * fix: don't show app window toolbar when in full screen * chore: update README * chore: update brick app window code * chore: update pubspec and changelog * chore: update actions Co-authored-by: Reuben Turner * Version 1.4.1 (#255) * Sidebar top (#244) * chore: refactor dir structure * feat: Sidebar top & updated default control color * feat(example): search results in top * chore: bump version, changelog * chore: run flutter pub upgrade * Update CHANGELOG.md * Update lib/src/layout/sidebar/sidebar.dart Co-authored-by: Minas Giannekas * chore: update issue templates * chore: update pr_prelaunch script * Flutter 3 upgrade & MacosColor update (#248) * chore: Update pubspec.yaml files to support Flutter 3 * chore: run dart fix --apply * chore: migrate Scrollbar to Flutter 3 * chore: update flutter_lints & subsequent fixes * feat: add missing functions to MacosColor the Color class has a number of functions that MacosColor had not implemented * chore: use super parameters * chore: update changelog * chore: tweak example app Uses the new PlatformMenuBar. Also update product name. * chore: small changelog tweak * chore: run flutter format . * chore: run dart fix --apply * chore: run flutter format . * chore: remove unused code in example * chore: remove unused import * chore: remove unused code * Starter app (#251) * chore: Update pubspec.yaml files to support Flutter 3 * chore: run dart fix --apply * chore: migrate Scrollbar to Flutter 3 * chore: update flutter_lints & subsequent fixes * feat: add missing functions to MacosColor the Color class has a number of functions that MacosColor had not implemented * chore: use super parameters * chore: update changelog * chore: tweak example app Uses the new PlatformMenuBar. Also update product name. * chore: small changelog tweak * chore: run flutter format . * chore: run dart fix --apply * chore: run flutter format . * chore: remove unused code in example * chore: remove unused import * feat: first pass at starter app brick * chore: improve starter app brick * chore: fix widget test in starter app * feat: conditional prompts & running pub get * chore: finalize brick * chore: run flutter format * chore: exclude starter app from analyzer * Full screen opaque toolbar issue (closes #249) (#252) * fix: don't show app window toolbar when in full screen * chore: update README * chore: update brick app window code * chore: update pubspec and changelog * chore: update actions Co-authored-by: Reuben Turner Co-authored-by: Minas Giannekas * chore: repository, homepage fields * chore: update readme * feat(starter_app): Version 1.1.0 * feat(starter_app): multi-window support * feat: starter_app 1.2.1 * chore: move brick to its own repo & go back to old pana action * Expand remaining part of row in MacosListTile (#265) * Expand remaining part of row https://github.com/GroovinChip/macos_ui/issues/264 * Increment to version 1.4.2 * End sidebar (#267) * chore: add missing trailing comma * chore: improve MacosIconButton animation curve * chore: remove false_secrets from pubspec.yaml * feat: end sidebar Also fixes the tests portion of the pr_prelaunch_tasks script * feat: add "isEndSidebarShown" to MacosWindowScope * fix: Correct the placement of the leading widget in disclosure sidebar items (#272) * chore: Update changelog * 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 * Update Actions (#279) * Update flutter_analysis.yml * Update dart_code_metrics.yaml * Update gh_pages.yml * Update pana_analysis.yml * Update codecov.yaml * fix syntax issue * Version 1.6.0 - `MacosTabView` & `MacosSegmentedControl` (#273) * feat: MacosTabView & MacosSegmentedControl * chore: fixup scripts * test: tests for segmented control & tab view * chore: remove unused code * chore: run flutter format . * chore: bump code metrics * docs: dartdoc updates * docs: fix a documentation error * chore: run flutter format . * refactor: make active property of MacosTab optional, since it is handled via MacosSegmentedControl * chore: fix import change * refactor: change colors to match default native app design * docs: update tab view screenshot in readme * chore: update README example for MacosTabView * chore: fix typo in MacosSegmentedControl * chore: fix typo in MacosSegmentedControl docstring * test: remove explicitly setting active property of MacosTabs Co-authored-by: Minas Giannekas * Version 1.7.0: `MacosImageIcon` & sidebar updates (#274) * feat: MacosTabView & MacosSegmentedControl * chore: fixup scripts * test: tests for segmented control & tab view * chore: remove unused code * chore: run flutter format . * chore: bump code metrics * docs: dartdoc updates * docs: fix a documentation error * chore: run flutter format . * feat: MacosImageIcon & sidebar updates * 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 * refactor: make active property of MacosTab optional, since it is handled via MacosSegmentedControl * chore: fix import change * refactor: change colors to match default native app design * docs: update tab view screenshot in readme * chore: fix README * test: fix date picker test Co-authored-by: Minas Giannekas * chore: fix typo in pr template * feat: gh action to auto-generate releases on push to stable * chore: update release action with latest from stable * feat: add action to publish to pub * docs: update readme * Addresses #237 * Adds MacosColorWell to selectors section * Adds code snippets for date & time pickers * fix: 1.7.1 Fixes issue where end sidebar window breakpoint wasn’t being respected * Tab view padding (#285) * fix: use prepared title wrapped with a DefaultTextStyle instead of the raw title (#289) * chore: fix typo Closes #290 * feat: add `backgroundColor` to `MacosSheet` (#291) * chore: fix Flutter 3.3 warnings * fix: address ScrollController bug in MacosPopupButton (#300) * fix(tests): account for Jan -> Dec & Dec -> Jan date_picker_test.dart was failing due to not accounting for going from January to December and vice-versa. * fix(plugin): Ensure the native color panel releases when closed * Update flutter_analysis.yml Closes #334 * Various bug fixes & minor updates (#338) * Macos slider (#337) * chore: run flutter format . * chore: fix analysis * chore: Bump version and update CHANGELOG.md * chore: Update images to self taken ones as MacOS images are outdated * fix: fix position offset by a small value * fix: PR review feedback * Update lib/src/indicators/slider.dart --------- Co-authored-by: Reuben Turner --------- Co-authored-by: Minas Giannekas Co-authored-by: Jon Saw Co-authored-by: Minas Giannekas Co-authored-by: st merlhin <77164238+stMerlHin@users.noreply.github.com> Co-authored-by: jtdLab <72231111+jtdLab@users.noreply.github.com> Co-authored-by: Norbert Kozsir * Version 1.9.1 (#341) * Sidebar top (#244) * chore: refactor dir structure * feat: Sidebar top & updated default control color * feat(example): search results in top * chore: bump version, changelog * chore: run flutter pub upgrade * Update CHANGELOG.md * Update lib/src/layout/sidebar/sidebar.dart Co-authored-by: Minas Giannekas * chore: update issue templates * chore: update pr_prelaunch script * Flutter 3 upgrade & MacosColor update (#248) * chore: Update pubspec.yaml files to support Flutter 3 * chore: run dart fix --apply * chore: migrate Scrollbar to Flutter 3 * chore: update flutter_lints & subsequent fixes * feat: add missing functions to MacosColor the Color class has a number of functions that MacosColor had not implemented * chore: use super parameters * chore: update changelog * chore: tweak example app Uses the new PlatformMenuBar. Also update product name. * chore: small changelog tweak * chore: run flutter format . * chore: run dart fix --apply * chore: run flutter format . * chore: remove unused code in example * chore: remove unused import * chore: remove unused code * Starter app (#251) * chore: Update pubspec.yaml files to support Flutter 3 * chore: run dart fix --apply * chore: migrate Scrollbar to Flutter 3 * chore: update flutter_lints & subsequent fixes * feat: add missing functions to MacosColor the Color class has a number of functions that MacosColor had not implemented * chore: use super parameters * chore: update changelog * chore: tweak example app Uses the new PlatformMenuBar. Also update product name. * chore: small changelog tweak * chore: run flutter format . * chore: run dart fix --apply * chore: run flutter format . * chore: remove unused code in example * chore: remove unused import * feat: first pass at starter app brick * chore: improve starter app brick * chore: fix widget test in starter app * feat: conditional prompts & running pub get * chore: finalize brick * chore: run flutter format * chore: exclude starter app from analyzer * Full screen opaque toolbar issue (closes #249) (#252) * fix: don't show app window toolbar when in full screen * chore: update README * chore: update brick app window code * chore: update pubspec and changelog * chore: update actions Co-authored-by: Reuben Turner * Version 1.4.1 (#255) * Sidebar top (#244) * chore: refactor dir structure * feat: Sidebar top & updated default control color * feat(example): search results in top * chore: bump version, changelog * chore: run flutter pub upgrade * Update CHANGELOG.md * Update lib/src/layout/sidebar/sidebar.dart Co-authored-by: Minas Giannekas * chore: update issue templates * chore: update pr_prelaunch script * Flutter 3 upgrade & MacosColor update (#248) * chore: Update pubspec.yaml files to support Flutter 3 * chore: run dart fix --apply * chore: migrate Scrollbar to Flutter 3 * chore: update flutter_lints & subsequent fixes * feat: add missing functions to MacosColor the Color class has a number of functions that MacosColor had not implemented * chore: use super parameters * chore: update changelog * chore: tweak example app Uses the new PlatformMenuBar. Also update product name. * chore: small changelog tweak * chore: run flutter format . * chore: run dart fix --apply * chore: run flutter format . * chore: remove unused code in example * chore: remove unused import * chore: remove unused code * Starter app (#251) * chore: Update pubspec.yaml files to support Flutter 3 * chore: run dart fix --apply * chore: migrate Scrollbar to Flutter 3 * chore: update flutter_lints & subsequent fixes * feat: add missing functions to MacosColor the Color class has a number of functions that MacosColor had not implemented * chore: use super parameters * chore: update changelog * chore: tweak example app Uses the new PlatformMenuBar. Also update product name. * chore: small changelog tweak * chore: run flutter format . * chore: run dart fix --apply * chore: run flutter format . * chore: remove unused code in example * chore: remove unused import * feat: first pass at starter app brick * chore: improve starter app brick * chore: fix widget test in starter app * feat: conditional prompts & running pub get * chore: finalize brick * chore: run flutter format * chore: exclude starter app from analyzer * Full screen opaque toolbar issue (closes #249) (#252) * fix: don't show app window toolbar when in full screen * chore: update README * chore: update brick app window code * chore: update pubspec and changelog * chore: update actions Co-authored-by: Reuben Turner Co-authored-by: Minas Giannekas * chore: repository, homepage fields * chore: update readme * feat(starter_app): Version 1.1.0 * feat(starter_app): multi-window support * feat: starter_app 1.2.1 * chore: move brick to its own repo & go back to old pana action * Expand remaining part of row in MacosListTile (#265) * Expand remaining part of row https://github.com/GroovinChip/macos_ui/issues/264 * Increment to version 1.4.2 * End sidebar (#267) * chore: add missing trailing comma * chore: improve MacosIconButton animation curve * chore: remove false_secrets from pubspec.yaml * feat: end sidebar Also fixes the tests portion of the pr_prelaunch_tasks script * feat: add "isEndSidebarShown" to MacosWindowScope * fix: Correct the placement of the leading widget in disclosure sidebar items (#272) * chore: Update changelog * 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 * Update Actions (#279) * Update flutter_analysis.yml * Update dart_code_metrics.yaml * Update gh_pages.yml * Update pana_analysis.yml * Update codecov.yaml * fix syntax issue * Version 1.6.0 - `MacosTabView` & `MacosSegmentedControl` (#273) * feat: MacosTabView & MacosSegmentedControl * chore: fixup scripts * test: tests for segmented control & tab view * chore: remove unused code * chore: run flutter format . * chore: bump code metrics * docs: dartdoc updates * docs: fix a documentation error * chore: run flutter format . * refactor: make active property of MacosTab optional, since it is handled via MacosSegmentedControl * chore: fix import change * refactor: change colors to match default native app design * docs: update tab view screenshot in readme * chore: update README example for MacosTabView * chore: fix typo in MacosSegmentedControl * chore: fix typo in MacosSegmentedControl docstring * test: remove explicitly setting active property of MacosTabs Co-authored-by: Minas Giannekas * Version 1.7.0: `MacosImageIcon` & sidebar updates (#274) * feat: MacosTabView & MacosSegmentedControl * chore: fixup scripts * test: tests for segmented control & tab view * chore: remove unused code * chore: run flutter format . * chore: bump code metrics * docs: dartdoc updates * docs: fix a documentation error * chore: run flutter format . * feat: MacosImageIcon & sidebar updates * 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 * refactor: make active property of MacosTab optional, since it is handled via MacosSegmentedControl * chore: fix import change * refactor: change colors to match default native app design * docs: update tab view screenshot in readme * chore: fix README * test: fix date picker test Co-authored-by: Minas Giannekas * chore: fix typo in pr template * feat: gh action to auto-generate releases on push to stable * chore: update release action with latest from stable * feat: add action to publish to pub * docs: update readme * Addresses #237 * Adds MacosColorWell to selectors section * Adds code snippets for date & time pickers * fix: 1.7.1 Fixes issue where end sidebar window breakpoint wasn’t being respected * Tab view padding (#285) * fix: use prepared title wrapped with a DefaultTextStyle instead of the raw title (#289) * chore: fix typo Closes #290 * feat: add `backgroundColor` to `MacosSheet` (#291) * chore: fix Flutter 3.3 warnings * fix: address ScrollController bug in MacosPopupButton (#300) * fix(tests): account for Jan -> Dec & Dec -> Jan date_picker_test.dart was failing due to not accounting for going from January to December and vice-versa. * fix(plugin): Ensure the native color panel releases when closed * Update flutter_analysis.yml Closes #334 * Various bug fixes & minor updates (#338) * Macos slider (#337) * chore: run flutter format . * chore: fix analysis * chore: Bump version and update CHANGELOG.md * chore: Update images to self taken ones as MacOS images are outdated * fix: fix position offset by a small value * fix: PR review feedback * Update lib/src/indicators/slider.dart --------- Co-authored-by: Reuben Turner * Adds `intialDate` to `MacosDatePicker` (#329) * Adds `intialDate` to `MacosDatePicker` * Bumps `macos_ui` version to `1.7.7` * Apply suggestions from code review * spelling correction --------- Co-authored-by: Reuben Turner --------- Co-authored-by: Minas Giannekas Co-authored-by: Jon Saw Co-authored-by: Minas Giannekas Co-authored-by: st merlhin <77164238+stMerlHin@users.noreply.github.com> Co-authored-by: jtdLab <72231111+jtdLab@users.noreply.github.com> Co-authored-by: Norbert Kozsir Co-authored-by: Elijah Luckey * chore: update gitignore * chore: update pr template * chore: update contributing.md & remove scripts * feat: update `flutter_analysis` workflow (#356) * attempt to automate dartfmt on pr's * finish the if condition * add some output messages * docs: update README Closes #352 * docs(ToolBar): update dartdocs Closes #351 * Bottom resizable pane (#349) * add bottom_resizable_pane.dart file * add test for BottomResizablePane * incremented the package version as appropriate and updated CHANGELOG.md * include the bottomResizable pane widget on the example app (ButtonsPage) and exports the file * makes ResizablePane horizontally draggable * update doc comments * include breaking change detail in CHANGELOG.md file * update ResizablePane test * remove bottom_resizable_pane.dart file and his test file * update buttons_page.dart file in example app * fix lints * run dart format * set the correct parameter name of resizable pane instance in example's ButtonsPage * Apply suggestions from code review * tweak changelog * dartfmt --------- Co-authored-by: Reuben Turner * docs: add usage note regarding Flutter channel Closes #362 * chore(actions): update `flutter_analysis.yaml` Closes #365 * fix: SearchField overlay actions are not performed (#357) * fix: SearchField overlay actions are not performed See #348 for more details * fix: run flutter pub get * fix: add missing MacosSearchField test * formatting tweak --------- Co-authored-by: Reuben Turner * address lints * chore(actions): use master channel * disable pana check on customer_testing branch * chore(actions): job-level "if" * chore(actions): do not run pana action for customer_testing branch --------- Co-authored-by: Minas Giannekas Co-authored-by: Jon Saw Co-authored-by: Minas Giannekas Co-authored-by: st merlhin <77164238+stMerlHin@users.noreply.github.com> Co-authored-by: jtdLab <72231111+jtdLab@users.noreply.github.com> Co-authored-by: Norbert Kozsir Co-authored-by: Elijah Luckey Co-authored-by: FelixMethe <42588649+FelixMethe@users.noreply.github.com> * When toolbar item is clicked, first pop the route and then call its callback - Fixes #346 (#381) * fix: when a toolbar item is clicked, remove route first and then call its callback - fixes #346 * chore: update version and changelog * feat: add support for `routerConfig` to `MacosApp.router` (#390) * feat: add support for routerConfig to MacosApp.router * remove required keywords in constructor * rm ! in _buildMacosApp * Update CHANGELOG.md Co-authored-by: Reuben Turner --------- Co-authored-by: Reuben Turner * DCM lint updates and related fixes (#393) * lints: add `double-literal-format` lint and fixes * lints: add `prefer-first`, `prefer-last`, and `prefer-immediate-return` Includes fixes * chore: remove a redundant `async` * fix: avoid non-const or final global state * lints: add always-remove-listener and fix * lints: add `avoid-unnecessary-setstate` and fixes * lints: add `avoid-wrapping-in-padding` and fixes * lints: add `prefer-const-border-radius` and fixes * lints: add `prefer-correct-edge-insets-constructor` and fixes * lints: add `use-setstate-synchronously` and fixes * lints: remove `number-of-parameters` * fix(MacosTextField): remove FocusNode listener instead of disposing FocusNode * chore(actions): split out tests into their own workflow * Fix the online gallery link * Fix for invalid dates (#402) * Revert "Merge branch 'customer_testing' into dev" This reverts commit ebf1d069238a468ba1f22d053ee0781239cf8db2, reversing changes made to a52b616f14b502de236c3ed0fb622e6abfb52b1c. * Fix: use the `sidebar` and `endSidebar` key parameter (#400) * Fix: use the `sidebard` and `endSidebar` key parameter - pass sidebar key to the sidebar root widget (`AnimatedPositioned`) - create a new sidebar `ScrollController` when the key is change * Increment version and update changelog * Apply suggestions from code review * Update pubspec.lock file in example --------- Co-authored-by: Reuben Turner * Migrate to macos_window_utils (#377) * chore: bump Dart SDK version * add `.vscode/settings.json` to .gitignore * feat: add macos_window_utils * feat: wrap side in example with `TransparentMacOSSidebar` * feat: enable wallpaper tinting on content area * feat: adjust macOS window brightness depending on theme * feat: add a way to disable wallpaper tinting Wallpaper tinting is now also disabled whenever an overlay filter is used. * refactor: refactor wallpaper tinted area * fix: fix wallpaper tinting override being applied on overlay filter rebuild * fix: do not override wallpaper tinting if no `WallpaperTintingSettingsCubit` is found * refactor: stop relying on exceptions to check if `WallpaperTintingSettingsCubit` exists in widget tree * change: switch from BLoC to global wallpaper tinting settings * chore: remove unused imports * change: make canvas color mimic `NSWindow.windowBackgroundColor` * change: make wallpaper tinted area rebuild on layout change * feat: make toolbar wallpaper-tinted * doc: document `disableWallpaperTinting` * doc: improve documentation of `disableWallpaperTinting` Add link to #16296 and clarify that disabling wallpaper tinting is meant to be a temporary solution. * doc: document wallpaper tinted area * doc: document wallpaper tinting override * doc: document wallpaper tinting settings builder * doc: document wallpaper tinting settings data * change: make `numberOfWallpaperTintingOverrides` private * doc: document global wallpaper tinting settings * doc: fix typo * refactor: refactor wallpaper tinted area * feat: add `insertRepaintBoundary` property * change: enable `insertRepaintBoundary` in toolbar * change: enable `insertRepaintBoundary` in scaffold * change: export wallpaper tinted area * feat: enable wallpaper tinting on end sidebar * feat: add `sidebarState` property to window * fix: fix background color of end sidebar not matching canvas color when theme brightness does not match platform brightness * fix: fix wallpaper tinting override using `deactivate` instead of `dispose` * merge * change: comment out swift code that hides the toolbar in fullscreen mode * change: upgrade to macos_window_utils ^1.1.1 * feat: migrate window delegate to macos_window_utils * change: remove commented-out and unused Swift code * fix: fix sliver toolbar not having a backdrop filter * docs: document toolbar's `isVisible` property * docs: document sliver toolbar's `isVisible` property * docs: document sliver toolbar page's `isVisible` property * docs: document `_WallpaperTintedAreaOrBlurFilter` * refactor: remove unused `key` parameter * refactor: remove unused import * readme: update “Modern window look” * change: bump version * changelog: add entry for version 2.0.0 * changelog: add migration hint * merge: toolbar * fix: remove duplicate `debugFillProperties` * refactor: rename `pages` to `pageBuilders` * fix: remove unused import * fix: remove unused imports * change: remove unnecessary `NSWindowDelegate` from `MainFlutterWindow` * change: rename `isVisible` property to `allowWallpaperTintingOverrides` for toolbar * change: rename `isVisible` property to `allowWallpaperTintingOverrides` for sliver toolbar * fix: fix typo in comment * fix: fix `disableWallpaperTinting` having no effect * change: disable `VisualEffectSubviewContainer` when disabling wallpaper tinting in window * fix: fix some unit tests not passing Some unit tests were failing with the following message: “A Timer is still pending even after the widget tree was disposed.” The cause of that was that `VisualEffectSubviewContainer`, or more precisely `VisualEffectSubviewContainerWithGlobalKey` is using a timer to update its visual effect subview outside of the widget's `build` method. The issue is fixed by either disabling wallpaper tinting in the window (and therefore eliminating the use of `VisualEffectSubviewContainer`) or, in cases where that was impossible (such as the sidebar) running `await tester.pump(Duration.zero);` to allow the timer to complete. * remove unused import * change default canvas color `NSColor.windowBackgroundColor` was found to be inaccurate. The color has instead been changed to a color that was captured using the Digital Color Meter. * upgrade to macos_window_utils 1.1.2 * update “Modern window look” in readme for use with macos_window_utils 1.1.2 * change version to 2.0.0-beta.1 Co-authored-by: Reuben Turner * add code formatting to documentation Highlighted `macos_ui` as a code element. Co-authored-by: Reuben Turner * add missing comma to documentation Co-authored-by: Reuben Turner * change version in changelog entry to 2.0.0-beta.1 Co-authored-by: Reuben Turner * change version in pubspec.lock * format `window.dart` * replace `Colors.transparent` with `MacosColors.transparent` * document `WallpaperTintingSettingsBuilder` * document `WallpaperTintedArea` * implement `MacosWindowUtilsConfig` * export `src/macos_window_utils_config.dart` * use `MacosWindowUtilsConfig` in example * document `MacosWindowUtilsConfig` constructor * rename `_initMacosWindowUtils` to `_configureMacosWindowUtils` in example * document `MacosWindowUtilsConfig` usage in readme * replace opacity widget with repaint boundary * improve documentation for `sidebarState` * improve `MacosWindowUtilsConfig` documentation * fix inconsistencies introduced by merging * Update example/lib/main.dart * remove ”do” prefix from field names in `MacosWindowUtilsConfig` --------- Co-authored-by: Reuben Turner * support flutter 3.10 minimum dart3 (#426) * Update dart_code_metrics.yaml * Update dart_code_metrics.yaml * Update dart_code_metrics.yaml * Fix EnumProperty test. (#419) Fix test. Co-authored-by: Reuben Turner * update dependencies * update changelog * fix dart version constraint * Rewritten `MacosSwitch` (#409) * feat(controls): rewrite `MacosSwitch` * feat(gallery): demonstrate new `MacosSwitch` * chore: update version & changelog * chore: update DCM * chore: include `MacosColor` updates in changelog * docs(MacosSwitch): update docs * chore: some cleanup * test: fix tests * docs: update readme * fix: UX of the click on the calendar elements in `MacosDatePicker` (#417) fix: UX of the click on the calendar elements * fix minor formatting fix * feat: Added support for `startWeekOnMonday` to `MacosDatePicker` (#414) * feat: Added support for `startWeekOnMonday` to `MacosDatePicker` * Update lib/src/selectors/date_picker.dart --------- Co-authored-by: Reuben Turner * feat: Added support for `dateFormat` to `MacosDatePicker` (#415) Co-authored-by: Reuben Turner * feat: Added support for `weekdayAbbreviations` and `monthAbbreviations` to `MacosDatePicker` (#416) * fix mangled conflict resolution part 1 * fix mangled PR conflict resolution part 2 * fix: remove missed state property --------- Co-authored-by: GroovinChip * fix: `ToolBar` title not avoiding traffic lights when no sidebar is present (#441) * fix: `ToolBar` not avoiding traffic lights when no sidebar is present * tweak changelog * feat: implement `ControlSize` for `PushButton` (#447) * feat: support `ControlSize` enum in `PushButton` * chore: clean up code * chore: update version & changelog * chore: adjust border radius for regular control size * fix: border radius no longer looks choppy * improve: disabled background color * refactor: `isSecondary` -> `secondary` * chore: update changelog * docs: update top-level `PushButton` dartdoc * docs: update readme * chore: migrate to DCM Teams * fix: dcm action * fix: push button tests * docs: update readme * fix: checkbox appearance (#448) * fix: checkbox appearance * fix: checkbox tests * Reorganize gallery, add `MacosTypography.of(context)`, and update `MacosAlertDialog` (#451) * move colors page out of disclosure & remove disclosure * feat: reorganize gallery * further refine buttons page * fix app icon and button sizes in `MacosAlertDialog` * update version, readme, & changelog * More gallery improvements (#457) * chore: recreate native app * chore: use modern app icon * chore: improve indicators page * chore: improve fields page * chore: improve selectors page * Typography improvments (#459) * fix: use `MacosColors.labelColor` as default typography color * chore: more accurate job step names for DCM action * fix gallery typography bold values * feat: Add `MacosFontWeight` so that Apple specific weights can be used Also updates the gallery to use the new weights * Non-scrollable `ResizablePane` (#420) * chore: add missing trailing commas & format * Fix broken web support, platform theming issues, and typography updates (#460) * start fixing gallery for web * additional platform safety, theme, and typographic fixes * update version * gallery: remove commented & deprecated code from `main.dart` * 2.0: Readme updates (#461) * docs: update readme screenshots * chore: update gallery to match readme * fix: `HelpButton` sizing * chore: deprecate `RelevanceIndicator` * update version, changelog, & remove `RelevanceIndicator` from gallery & tests * feat: version `2.0.0` (#462) * Merges beta changelog entries into a singular `2.0.0` entry * Updates the version to `2.0.0` * Fixes the codecov badge * Update CHANGELOG.md --------- Co-authored-by: Minas Giannekas Co-authored-by: Jon Saw Co-authored-by: Minas Giannekas Co-authored-by: st merlhin <77164238+stMerlHin@users.noreply.github.com> Co-authored-by: jtdLab <72231111+jtdLab@users.noreply.github.com> Co-authored-by: Norbert Kozsir Co-authored-by: Elijah Luckey Co-authored-by: FelixMethe <42588649+FelixMethe@users.noreply.github.com> Co-authored-by: Erick Ghaumez Co-authored-by: Elias Yishak <42216813+eliasyishak@users.noreply.github.com> Co-authored-by: Zemlaynikin Max Co-authored-by: Adrian Samoticha <86920182+Adrian-Samoticha@users.noreply.github.com> Co-authored-by: Michelle Raouf <72160249+the-best-is-best@users.noreply.github.com> Co-authored-by: Bernardo Ferrari Co-authored-by: Radosław Kłos --- .github/workflows/dart_code_metrics.yaml | 16 +- .github/workflows/flutter_analysis.yml | 4 - .github/workflows/test.yaml | 19 + CHANGELOG.md | 60 + README.md | 244 ++-- analysis_options.yaml | 14 +- example/.metadata | 24 +- example/lib/main.dart | 173 ++- example/lib/pages/buttons_page.dart | 1077 +++++++++++------ example/lib/pages/colors_page.dart | 23 +- example/lib/pages/dialogs_page.dart | 188 ++- example/lib/pages/fields_page.dart | 53 +- example/lib/pages/indicators_page.dart | 135 ++- example/lib/pages/resizable_pane_page.dart | 86 ++ example/lib/pages/selectors_page.dart | 67 +- example/lib/pages/sliver_toolbar_page.dart | 23 +- example/lib/pages/tabview_page.dart | 25 +- example/lib/pages/toolbar_page.dart | 4 +- example/lib/pages/typography_page.dart | 394 ++++++ example/lib/platform_menus.dart | 52 + example/lib/widgets/widget_text_title1.dart | 30 + example/lib/widgets/widget_text_title2.dart | 30 + .../Flutter/GeneratedPluginRegistrant.swift | 6 + example/macos/Podfile | 2 +- example/macos/Podfile.lock | 21 +- .../macos/Runner.xcodeproj/project.pbxproj | 9 +- .../AppIcon.appiconset/Contents.json | 64 +- .../AppIcon.appiconset/app_icon_1024.png | Bin 46993 -> 102994 bytes .../AppIcon.appiconset/app_icon_128.png | Bin 3276 -> 5680 bytes .../AppIcon.appiconset/app_icon_16.png | Bin 1429 -> 520 bytes .../AppIcon.appiconset/app_icon_256 1.png | Bin 0 -> 14142 bytes .../AppIcon.appiconset/app_icon_256.png | Bin 5933 -> 14142 bytes .../AppIcon.appiconset/app_icon_32 1.png | Bin 0 -> 1066 bytes .../AppIcon.appiconset/app_icon_32.png | Bin 1243 -> 1066 bytes .../AppIcon.appiconset/app_icon_512 1.png | Bin 0 -> 36406 bytes .../AppIcon.appiconset/app_icon_512.png | Bin 14800 -> 36406 bytes .../AppIcon.appiconset/app_icon_64.png | Bin 1874 -> 2218 bytes .../macos/Runner/DebugProfile.entitlements | 2 + example/macos/Runner/MainFlutterWindow.swift | 75 +- example/macos/RunnerTests/RunnerTests.swift | 12 + example/pubspec.lock | 267 +++- example/pubspec.yaml | 8 +- lib/macos_ui.dart | 8 +- lib/src/buttons/back_button.dart | 2 +- lib/src/buttons/checkbox.dart | 63 +- lib/src/buttons/disclosure_button.dart | 2 +- lib/src/buttons/help_button.dart | 20 +- lib/src/buttons/icon_button.dart | 2 +- lib/src/buttons/popup_button.dart | 7 +- lib/src/buttons/pulldown_button.dart | 7 +- lib/src/buttons/push_button.dart | 180 ++- lib/src/buttons/segmented_control.dart | 4 +- lib/src/buttons/switch.dart | 663 +++++++++- .../buttons/toolbar/toolbar_icon_button.dart | 2 +- lib/src/dialogs/macos_alert_dialog.dart | 49 +- lib/src/enums/control_size.dart | 25 + lib/src/fields/text_field.dart | 12 +- lib/src/indicators/progress_indicators.dart | 4 +- lib/src/indicators/relevance_indicator.dart | 1 + lib/src/indicators/slider.dart | 17 +- lib/src/layout/resizable_pane.dart | 68 +- lib/src/layout/scaffold.dart | 62 +- lib/src/layout/scrollbar.dart | 2 +- lib/src/layout/sidebar/sidebar_items.dart | 9 +- lib/src/layout/toolbar/sliver_toolbar.dart | 37 +- lib/src/layout/toolbar/toolbar.dart | 187 ++- lib/src/layout/toolbar/toolbar_divider.dart | 10 +- lib/src/layout/toolbar/toolbar_popup.dart | 5 +- lib/src/layout/wallpaper_tinted_area.dart | 156 +++ .../global_wallpaper_tinting_settings.dart | 46 + .../wallpaper_tinting_override.dart | 41 + .../wallpaper_tinting_settings_builder.dart | 46 + .../wallpaper_tinting_settings_data.dart | 38 + lib/src/layout/window.dart | 389 ++++-- lib/src/macos_app.dart | 25 +- lib/src/macos_window_utils_config.dart | 120 ++ lib/src/selectors/color_well.dart | 2 +- lib/src/selectors/date_picker.dart | 261 ++-- lib/src/theme/icon_theme.dart | 2 +- lib/src/theme/macos_colors.dart | 71 ++ lib/src/theme/macos_theme.dart | 21 +- lib/src/theme/overlay_filter.dart | 67 +- lib/src/theme/tooltip_theme.dart | 2 +- lib/src/theme/typography.dart | 184 ++- lib/src/utils.dart | 55 +- pubspec.lock | 232 +--- pubspec.yaml | 10 +- test/buttons/back_button_test.dart | 1 + test/buttons/checkbox_test.dart | 2 +- test/buttons/help_button_test.dart | 1 + test/buttons/icon_button_test.dart | 1 + test/buttons/pulldown_button_test.dart | 2 +- test/buttons/push_button_test.dart | 11 +- test/buttons/radio_button_test.dart | 1 + test/buttons/switch_test.dart | 2 + test/indicators/relevance_indicator_test.dart | 31 - test/indicators/scrollbar_test.dart | 4 +- test/indicators/slider_test.dart | 14 +- test/layout/macos_list_tile_test.dart | 1 + test/layout/resizeable_pane_test.dart | 242 +++- test/layout/sliver_toolbar_test.dart | 10 + test/layout/window_test.dart | 27 + test/selectors/date_picker_test.dart | 211 ++++ test/theme/help_button_theme_test.dart | 5 +- test/theme/icon_button_theme_test.dart | 1 + test/theme/icon_theme_test.dart | 3 +- test/theme/popup_button_theme_test.dart | 5 +- test/theme/pulldown_button_theme_test.dart | 7 +- test/theme/push_button_theme_test.dart | 13 +- test/theme/search_field_theme_test.dart | 1 + 110 files changed, 5245 insertions(+), 1774 deletions(-) create mode 100644 .github/workflows/test.yaml create mode 100644 example/lib/pages/resizable_pane_page.dart create mode 100644 example/lib/pages/typography_page.dart create mode 100644 example/lib/platform_menus.dart create mode 100644 example/lib/widgets/widget_text_title1.dart create mode 100644 example/lib/widgets/widget_text_title2.dart create mode 100644 example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256 1.png create mode 100644 example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32 1.png create mode 100644 example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512 1.png create mode 100644 example/macos/RunnerTests/RunnerTests.swift create mode 100644 lib/src/enums/control_size.dart create mode 100644 lib/src/layout/wallpaper_tinted_area.dart create mode 100644 lib/src/layout/wallpaper_tinting_settings/global_wallpaper_tinting_settings.dart create mode 100644 lib/src/layout/wallpaper_tinting_settings/wallpaper_tinting_override.dart create mode 100644 lib/src/layout/wallpaper_tinting_settings/wallpaper_tinting_settings_builder.dart create mode 100644 lib/src/layout/wallpaper_tinting_settings/wallpaper_tinting_settings_data.dart create mode 100644 lib/src/macos_window_utils_config.dart delete mode 100644 test/indicators/relevance_indicator_test.dart diff --git a/.github/workflows/dart_code_metrics.yaml b/.github/workflows/dart_code_metrics.yaml index a22ecac7..03336a6a 100644 --- a/.github/workflows/dart_code_metrics.yaml +++ b/.github/workflows/dart_code_metrics.yaml @@ -8,11 +8,15 @@ jobs: steps: - uses: actions/checkout@v3 - - name: Run Dart Code Metrics - uses: dart-code-checker/dart-code-metrics-action@v3 + - name: Install Flutter + uses: subosito/flutter-action@v2 + with: + channel: stable + + - name: Set Up DCM + run: flutter pub get + - uses: CQLabs/setup-dcm@v1.0.0 with: github_token: ${{ secrets.GITHUB_TOKEN }} - pull_request_comment: true - fatal_warnings: true - fatal_performance: true - fatal_style: true + + - run: dcm analyze --ci-key="${{ secrets.DCM_CI_KEY }}" --email="${{ secrets.DCM_EMAIL }}" lib diff --git a/.github/workflows/flutter_analysis.yml b/.github/workflows/flutter_analysis.yml index 4a4b58ff..fba04aa4 100644 --- a/.github/workflows/flutter_analysis.yml +++ b/.github/workflows/flutter_analysis.yml @@ -34,7 +34,3 @@ jobs: - name: Analyze code run: flutter analyze --fatal-infos . - - - name: Test code - run: flutter test - diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml new file mode 100644 index 00000000..8c64e72e --- /dev/null +++ b/.github/workflows/test.yaml @@ -0,0 +1,19 @@ +name: Flutter Analysis +on: [pull_request, workflow_dispatch] + +jobs: + package-analysis: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + - name: Install Flutter + uses: subosito/flutter-action@v2 + with: + channel: stable + + - name: Install dependencies + run: flutter pub get + + - name: Test code + run: flutter test diff --git a/CHANGELOG.md b/CHANGELOG.md index 50742fa2..0c59bae9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,63 @@ +## [2.0.0] +### 🚨 Breaking Changes 🚨 +* `macos_ui` has been migrated to utilize [macos_window_utils](https://pub.dev/packages/macos_window_utils) under the hood, which provides the following benefits: + * Window animation smoothness is drastically improved, particularly when miniaturizing and deminiaturizing the application window. + * Some visual artifacts that occurred while the window was being (de)miniaturized (such as the application's shadow going missing) no longer occur. + * The sidebar remains transparent when the app's brightness setting mismatches the OS setting. + * Wallpaper tinting is now supported. + * To migrate an existing application, please refer to the “Modern window look” section in the README. + +* Support for Flutter 3.10 and Dart 3 +* `PushButton` has been updated to support the `ControlSize` enum. + * The `buttonSize` property has been changed to `controlSize`. + * Buttons can now be any of the following sizes: mini, small, regular, or large. +* `PushButton.isSecondary` is now `PushButton.secondary`. +* `MacosAlertDialog`: `primaryButton` and `secondaryButton` are now declared to be of type `PushButton`. +* `RelevanceIndicator` has been deprecated +* `MacosTypography` white and black are now factory constructors called `darkOpaque()` and `lightOpaque()` to reflect + Apple's naming conventions. + +### ✨ New ✨ +* `MacosSwitch` has been completely rewritten and now matches the native macOS switch in appearance and behavior. +* A `ControlSize` enum has been introduced, which will allow widgets to more closely match their native counterparts. +* `MacosTypography` + * You can now call `MacosTypography.of(context)` as a shorthand for retrieving the typography used in your `MacosTheme`. + * `MacosFontWeight` allows using Apple-specific font weights like `w510`, `w590`, and `w860`. +* Localization + * Added support for `weekdayAbbreviations` and `monthAbbreviations` to `MacosDatePicker`. + * Added support for `dateFormat` to `MacosDatePicker`. + * Added support for `startWeekOnMonday` to `MacosDatePicker`. + +### 🔄 Updated 🔄 +* `MacosColor` has been updated with some previously missing elements. +* `PushButton` + * Now uses the correct `body` text style instead of the incorrect `headline` +* `PushButton`'s secondary and disabled colors more closely match their native counterparts. +* `MacosCheckbox` appearance more closely matches its native counterpart. +* `MacosAlertDialog` + * `primaryButton` and `secondaryButton` are now required to have `controlSize`s of `ControlSize.large`. + * Docs now suggest that `appIcon` should be of size 64x64. +* `Toolbar` now uses the correct `title3` text style instead of the incorrect `headline` +* `MacosTheme` sets the global typography more efficiently +* `HelpButton` now sizes itself according to specification +* `ResizablePane` can now disallow the usage of its internal scrollbar via the `ReziablePane.noScrollBar` constructor. + +### 🛠️ Fixed 🛠️ +* Clicking on the calendar elements in `MacosDatePicker` has better UX +* `ToolBar`s in use where a `SideBar` is not present will now have their title's avoid the traffic lights (native window controls). +* `MacosTypography.darkOpaque()` and `MacosTypography.lightOpaque()` now conform to specification by using `MacosColors.labelColor` +* Ensure builds targeting web do not utilize any `macos_window_utils` code +* Ensure builds targeting web are themed correctly + +## [1.12.5] +* Fixed a bug where the `Sidebar.key` parameter wasn't used, which caused certain layouts to be unachievable. + +## [1.12.4] +* Default the `_selectedDay` state variable to be 1 when selecting the previous/next month from widget to ensure new date is valid for `_formatAsDateTime()` method (https://github.com/flutter/flutter/issues/123669 & https://github.com/macosui/macos_ui/pull/402) + +## [1.12.3] +* Added support for `routerConfig` to `MacosApp.router`. ([#388](https://github.com/macosui/macos_ui/issues/388)) + ## [1.12.2] * Fixed a bug where clicking on a overflowed toolbar item with a navigation callback wouldn't work ([#346](https://github.com/GroovinChip/macos_ui/issues/346)). diff --git a/README.md b/README.md index b0925894..23a54c9a 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ Flutter widgets and themes implementing the current macOS design language. -Check out our **interactive widget gallery** online at https://groovinchip.github.io/macos_ui/#/ +Check out our **interactive widget gallery** online at https://macosui.github.io/macos_ui/#/ Guides, codelabs, and other documentation can be found at https://macosui.dev @@ -12,9 +12,9 @@ Guides, codelabs, and other documentation can be found at https://macosui.dev [![Flutter Analysis](https://github.com/GroovinChip/macos_ui/actions/workflows/flutter_analysis.yml/badge.svg?branch=stable)](https://github.com/GroovinChip/macos_ui/actions/workflows/flutter_analysis.yml) [![Pana Analysis](https://github.com/GroovinChip/macos_ui/actions/workflows/pana_analysis.yml/badge.svg)](https://github.com/GroovinChip/macos_ui/actions/workflows/pana_analysis.yml) [![codecov](https://github.com/GroovinChip/macos_ui/actions/workflows/codecov.yaml/badge.svg)](https://github.com/GroovinChip/macos_ui/actions/workflows/codecov.yaml) -[![codecov](https://codecov.io/gh/GroovinChip/macos_ui/branch/dev/graph/badge.svg?token=1SZGEVVMCH)](https://codecov.io/gh/GroovinChip/macos_ui) +[![codecov](https://codecov.io/gh/macosui/macos_ui/branch/dev/graph/badge.svg?token=1SZGEVVMCH)](https://codecov.io/gh/macosui/macos_ui) - + ## 🚨 Usage notes ### Flutter channel @@ -23,10 +23,13 @@ Guides, codelabs, and other documentation can be found at https://macosui.dev ### Platform Compatibility pub.dev shows that `macos_ui` only supports macOS. This is because `macos_ui` calls some native code, and therefore -specifies macOS as a plugin platform in the `pubspec.yaml` file. `macos_ui` _will_ work on any platform that -Flutter supports, **but you will get best results on macOS**. +specifies macOS as a plugin platform in the `pubspec.yaml` file. + +`macos_ui` _technically_ will work on any platform that +Flutter supports, **but you will get best results on macOS**. non-macOS platform support is ***not*** guaranteed. The features of `macos_ui` that will _not_ work on platforms other than macOS due to calling native code are: +* Anything related to `macos_window_utils` * The `MacosColors.controlAccentColor()` function * The `MacosColorWell` widget @@ -43,9 +46,8 @@ should avoid allowing your application window to be resized below the height of
Contributing & Resources -- [macos_ui](#macos_ui) - - [Contributing](#contributing) - - [Resources](#resources) +- [Contributing](#contributing) +- [Resources](#resources)
@@ -111,7 +113,6 @@ should avoid allowing your application window to be resized below the height of - [Level Indicators](#level-indicators) - [CapacityIndicator](#capacityindicator) - [RatingIndicator](#ratingindicator) - - [RelevanceIndicator](#relevanceindicator)
@@ -131,9 +132,9 @@ should avoid allowing your application window to be resized below the height of ## Resources +- [macOS Sonoma Figma kit](https://www.figma.com/file/M6K5L3GK0WJh6pnsASyVeE/macOS-Big-Sur-UI-Kit?node-id=1%3A2) +- [macOS Human Interface Guidelines](https://developer.apple.com/design/human-interface-guidelines/designing-for-macos) - [macOS Design Resources](https://developer.apple.com/design/resources/) -- [macOS Human Interface Guidelines](https://developer.apple.com/design/human-interface-guidelines/macos) -- [macOS Big Sur Figma kit](https://www.figma.com/file/M6K5L3GK0WJh6pnsASyVeE/macOS-Big-Sur-UI-Kit?node-id=1%3A2) # Layout @@ -158,7 +159,7 @@ A sidebar enables app navigation and provides quick access to top-level collecti Sidebars may be placed at the left or right of your app. To place a sidebar on the left, use the `MacosWindow.sidebar` property. To place a sidebar on the right, use the `MacosWindow.endSidebar` property. - + Example usage: @@ -217,105 +218,50 @@ covering the entire window. To push a route outside a `MacosScaffold` wrapped in See the documentation for customizations and `ToolBar` examples. - - - + - + ## Modern window look -A new look for macOS apps was introduced in Big Sur (macOS 11). To match that look -in your Flutter app, like our screenshots, your `macos/Runner/MainFlutterWindow.swift` -file should look like this: - -```swift -import Cocoa -import FlutterMacOS +A new look for macOS apps was introduced in Big Sur (macOS 11). To match that look in your Flutter app, macos_ui relies on [macos_window_utils](https://pub.dev/packages/macos_window_utils), which requires a minimum macOS deployment target of 10.14.6. Therefore, make sure to open the `macos/Runner.xcworkspace` folder of your project using Xcode and search for `Runner.xcodeproj`. Go to `Info` > `Deployment Target` and set the `macOS Deployment Target` to `10.14.6` or above. Then, open your project's `Podfile` (if it doesn't show up in Xcode, you can find it in your project's `macos` directory via VS Code) and set the minimum deployment version in the first line to `10.14.6` or above: -class BlurryContainerViewController: NSViewController { - let flutterViewController = FlutterViewController() - - init() { - super.init(nibName: nil, bundle: nil) - } +```podspec +platform :osx, '10.14.6' +``` - required init?(coder: NSCoder) { - fatalError() - } +You may also need to open up your app's `Runner.xcodeproj` in XCode and set the minimum deployment version there. - override func loadView() { - let blurView = NSVisualEffectView() - blurView.autoresizingMask = [.width, .height] - blurView.blendingMode = .behindWindow - blurView.state = .active - if #available(macOS 10.14, *) { - blurView.material = .sidebar - } - self.view = blurView - } +Now, configure your window inside your `main()` as follows: - override func viewDidLoad() { - super.viewDidLoad() +```dart +/// This method initializes macos_window_utils and styles the window. +Future _configureMacosWindowUtils() async { + const config = MacosWindowUtilsConfig( + toolbarStyle: NSWindowToolbarStyle.unified, + ); + await config.apply(); +} - self.addChild(flutterViewController) +void main() async { + await _configureMacosWindowUtils(); - flutterViewController.view.frame = self.view.bounds - flutterViewController.backgroundColor = .clear // **Required post-Flutter 3.7.0** - flutterViewController.view.autoresizingMask = [.width, .height] - self.view.addSubview(flutterViewController.view) - } + runApp(const YourAppHere()); } +``` -class MainFlutterWindow: NSWindow, NSWindowDelegate { - override func awakeFromNib() { - delegate = self - let blurryContainerViewController = BlurryContainerViewController() - let windowFrame = self.frame - self.contentViewController = blurryContainerViewController - self.setFrame(windowFrame, display: true) - - if #available(macOS 10.13, *) { - let customToolbar = NSToolbar() - customToolbar.showsBaselineSeparator = false - self.toolbar = customToolbar - } - self.titleVisibility = .hidden - self.titlebarAppearsTransparent = true - if #available(macOS 11.0, *) { - // Use .expanded if the app will have a title bar, else use .unified - self.toolbarStyle = .unified - } - - self.isMovableByWindowBackground = true - self.styleMask.insert(NSWindow.StyleMask.fullSizeContentView) - - self.isOpaque = false - self.backgroundColor = .clear - - RegisterGeneratedPlugins(registry: blurryContainerViewController.flutterViewController) - - super.awakeFromNib() - } - - func window(_ window: NSWindow, willUseFullScreenPresentationOptions proposedOptions: NSApplication.PresentationOptions = []) -> NSApplication.PresentationOptions { - return [.autoHideToolbar, .autoHideMenuBar, .fullScreen] - } +Please note that if you are using a title bar (`TitleBar`) in your `MacosWindow`, you should set the `toolbarStyle` of your window to `NSWindowToolbarStyle.expanded`, in order to properly align the close, minimize, zoom window buttons: - func windowWillEnterFullScreen(_ notification: Notification) { - self.toolbar?.isVisible = false - } - - func windowDidExitFullScreen(_ notification: Notification) { - self.toolbar?.isVisible = true - } +```dart +Future _configureMacosWindowUtils() async { + const config = MacosWindowUtilsConfig( + toolbarStyle: NSWindowToolbarStyle.expanded, + ); + await config.apply(); } - ``` -See [this issue comment](https://github.com/flutter/flutter/issues/59969#issuecomment-916682559) for more details on the new look and explanations for how it works. - -Please note that if you are using a title bar (`TitleBar`) in your `MacosWindow`, you should set the `toolbarStyle` of NSWindow to `.expanded`, in order to properly align the close, minimize, zoom window buttons. In any other case, you should keep it as `.unified`. This must be set beforehand, i.e. it cannot be switched in runtime. +In any other case, you should keep it as `NSWindowToolbarStyle.unified`. ## ToolBar @@ -389,7 +335,7 @@ Other toolbar examples: - Toolbar with a pulldown button open: - + - Toolbar with title bar above (also see [the note above](#modern-window-look)): @@ -462,7 +408,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. @@ -526,9 +472,9 @@ A checkbox is a type of button that lets the user choose between two opposite st checkbox is considered on when it contains a checkmark and off when it's empty. A checkbox is almost always followed by a title unless it appears in a checklist. [Learn more](https://developer.apple.com/design/human-interface-guidelines/macos/buttons/checkboxes/) -| Off | On | Mixed | +| Unchecked | Checked | Mixed | | --------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------ | -| ![Off Checkbox](https://developer.apple.com/design/human-interface-guidelines/macos/images/CheckBoxes_Deselected.svg) | ![On Checkbox](https://developer.apple.com/design/human-interface-guidelines/macos/images/CheckBoxes_Selected.svg) | ![Mixed Checkbox](https://developer.apple.com/design/human-interface-guidelines/macos/images/CheckBoxes_Mixed.svg) | +| ![Unchecked Checkbox](https://imgur.com/Pu4EDAE.png) | ![Checked Checkbox](https://imgur.com/CB3Kmwo.png) | ![Mixed Checkbox](https://imgur.com/T44rV38.png) | Here's an example of how to create a basic checkbox: @@ -550,7 +496,7 @@ To make a checkbox in the `mixed` state, set `value` to `null`. A help button appears within a view and opens app-specific help documentation when clicked. All help buttons are circular, consistently sized buttons that contain a question mark icon. [Learn more](https://developer.apple.com/design/human-interface-guidelines/macos/buttons/help-buttons/) -![HelpButton Example](https://developer.apple.com/design/human-interface-guidelines/macos/images/buttonsHelp.png) +![HelpButton Example](https://imgur.com/DlP7uLV.png) Here's an example of how to create a help button: @@ -571,7 +517,7 @@ A radio button is a small, circular button followed by a title. Typically presen buttons provide the user a set of related but mutually exclusive choices. A radio button’s state is either on (a filled circle) or off (an empty circle). [Learn more](https://developer.apple.com/design/human-interface-guidelines/macos/buttons/radio-buttons/) -![RadioButton Preview](https://developer.apple.com/design/human-interface-guidelines/macos/images/radioButtons.png) +![RadioButton Preview](https://imgur.com/HI0eQsU.png) Here's an example of how to create a basic radio button: @@ -686,23 +632,22 @@ MacosPopupButton( ## PushButton -A push button appears within a view and initiates an instantaneous app-specific action, such as printing a document or -deleting a file. Push buttons contain text—not icons—and often open a separate window, dialog, or app so the user can +Push buttons are the standard button type in macOS. Push buttons contain text—not icons—and often open a separate window, dialog, or app so the user can complete a task. [Learn more](https://developer.apple.com/design/human-interface-guidelines/macos/buttons/push-buttons/) | Dark Theme | Light Theme | | ------------------------------------------ | ------------------------------------------ | -| | | -| | | -| | | -| | | +| | | + +ℹ️ **Note** ℹ️ +Native push buttons can be styled as text-only, text with an icon, or icon-only. Currently, text-only push buttons are supported. To create an icon-only button, use the `MacosIconButton` widget. Here's an example of how to create a basic push button: ```dart PushButton( child: Text('button'), - buttonSize: ButtonSize.large, + controlSize: ControlSize.regular, onPressed: () { print('button pressed'); }, @@ -711,32 +656,40 @@ PushButton( ## 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. [Learn more](https://developer.apple.com/design/human-interface-guidelines/macos/buttons/switches/) +A switch (also known as a toggle) is a control that offers a binary choice 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. + +The `ContolSize` enum can be passed to the `size` property to control the size of the switch. `MacosSwitch` supports the following +control sizes: +* `mini` +* `small` +* `regular` -| On | Off | +| Off | On | | ------------------------------------------ | ------------------------------------------ | -| | | +| | | Here's an example of how to create a basic toggle switch: ```dart -bool selected = false; +bool switchValue = false; MacosSwitch( - value: selected, + value: switchValue, onChanged: (value) { - setState(() => selected = value); + setState(() => switchValue = value); }, ), ``` +Learn more about switches [here](https://developer.apple.com/design/human-interface-guidelines/toggles). + ## 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. @@ -750,9 +703,7 @@ Usage: showMacosAlertDialog( context: context, builder: (_) => MacosAlertDialog( - appIcon: FlutterLogo( - size: 56, - ), + appIcon: FlutterLogo(size: 64), title: Text( 'Alert Dialog with Primary Action', style: MacosTheme.of(context).typography.headline, @@ -760,10 +711,10 @@ showMacosAlertDialog( message: Text( 'This is an alert dialog with a primary action and no secondary action', textAlign: TextAlign.center, - style: MacosTheme.of(context).typography.headline, + style: MacosTypography.of(context).headline, ), primaryButton: PushButton( - buttonSize: ButtonSize.large, + controlSize: ControlSize.large, child: Text('Primary'), onPressed: () {}, ), @@ -771,9 +722,10 @@ showMacosAlertDialog( ); ``` -![](https://imgur.com/G3dcjew.png) -![](https://imgur.com/YHtgv59.png) -![](https://imgur.com/xuBR5qK.png) +![](https://imgur.com/4zbGsFi.png) +![](https://imgur.com/5fgkRU9.png) +![](https://imgur.com/jOyJrZO.png) +![](https://imgur.com/NX9taPj.png) ## MacosSheet @@ -785,7 +737,7 @@ showMacosSheet( ); ``` -![](https://imgur.com/NV0o5Ws.png) +![](https://imgur.com/Mnw2ywm.png) # Fields @@ -872,8 +824,6 @@ Progress indicators have two distinct styles: People don't interact with progress indicators; however, they are often accompanied by a button for canceling the corresponding operation. [Learn more](https://developer.apple.com/design/human-interface-guidelines/macos/indicators/progress-indicators/) -![Progress Indicator Example](https://developer.apple.com/design/human-interface-guidelines/macos/images/ProgressIndicators_Lead.png) - ### ProgressCircle A `ProgressCircle` can be either determinate or indeterminate. @@ -919,10 +869,8 @@ indicator styles, each with a different appearance, for communicating capacity, A capacity indicator illustrates the current level in relation to a finite capacity. Capacity indicators are often used when communicating factors like disk and battery usage. [Learn more](https://developer.apple.com/design/human-interface-guidelines/macos/indicators/level-indicators#capacity-indicators) -| Continuous | Discrete | -| ---------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| ![Continuous CapacityIndicator Example](https://developer.apple.com/design/human-interface-guidelines/macos/images/indicators-continous.png) | ![Discrete CapacityIndicator Example](https://developer.apple.com/design/human-interface-guidelines/macos/images/indicators-discrete.png) | -| A horizontal translucent track that fills with a colored bar to indicate the current value. Tick marks are often displayed to provide context. | A horizontal row of separate, equally sized, rectangular segments. The number of segments matches the total capacity, and the segments fill completely—never partially—with color to indicate the current value. | + + Here's an example of how to create an interactive continuous capacity indicator: @@ -968,7 +916,7 @@ MacosSlider( A rating indicator uses a series of horizontally arranged graphical symbols to communicate a ranking level. The default symbol is a star. -![RatingIndicator Example](https://developer.apple.com/design/human-interface-guidelines/macos/images/indicator-rating.png) +![RatingIndicator Example](https://imgur.com/ySQBpL6.png) A rating indicator doesn’t display partial symbols—its value is rounded in order to display complete symbols only. Within a rating indicator, symbols are always the same distance apart and don't expand or shrink to fit the control. @@ -988,22 +936,6 @@ RatingIndicator( ) ``` -### RelevanceIndicator - -A relevance indicator communicates relevancy using a series of vertical bars. It often appears in a list of search -results for reference when sorting and comparing multiple items. [Learn more](https://developer.apple.com/design/human-interface-guidelines/macos/indicators/level-indicators#relevance-indicators) - -![RelevanceIndicator Example](https://developer.apple.com/design/human-interface-guidelines/macos/images/indicator-relevance.png) - -Here's an example of how to create a relevance indicator: - -```dart -RelevanceIndicator( - value: 15, - amount: 20, -) -``` - # Selectors ## MacosDatePicker @@ -1020,6 +952,22 @@ There are three styles of `MacosDatePickers`: calendar-like interface to select a date. * `combined`: provides both `textual` and `graphical` interfaces. +Localization of the time picker is supported by the `weekdayAbbreviations` and `monthAbbreviations` parameters (instead of e.g. standard `localizations.narrowWeekdays()` in order to match Apple's spec). +* `weekdayAbbreviations` should be a list of 7 strings, one for each day of the week, starting with Sunday +* `monthAbbreviations` should be a list of 12 strings, one for each month of the year, starting with January + +You can also define the `dateFormat` to change the way dates are displayed in the textual interface. +It takes a string of tokens (case-insensitive) and replaces them with their corresponding values. +The following tokens are supported: +* `D`: day of the month (1-31) +* `DD`: day of the month (01-31) +* `M`: month of the year (1-12) +* `MM`: month of the year (01-12) +* `YYYY`: year (0000-9999) +* Any separator between tokens is preserved (e.g. `/`, `-`, `.`) + +The default format is `M/D/YYYY`. + Example usage: ```dart MacosDatePicker( diff --git a/analysis_options.yaml b/analysis_options.yaml index d4a906dd..7ed9076e 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -5,8 +5,6 @@ linter: - use_super_parameters analyzer: - plugins: - - dart_code_metrics exclude: - test/mock_canvas.dart - test/recording_canvas.dart @@ -14,13 +12,23 @@ analyzer: dart_code_metrics: metrics: cyclomatic-complexity: 20 - number-of-parameters: 4 maximum-nesting-level: 5 metrics-exclude: - test/** - example/test/** rules: - prefer-trailing-comma + - double-literal-format + - prefer-first + - prefer-last + - prefer-immediate-return + - avoid-global-state + - always-remove-listener + - avoid-unnecessary-setstate + - avoid-wrapping-in-padding + - prefer-const-border-radius + - prefer-correct-edge-insets-constructor + - use-setstate-synchronously - member-ordering: alphabetize: false order: diff --git a/example/.metadata b/example/.metadata index 140b9294..53830e36 100644 --- a/example/.metadata +++ b/example/.metadata @@ -1,10 +1,30 @@ # This file tracks properties of this Flutter project. # Used by Flutter tool to assess capabilities and perform upgrades etc. # -# This file should be version controlled and should not be manually edited. +# This file should be version controlled. version: - revision: 4d7946a68d26794349189cf21b3f68cc6fe61dcb + revision: 796c8ef79279f9c774545b3771238c3098dbefab channel: stable project_type: app + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: 796c8ef79279f9c774545b3771238c3098dbefab + base_revision: 796c8ef79279f9c774545b3771238c3098dbefab + - platform: macos + create_revision: 796c8ef79279f9c774545b3771238c3098dbefab + base_revision: 796c8ef79279f9c774545b3771238c3098dbefab + + # User provided section + + # List of Local paths (relative to this file) that should be + # ignored by the migrate tool. + # + # Files that are not part of the templates will be ignored by default. + unmanaged_files: + - 'lib/main.dart' + - 'ios/Runner.xcodeproj/project.pbxproj' diff --git a/example/lib/main.dart b/example/lib/main.dart index 3d1a24e7..193bef1f 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -1,19 +1,38 @@ +import 'dart:io'; + import 'package:example/pages/buttons_page.dart'; import 'package:example/pages/colors_page.dart'; 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/resizable_pane_page.dart'; import 'package:example/pages/selectors_page.dart'; import 'package:example/pages/sliver_toolbar_page.dart'; import 'package:example/pages/tabview_page.dart'; import 'package:example/pages/toolbar_page.dart'; +import 'package:example/pages/typography_page.dart'; +import 'package:example/platform_menus.dart'; import 'package:flutter/cupertino.dart'; +import 'package:flutter/foundation.dart'; import 'package:macos_ui/macos_ui.dart'; import 'package:provider/provider.dart'; +import 'package:url_launcher/url_launcher.dart'; import 'theme.dart'; -void main() { +/// This method initializes macos_window_utils and styles the window. +Future _configureMacosWindowUtils() async { + const config = MacosWindowUtilsConfig(); + await config.apply(); +} + +Future main() async { + if (!kIsWeb) { + if (Platform.isMacOS) { + await _configureMacosWindowUtils(); + } + } + runApp(const MacosUIGalleryApp()); } @@ -47,68 +66,14 @@ class WidgetGallery extends StatefulWidget { } class _WidgetGalleryState extends State { - double ratingValue = 0; - double sliderValue = 0; - bool value = false; - int pageIndex = 0; late final searchFieldController = TextEditingController(); - final List pages = [ - CupertinoTabView( - builder: (_) => const ButtonsPage(), - ), - const IndicatorsPage(), - const FieldsPage(), - const ColorsPage(), - const Center( - child: MacosIcon( - CupertinoIcons.add, - ), - ), - const DialogsPage(), - const ToolbarPage(), - const SliverToolbarPage(), - const TabViewPage(), - const SelectorsPage(), - ]; - @override Widget build(BuildContext context) { return PlatformMenuBar( - menus: const [ - PlatformMenu( - label: 'macos_ui Widget Gallery', - menus: [ - PlatformProvidedMenuItem( - type: PlatformProvidedMenuItemType.about, - ), - PlatformProvidedMenuItem( - type: PlatformProvidedMenuItemType.quit, - ), - ], - ), - PlatformMenu( - label: 'View', - menus: [ - PlatformProvidedMenuItem( - type: PlatformProvidedMenuItemType.toggleFullScreen, - ), - ], - ), - PlatformMenu( - label: 'Window', - menus: [ - PlatformProvidedMenuItem( - type: PlatformProvidedMenuItemType.minimizeWindow, - ), - PlatformProvidedMenuItem( - type: PlatformProvidedMenuItemType.zoomWindow, - ), - ], - ), - ], + menus: menuBarItems(), child: MacosWindow( sidebar: Sidebar( top: MacosSearchField( @@ -142,7 +107,7 @@ class _WidgetGalleryState extends State { break; case 'Dialogs and Sheets': setState(() { - pageIndex = 5; + pageIndex = 4; searchFieldController.clear(); }); break; @@ -152,12 +117,18 @@ class _WidgetGalleryState extends State { searchFieldController.clear(); }); break; - case 'Selectors': + case 'ResizablePane': setState(() { pageIndex = 7; searchFieldController.clear(); }); break; + case 'Selectors': + setState(() { + pageIndex = 8; + searchFieldController.clear(); + }); + break; default: searchFieldController.clear(); } @@ -169,6 +140,7 @@ class _WidgetGalleryState extends State { SearchResultItem('Colors'), SearchResultItem('Dialogs and Sheets'), SearchResultItem('Toolbar'), + SearchResultItem('ResizablePane'), SearchResultItem('Selectors'), ], ), @@ -176,20 +148,27 @@ class _WidgetGalleryState extends State { builder: (context, scrollController) { return SidebarItems( currentIndex: pageIndex, - onChanged: (i) => setState(() => pageIndex = i), + onChanged: (i) { + if (kIsWeb && i == 10) { + launchUrl( + Uri.parse( + 'https://www.figma.com/file/IX6ph2VWrJiRoMTI1Byz0K/Apple-Design-Resources---macOS-(Community)?node-id=0%3A1745&mode=dev', + ), + ); + } else { + setState(() => pageIndex = i); + } + }, scrollController: scrollController, itemSize: SidebarItemSize.large, - items: [ - const SidebarItem( - // leading: MacosIcon(CupertinoIcons.square_on_circle), + items: const [ + SidebarItem( leading: MacosImageIcon( - AssetImage( - 'assets/sf_symbols/button_programmable_2x.png', - ), + AssetImage('assets/sf_symbols/button_programmable_2x.png'), ), label: Text('Buttons'), ), - const SidebarItem( + SidebarItem( leading: MacosImageIcon( AssetImage( 'assets/sf_symbols/lines_measurement_horizontal_2x.png', @@ -197,7 +176,7 @@ class _WidgetGalleryState extends State { ), label: Text('Indicators'), ), - const SidebarItem( + SidebarItem( leading: MacosImageIcon( AssetImage( 'assets/sf_symbols/character_cursor_ibeam_2x.png', @@ -206,36 +185,16 @@ class _WidgetGalleryState extends State { label: Text('Fields'), ), SidebarItem( - 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, - ), + leading: MacosImageIcon( + AssetImage('assets/sf_symbols/rectangle_3_group_2x.png'), ), - disclosureItems: [ - const SidebarItem( - leading: MacosImageIcon( - AssetImage( - 'assets/sf_symbols/rectangle_3_group_2x.png', - ), - ), - label: Text('Colors'), - ), - const SidebarItem( - leading: MacosIcon(CupertinoIcons.infinite), - label: Text('Item 3'), - ), - ], + label: Text('Colors'), ), - const SidebarItem( + SidebarItem( leading: MacosIcon(CupertinoIcons.square_on_square), label: Text('Dialogs & Sheets'), ), - const SidebarItem( + SidebarItem( leading: MacosImageIcon( AssetImage( 'assets/sf_symbols/macwindow.on.rectangle_2x.png', @@ -259,16 +218,23 @@ class _WidgetGalleryState extends State { leading: MacosIcon(CupertinoIcons.uiwindow_split_2x1), label: Text('TabView'), ), + SidebarItem( + leading: MacosIcon(CupertinoIcons.rectangle_split_3x1), + label: Text('ResizablePane'), + ), ], ), - const SidebarItem( + SidebarItem( leading: MacosImageIcon( AssetImage( - 'assets/sf_symbols/filemenu_and_selection_2x.png', - ), + 'assets/sf_symbols/filemenu_and_selection_2x.png'), ), label: Text('Selectors'), ), + SidebarItem( + leading: MacosIcon(CupertinoIcons.textformat_size), + label: Text('Typography'), + ), ], ); }, @@ -289,10 +255,19 @@ class _WidgetGalleryState extends State { ); }, ), - child: IndexedStack( - index: pageIndex, - children: pages, - ), + child: [ + CupertinoTabView(builder: (_) => const ButtonsPage()), + const IndicatorsPage(), + const FieldsPage(), + const ColorsPage(), + const DialogsPage(), + const ToolbarPage(), + const SliverToolbarPage(isVisible: !kIsWeb), + const TabViewPage(), + const ResizablePanePage(), + const SelectorsPage(), + const TypographyPage(), + ][pageIndex], ), ); } diff --git a/example/lib/pages/buttons_page.dart b/example/lib/pages/buttons_page.dart index 6c373f5f..fd61774f 100644 --- a/example/lib/pages/buttons_page.dart +++ b/example/lib/pages/buttons_page.dart @@ -1,3 +1,5 @@ +import 'package:example/widgets/widget_text_title1.dart'; +import 'package:example/widgets/widget_text_title2.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:macos_ui/macos_ui.dart'; @@ -59,412 +61,719 @@ class _ButtonsPageState extends State { ], ), children: [ - ResizablePane( - minSize: 180, - startSize: 200, - windowBreakpoint: 700, - resizableSide: ResizableSide.right, - builder: (_, __) { - return const Center( - child: Text('Resizable Pane'), - ); - }, - ), ContentArea( builder: (context, scrollController) { - return Column( - children: [ - Flexible( - fit: FlexFit.loose, - child: SingleChildScrollView( - controller: scrollController, - 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('MacosDisclosureButton'), - const SizedBox(height: 8), - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - MacosDisclosureButton( - isPressed: isDisclosureButtonPressed, - onPressed: () { - debugPrint('click'); - setState(() { - isDisclosureButtonPressed = - !isDisclosureButtonPressed; - }); - }), - ], - ), - const SizedBox(height: 20), - const Text('MacosIconButton'), - const SizedBox(height: 8), - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - MacosIconButton( - icon: const MacosIcon( - CupertinoIcons.star_fill, - ), - shape: BoxShape.rectangle, - borderRadius: BorderRadius.circular(7), - onPressed: () {}, - ), - const SizedBox(width: 8), - const MacosIconButton( - icon: MacosIcon( - CupertinoIcons.plus_app, - ), - 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(); + return SingleChildScrollView( + controller: scrollController, + padding: const EdgeInsets.all(20), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const WidgetTextTitle1(widgetName: 'PushButton'), + Divider(color: MacosTheme.of(context).dividerColor), + Text( + 'Primary', + style: MacosTypography.of(context).title2, + ), + Row( + children: [ + PushButton( + controlSize: ControlSize.mini, + child: const Text('Mini'), + onPressed: () { + MacosWindowScope.of(context).toggleSidebar(); + }, + ), + const SizedBox(width: 8), + PushButton( + controlSize: ControlSize.small, + child: const Text('Small'), + onPressed: () { + Navigator.of(context).push( + MaterialPageRoute( + builder: (_) { + return MacosScaffold( + toolBar: const ToolBar( + title: Text('New page'), + ), + children: [ + ContentArea( + builder: (context, _) { + return Center( + child: PushButton( + controlSize: ControlSize.regular, + child: const Text('Go Back'), + onPressed: () { + Navigator.of(context).maybePop(); + }, + ), + ); + }, + ), + ResizablePane( + minSize: 180, + startSize: 200, + windowBreakpoint: 700, + resizableSide: ResizableSide.left, + builder: (_, __) { + return const Center( + child: Text('Resizable Pane'), + ); + }, + ), + ], + ); }, ), - const SizedBox(width: 20), - PushButton( - buttonSize: ButtonSize.small, - child: const Text('Small'), - onPressed: () { - Navigator.of(context).push( - MaterialPageRoute( - builder: (_) { - return MacosScaffold( - toolBar: const ToolBar( - title: Text('New page'), - ), - children: [ - ContentArea( - builder: (context, _) { - return Center( - child: PushButton( - buttonSize: ButtonSize.large, - child: const Text('Go Back'), - onPressed: () { - Navigator.of(context) - .maybePop(); - }, - ), - ); + ); + }, + ), + const SizedBox(width: 8), + PushButton( + controlSize: ControlSize.regular, + child: const Text('Regular'), + onPressed: () { + Navigator.of(context).push( + MaterialPageRoute( + builder: (_) { + return MacosScaffold( + toolBar: const ToolBar( + title: Text('New page'), + ), + children: [ + ContentArea( + builder: (context, _) { + return Center( + child: PushButton( + controlSize: ControlSize.regular, + child: const Text('Go Back'), + onPressed: () { + Navigator.of(context).maybePop(); }, ), - ResizablePane( - minSize: 180, - startSize: 200, - windowBreakpoint: 700, - resizableSide: ResizableSide.left, - builder: (_, __) { - return const Center( - child: Text('Resizable Pane'), - ); + ); + }, + ), + ResizablePane( + minSize: 180, + startSize: 200, + windowBreakpoint: 700, + resizableSide: ResizableSide.left, + builder: (_, __) { + return const Center( + child: Text('Resizable Pane'), + ); + }, + ), + ], + ); + }, + ), + ); + }, + ), + const SizedBox(width: 8), + PushButton( + controlSize: ControlSize.large, + child: const Text('Large'), + onPressed: () { + Navigator.of(context).push( + MaterialPageRoute( + builder: (_) { + return MacosScaffold( + toolBar: const ToolBar( + title: Text('New page'), + ), + children: [ + ContentArea( + builder: (context, _) { + return Center( + child: PushButton( + controlSize: ControlSize.regular, + child: const Text('Go Back'), + onPressed: () { + Navigator.of(context).maybePop(); }, ), - ], - ); - }, - ), + ); + }, + ), + ResizablePane( + minSize: 180, + startSize: 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: 16), + Text( + 'Disabled Primary', + style: MacosTypography.of(context).title2, + ), + const Row( + children: [ + PushButton( + controlSize: ControlSize.mini, + child: Text('Mini'), + ), + SizedBox(width: 8), + PushButton( + controlSize: ControlSize.small, + child: Text('Small'), + ), + SizedBox(width: 8), + PushButton( + controlSize: ControlSize.regular, + child: Text('Regular'), + ), + SizedBox(width: 8), + PushButton( + controlSize: ControlSize.large, + child: Text('Large'), + ), + ], + ), + const SizedBox(height: 16), + Text( + 'Secondary', + style: MacosTypography.of(context).title2, + ), + Row( + children: [ + PushButton( + controlSize: ControlSize.mini, + secondary: true, + child: const Text('Mini'), + onPressed: () { + MacosWindowScope.of(context).toggleSidebar(); + }, + ), + const SizedBox(width: 8), + PushButton( + controlSize: ControlSize.small, + secondary: true, + child: const Text('Small'), + onPressed: () { + Navigator.of(context).push( + MaterialPageRoute( + builder: (_) { + return MacosScaffold( + toolBar: const ToolBar( + title: Text('New page'), + ), + children: [ + ContentArea( + builder: (context, _) { + return Center( + child: PushButton( + controlSize: ControlSize.regular, + child: const Text('Go Back'), + onPressed: () { + Navigator.of(context).maybePop(); + }, + ), + ); + }, + ), + ResizablePane( + minSize: 180, + startSize: 200, + windowBreakpoint: 700, + resizableSide: ResizableSide.left, + builder: (_, __) { + return const Center( + child: Text('Resizable Pane'), + ); + }, + ), + ], + ); }, ), - ], - ), - 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), + ); + }, + ), + const SizedBox(width: 8), + PushButton( + controlSize: ControlSize.regular, + secondary: true, + child: const Text('Regular'), + onPressed: () { + Navigator.of(context).push( + MaterialPageRoute( + builder: (_) { + return MacosScaffold( + toolBar: const ToolBar( + title: Text('New page'), + ), + children: [ + ContentArea( + builder: (context, _) { + return Center( + child: PushButton( + controlSize: ControlSize.regular, + child: const Text('Go Back'), + onPressed: () { + Navigator.of(context).maybePop(); + }, + ), + ); + }, + ), + ResizablePane( + minSize: 180, + startSize: 200, + windowBreakpoint: 700, + resizableSide: ResizableSide.left, + builder: (_, __) { + return const Center( + child: Text('Resizable Pane'), + ); + }, + ), + ], ); - }).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(width: 8), + PushButton( + controlSize: ControlSize.large, + secondary: true, + child: const Text('Large'), + onPressed: () { + Navigator.of(context).push( + MaterialPageRoute( + builder: (_) { + return MacosScaffold( + toolBar: const ToolBar( + title: Text('New page'), + ), + children: [ + ContentArea( + builder: (context, _) { + return Center( + child: PushButton( + controlSize: ControlSize.regular, + child: const Text('Go Back'), + onPressed: () { + Navigator.of(context).maybePop(); + }, + ), + ); + }, + ), + ResizablePane( + minSize: 180, + startSize: 200, + windowBreakpoint: 700, + resizableSide: ResizableSide.left, + builder: (_, __) { + return const Center( + child: Text('Resizable Pane'), + ); + }, + ), + ], + ); }, ), - ], + ); + }, + ), + ], + ), + const SizedBox(height: 16), + Text( + 'Disabled Secondary', + style: MacosTypography.of(context).title2, + ), + const Row( + children: [ + PushButton( + controlSize: ControlSize.mini, + secondary: true, + child: Text('Mini'), + ), + SizedBox(width: 8), + PushButton( + controlSize: ControlSize.small, + secondary: true, + child: Text('Small'), + ), + SizedBox(width: 8), + PushButton( + controlSize: ControlSize.regular, + secondary: true, + child: Text('Regular'), + ), + SizedBox(width: 8), + PushButton( + controlSize: ControlSize.large, + secondary: true, + child: Text('Large'), + ), + ], + ), + const SizedBox(height: 16), + const WidgetTextTitle1(widgetName: 'HelpButton'), + Divider(color: MacosTheme.of(context).dividerColor), + HelpButton(onPressed: () {}), + const SizedBox(height: 16), + Text( + 'Icon Buttons', + style: MacosTypography.of(context).title1, + ), + Divider(color: MacosTheme.of(context).dividerColor), + const WidgetTextTitle2(widgetName: 'MacosBackButton'), + const SizedBox(height: 8), + Row( + children: [ + MacosBackButton( + onPressed: () => debugPrint('click'), + fillColor: Colors.transparent, + ), + const SizedBox(width: 16.0), + MacosBackButton( + onPressed: () => debugPrint('click'), + ), + ], + ), + const SizedBox(height: 20), + const WidgetTextTitle2(widgetName: 'MacosDisclosureButton'), + const SizedBox(height: 8), + Row( + children: [ + MacosDisclosureButton( + isPressed: isDisclosureButtonPressed, + onPressed: () { + debugPrint('click'); + setState(() { + isDisclosureButtonPressed = + !isDisclosureButtonPressed; + }); + }, + ), + ], + ), + const SizedBox(height: 20), + const WidgetTextTitle2(widgetName: 'MacosIconButton'), + const SizedBox(height: 8), + Row( + children: [ + MacosIconButton( + icon: const MacosIcon( + CupertinoIcons.star_fill, ), - 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!; - }, - ), - ], + shape: BoxShape.rectangle, + borderRadius: BorderRadius.circular(7), + onPressed: () {}, + ), + const SizedBox(width: 8), + const MacosIconButton( + icon: MacosIcon( + CupertinoIcons.plus_app, ), - 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, - ), - ], + shape: BoxShape.circle, + //onPressed: () {}, + ), + const SizedBox(width: 8), + MacosIconButton( + icon: const MacosIcon( + CupertinoIcons.minus_square, ), - ], - ), + backgroundColor: Colors.transparent, + onPressed: () {}, + ), + ], ), - ), - ResizablePane( - minSize: 50, - startSize: 200, - //windowBreakpoint: 600, - builder: (_, __) { - return const Center( - child: Text('Resizable Pane'), - ); - }, - resizableSide: ResizableSide.top, - ) - ], - ); - }, - ), - ResizablePane( - minSize: 180, - startSize: 200, - windowBreakpoint: 800, - resizableSide: ResizableSide.left, - builder: (_, __) { - return const Center( - child: Text('Resizable Pane'), + const SizedBox(height: 20), + Text( + 'Switches, Checkboxes, & Radios', + style: MacosTypography.of(context).title1, + ), + Divider(color: MacosTheme.of(context).dividerColor), + const WidgetTextTitle2(widgetName: 'MacosSwitch'), + const SizedBox(height: 8), + Column( + mainAxisSize: MainAxisSize.min, + children: [ + Row( + children: [ + const Text('Mini'), + const SizedBox(width: 8), + MacosSwitch( + value: switchValue, + size: ControlSize.mini, + onChanged: (value) { + setState(() => switchValue = value); + }, + ), + ], + ), + const SizedBox(height: 8.0), + Row( + children: [ + const Text('Small'), + const SizedBox(width: 8), + MacosSwitch( + value: switchValue, + size: ControlSize.small, + onChanged: (value) { + setState(() => switchValue = value); + }, + ), + ], + ), + const SizedBox(height: 8.0), + Row( + children: [ + const Text('Regular'), + const SizedBox(width: 8), + MacosSwitch( + value: switchValue, + onChanged: (value) { + setState(() => switchValue = value); + }, + ), + ], + ), + ], + ), + const SizedBox(height: 16), + const WidgetTextTitle2(widgetName: 'MacosCheckbox'), + const SizedBox(height: 8), + MacosCheckbox( + value: switchValue, + onChanged: (value) { + setState(() => switchValue = value); + }, + ), + const SizedBox(height: 16), + const WidgetTextTitle2(widgetName: 'MacosRadioButton'), + const SizedBox(height: 8), + Row( + 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( + 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( + 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), + Text( + 'Pulldown & Popup Buttons', + style: MacosTypography.of(context).title1, + ), + Divider(color: MacosTheme.of(context).dividerColor), + const WidgetTextTitle2(widgetName: 'MacosPulldownButton'), + const SizedBox(height: 8), + Row( + 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( + 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 WidgetTextTitle2(widgetName: 'MacosPopupButton'), + const SizedBox(height: 8), + Row( + 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), + const WidgetTextTitle1(widgetName: 'MacosSegmentedControl'), + Divider(color: MacosTheme.of(context).dividerColor), + 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, + ), + ], + ), + ], + ), ); }, ), diff --git a/example/lib/pages/colors_page.dart b/example/lib/pages/colors_page.dart index 7a9df4fe..6eb3eac8 100644 --- a/example/lib/pages/colors_page.dart +++ b/example/lib/pages/colors_page.dart @@ -15,16 +15,27 @@ class _ColorsPageState extends State { toolBar: ToolBar( title: const Text('Colors'), titleWidth: 150.0, - actions: [ - ToolBarIconButton( - label: 'Toggle Sidebar', - icon: const MacosIcon( + leading: MacosTooltip( + message: 'Toggle Sidebar', + useMousePosition: false, + child: MacosIconButton( + icon: MacosIcon( CupertinoIcons.sidebar_left, + color: MacosTheme.brightnessOf(context).resolve( + const Color.fromRGBO(0, 0, 0, 0.5), + const Color.fromRGBO(255, 255, 255, 0.5), + ), + size: 20.0, + ), + boxConstraints: const BoxConstraints( + minHeight: 20, + minWidth: 20, + maxWidth: 48, + maxHeight: 38, ), onPressed: () => MacosWindowScope.of(context).toggleSidebar(), - showLabel: false, ), - ], + ), ), children: [ ContentArea( diff --git a/example/lib/pages/dialogs_page.dart b/example/lib/pages/dialogs_page.dart index b7645978..33764bda 100644 --- a/example/lib/pages/dialogs_page.dart +++ b/example/lib/pages/dialogs_page.dart @@ -2,6 +2,9 @@ import 'package:macos_ui/macos_ui.dart'; // ignore: implementation_imports import 'package:macos_ui/src/library.dart'; +const dialogMessage = + 'Description text about this alert is shown here, explaining to users what the options underneath are about and what to do.'; + class DialogsPage extends StatefulWidget { const DialogsPage({super.key}); @@ -16,16 +19,27 @@ class _DialogsPageState extends State { toolBar: ToolBar( title: const Text('Dialogs and Sheets'), titleWidth: 150.0, - actions: [ - ToolBarIconButton( - label: 'Toggle Sidebar', - icon: const MacosIcon( + leading: MacosTooltip( + message: 'Toggle Sidebar', + useMousePosition: false, + child: MacosIconButton( + icon: MacosIcon( CupertinoIcons.sidebar_left, + color: MacosTheme.brightnessOf(context).resolve( + const Color.fromRGBO(0, 0, 0, 0.5), + const Color.fromRGBO(255, 255, 255, 0.5), + ), + size: 20.0, + ), + boxConstraints: const BoxConstraints( + minHeight: 20, + minWidth: 20, + maxWidth: 48, + maxHeight: 38, ), onPressed: () => MacosWindowScope.of(context).toggleSidebar(), - showLabel: false, ), - ], + ), ), children: [ ContentArea( @@ -36,120 +50,101 @@ class _DialogsPageState extends State { child: Column( children: [ PushButton( - buttonSize: ButtonSize.large, + controlSize: ControlSize.regular, child: const Text('Show Alert Dialog 1'), onPressed: () => showMacosAlertDialog( context: context, builder: (context) => MacosAlertDialog( - appIcon: const FlutterLogo( - size: 56, - ), - title: const Text( - 'Alert Dialog with Primary Action', - ), - message: const Text( - 'This is an alert dialog with a primary action and no secondary action', - ), + appIcon: const FlutterLogo(size: 64), + title: const Text('Title'), + message: const Text(dialogMessage), //horizontalActions: false, primaryButton: PushButton( - buttonSize: ButtonSize.large, + controlSize: ControlSize.large, onPressed: Navigator.of(context).pop, - child: const Text('Primary'), + child: const Text('Label'), ), ), ), ), const SizedBox(height: 16), PushButton( - buttonSize: ButtonSize.large, + controlSize: ControlSize.regular, child: const Text('Show Alert Dialog 2'), onPressed: () => showMacosAlertDialog( context: context, builder: (context) => MacosAlertDialog( - appIcon: const FlutterLogo( - size: 56, - ), - title: const Text( - 'Alert Dialog with Secondary Action', - ), + appIcon: const FlutterLogo(size: 64), + title: const Text('Title'), message: const Text( - 'This is an alert dialog with primary action and secondary action laid out horizontally', + dialogMessage, textAlign: TextAlign.center, ), //horizontalActions: false, primaryButton: PushButton( - buttonSize: ButtonSize.large, + controlSize: ControlSize.large, onPressed: Navigator.of(context).pop, - child: const Text('Primary'), + child: const Text('Label'), ), secondaryButton: PushButton( - buttonSize: ButtonSize.large, - isSecondary: true, + controlSize: ControlSize.large, + secondary: true, onPressed: Navigator.of(context).pop, - child: const Text('Secondary'), + child: const Text('Label'), ), ), ), ), const SizedBox(height: 16), PushButton( - buttonSize: ButtonSize.large, + controlSize: ControlSize.regular, child: const Text('Show Alert Dialog 3'), onPressed: () => showMacosAlertDialog( context: context, builder: (context) => MacosAlertDialog( - appIcon: const FlutterLogo( - size: 56, - ), - title: const Text( - 'Alert Dialog with Secondary Action', - ), + appIcon: const FlutterLogo(size: 64), + title: const Text('Title'), message: const Text( - 'This is an alert dialog with primary action and secondary action laid out vertically', + dialogMessage, textAlign: TextAlign.center, ), horizontalActions: false, primaryButton: PushButton( - buttonSize: ButtonSize.large, + controlSize: ControlSize.large, onPressed: Navigator.of(context).pop, - child: const Text('Primary'), + child: const Text('Label'), ), secondaryButton: PushButton( - buttonSize: ButtonSize.large, - isSecondary: true, + controlSize: ControlSize.large, + secondary: true, onPressed: Navigator.of(context).pop, - child: const Text('Secondary'), + child: const Text('Label'), ), ), ), ), const SizedBox(height: 16), PushButton( - buttonSize: ButtonSize.large, + controlSize: ControlSize.regular, child: const Text('Show Alert Dialog 4'), onPressed: () => showMacosAlertDialog( context: context, builder: (context) => MacosAlertDialog( - appIcon: const FlutterLogo( - size: 56, - ), - title: const Text( - 'Alert Dialog with Secondary Action', - ), + appIcon: const FlutterLogo(size: 64), + title: const Text('Title'), message: const Text( - 'This is an alert dialog with primary action and secondary ' - 'action laid out vertically. It also contains a "suppress" option.', + dialogMessage, textAlign: TextAlign.center, ), horizontalActions: false, primaryButton: PushButton( - buttonSize: ButtonSize.large, + controlSize: ControlSize.large, onPressed: Navigator.of(context).pop, child: const Text('Primary'), ), secondaryButton: PushButton( - buttonSize: ButtonSize.large, - isSecondary: true, + controlSize: ControlSize.large, + secondary: true, onPressed: Navigator.of(context).pop, child: const Text('Secondary'), ), @@ -159,7 +154,7 @@ class _DialogsPageState extends State { ), const SizedBox(height: 16), PushButton( - buttonSize: ButtonSize.large, + controlSize: ControlSize.regular, child: const Text('Show sheet'), onPressed: () { showMacosSheet( @@ -214,47 +209,50 @@ class DemoSheet extends StatelessWidget { @override Widget build(BuildContext context) { return MacosSheet( - child: Center( - child: Column( - children: [ - const SizedBox(height: 50), - const FlutterLogo( - size: 56, - ), - const SizedBox(height: 24), - Text( - 'Welcome to macos_ui', - style: MacosTheme.of(context).typography.largeTitle.copyWith( - fontWeight: FontWeight.bold, - ), - ), - const SizedBox(height: 24), - const MacosListTile( - leading: MacosIcon(CupertinoIcons.lightbulb), - title: Text( - 'A robust library of Flutter components for macOS', - //style: MacosTheme.of(context).typography.headline, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 8.0), + child: Center( + child: Column( + children: [ + const SizedBox(height: 50), + const FlutterLogo( + size: 56, ), - subtitle: Text( - 'Create native looking macOS applications using Flutter', + const SizedBox(height: 24), + Text( + 'Welcome to macos_ui', + style: MacosTheme.of(context).typography.largeTitle.copyWith( + fontWeight: FontWeight.bold, + ), ), - ), - const SizedBox(height: 16), - const MacosListTile( - leading: MacosIcon(CupertinoIcons.bolt), - title: Text( - 'Create beautiful macOS applications in minutes', - //style: MacosTheme.of(context).typography.headline, + const SizedBox(height: 24), + const MacosListTile( + leading: MacosIcon(CupertinoIcons.lightbulb), + title: Text( + 'A robust library of Flutter components for macOS', + //style: MacosTheme.of(context).typography.headline, + ), + subtitle: Text( + 'Create native looking macOS applications using Flutter', + ), ), - ), - const Spacer(), - PushButton( - buttonSize: ButtonSize.large, - child: const Text('Get started'), - onPressed: () => Navigator.of(context).pop(), - ), - const SizedBox(height: 50), - ], + const SizedBox(height: 16), + const MacosListTile( + leading: MacosIcon(CupertinoIcons.bolt), + title: Text( + 'Create beautiful macOS applications in minutes', + //style: MacosTheme.of(context).typography.headline, + ), + ), + const Spacer(), + PushButton( + controlSize: ControlSize.regular, + child: const Text('Get started'), + onPressed: () => Navigator.of(context).pop(), + ), + const SizedBox(height: 50), + ], + ), ), ), ); diff --git a/example/lib/pages/fields_page.dart b/example/lib/pages/fields_page.dart index 94785708..fd9183bb 100644 --- a/example/lib/pages/fields_page.dart +++ b/example/lib/pages/fields_page.dart @@ -1,4 +1,6 @@ +import 'package:example/widgets/widget_text_title1.dart'; import 'package:flutter/cupertino.dart' hide OverlayVisibilityMode; +import 'package:flutter/material.dart'; import 'package:macos_ui/macos_ui.dart'; class FieldsPage extends StatefulWidget { @@ -15,16 +17,27 @@ class _FieldsPageState extends State { toolBar: ToolBar( title: const Text('Fields'), titleWidth: 150.0, - actions: [ - ToolBarIconButton( - label: 'Toggle Sidebar', - icon: const MacosIcon( + leading: MacosTooltip( + message: 'Toggle Sidebar', + useMousePosition: false, + child: MacosIconButton( + icon: MacosIcon( CupertinoIcons.sidebar_left, + color: MacosTheme.brightnessOf(context).resolve( + const Color.fromRGBO(0, 0, 0, 0.5), + const Color.fromRGBO(255, 255, 255, 0.5), + ), + size: 20.0, + ), + boxConstraints: const BoxConstraints( + minHeight: 20, + minWidth: 20, + maxWidth: 48, + maxHeight: 38, ), onPressed: () => MacosWindowScope.of(context).toggleSidebar(), - showLabel: false, ), - ], + ), ), children: [ ContentArea( @@ -32,7 +45,10 @@ class _FieldsPageState extends State { return SingleChildScrollView( padding: const EdgeInsets.all(20), child: Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ + const WidgetTextTitle1(widgetName: 'MacosTextField'), + Divider(color: MacosTheme.of(context).dividerColor), const SizedBox( width: 300.0, child: MacosTextField( @@ -84,6 +100,8 @@ class _FieldsPageState extends State { ), ), const SizedBox(height: 20), + const WidgetTextTitle1(widgetName: 'MacosSearchField'), + Divider(color: MacosTheme.of(context).dividerColor), SizedBox( width: 300.0, child: MacosSearchField( @@ -125,17 +143,6 @@ class _FieldsPageState extends State { ); }, ), - ResizablePane( - minSize: 180, - startSize: 200, - windowBreakpoint: 800, - resizableSide: ResizableSide.left, - builder: (_, __) { - return const Center( - child: Text('Resizable Pane'), - ); - }, - ), ], ); } @@ -352,8 +359,8 @@ const countries = [ var actionResults = [ SearchResultItem( 'Build project', - child: Row( - children: const [ + child: const Row( + children: [ Padding( padding: EdgeInsets.symmetric(horizontal: 8.0), child: MacosIcon(CupertinoIcons.hammer), @@ -365,8 +372,8 @@ var actionResults = [ ), SearchResultItem( 'Debug project', - child: Row( - children: const [ + child: const Row( + children: [ Padding( padding: EdgeInsets.symmetric(horizontal: 8.0), child: MacosIcon(CupertinoIcons.tickets), @@ -378,8 +385,8 @@ var actionResults = [ ), SearchResultItem( 'Open containing folder', - child: Row( - children: const [ + child: const Row( + children: [ Padding( padding: EdgeInsets.symmetric(horizontal: 8.0), child: MacosIcon(CupertinoIcons.folder), diff --git a/example/lib/pages/indicators_page.dart b/example/lib/pages/indicators_page.dart index 1dd4caf5..511b40c6 100644 --- a/example/lib/pages/indicators_page.dart +++ b/example/lib/pages/indicators_page.dart @@ -1,3 +1,5 @@ +import 'package:example/widgets/widget_text_title1.dart'; +import 'package:example/widgets/widget_text_title2.dart'; import 'package:macos_ui/macos_ui.dart'; // ignore: implementation_imports import 'package:macos_ui/src/library.dart'; @@ -20,16 +22,27 @@ class _IndicatorsPageState extends State { toolBar: ToolBar( title: const Text('Indicators'), titleWidth: 150.0, - actions: [ - ToolBarIconButton( - label: 'Toggle Sidebar', - icon: const MacosIcon( + leading: MacosTooltip( + message: 'Toggle Sidebar', + useMousePosition: false, + child: MacosIconButton( + icon: MacosIcon( CupertinoIcons.sidebar_left, + color: MacosTheme.brightnessOf(context).resolve( + const Color.fromRGBO(0, 0, 0, 0.5), + const Color.fromRGBO(255, 255, 255, 0.5), + ), + size: 20.0, + ), + boxConstraints: const BoxConstraints( + minHeight: 20, + minWidth: 20, + maxWidth: 48, + maxHeight: 38, ), onPressed: () => MacosWindowScope.of(context).toggleSidebar(), - showLabel: false, ), - ], + ), ), children: [ ContentArea( @@ -37,47 +50,103 @@ class _IndicatorsPageState extends State { return SingleChildScrollView( padding: const EdgeInsets.all(20), child: Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - CapacityIndicator( - value: capacitorValue, - onChanged: (v) => setState(() => capacitorValue = v), - splits: 20, - discrete: true, + const WidgetTextTitle1(widgetName: 'CapacityIndicator'), + Divider(color: MacosTheme.of(context).dividerColor), + Row( + children: [ + const Text('Standard'), + const SizedBox(width: 8), + Expanded( + child: CapacityIndicator( + value: capacitorValue, + onChanged: (v) => setState(() => capacitorValue = v), + ), + ), + ], ), - const SizedBox(height: 20), - CapacityIndicator( - value: capacitorValue, - onChanged: (v) => setState(() => capacitorValue = v), + const SizedBox(height: 16), + Row( + children: [ + const Text('Discrete'), + const SizedBox(width: 8), + Expanded( + child: CapacityIndicator( + value: capacitorValue, + onChanged: (v) => setState(() => capacitorValue = v), + splits: 20, + discrete: true, + ), + ), + ], ), const SizedBox(height: 20), - MacosSlider( - value: sliderValue, - onChanged: (v) => setState(() => sliderValue = v), + const WidgetTextTitle1(widgetName: 'MacosSlider'), + Divider(color: MacosTheme.of(context).dividerColor), + Row( + children: [ + const Text('Standard'), + const SizedBox(width: 8), + Expanded( + child: MacosSlider( + value: sliderValue, + onChanged: (v) => setState(() => sliderValue = v), + ), + ), + ], ), - const SizedBox(height: 20), - MacosSlider( - value: sliderValue, - discrete: true, - onChanged: (v) => setState(() => sliderValue = v), + const SizedBox(height: 16), + Row( + children: [ + const Text('Discrete'), + const SizedBox(width: 8), + Expanded( + child: MacosSlider( + value: sliderValue, + discrete: true, + onChanged: (v) => setState(() => sliderValue = v), + ), + ), + ], ), const SizedBox(height: 20), + const WidgetTextTitle1(widgetName: 'RatingIndicator'), + Divider(color: MacosTheme.of(context).dividerColor), RatingIndicator( value: ratingValue, onChanged: (v) => setState(() => ratingValue = v), ), const SizedBox(height: 20), - const ProgressCircle(), - const SizedBox(height: 20), - const RelevanceIndicator( - value: 25, - amount: 50, + Text( + 'Progress Indicators', + style: MacosTypography.of(context).title1, ), - const SizedBox(height: 20), - const Label( - icon: MacosIcon(CupertinoIcons.tag), - text: SelectableText('A determinate progress circle: '), - child: ProgressCircle(value: 50), + Divider(color: MacosTheme.of(context).dividerColor), + const WidgetTextTitle2(widgetName: 'ProgressBar'), + const SizedBox(height: 8), + const ProgressBar(value: 50), + const SizedBox(height: 16), + const WidgetTextTitle2(widgetName: 'ProgressCircle'), + const SizedBox(height: 8), + const Row( + children: [ + Text('Indeterminate'), + SizedBox(width: 8), + ProgressCircle(), + ], ), + const Row( + children: [ + Text('Determinate'), + SizedBox(width: 8), + ProgressCircle(value: 50), + ], + ), + const SizedBox(height: 20), + const WidgetTextTitle1(widgetName: 'RelevanceIndicator'), + Divider(color: MacosTheme.of(context).dividerColor), + const SizedBox(height: 8), ], ), ); diff --git a/example/lib/pages/resizable_pane_page.dart b/example/lib/pages/resizable_pane_page.dart new file mode 100644 index 00000000..78ed3320 --- /dev/null +++ b/example/lib/pages/resizable_pane_page.dart @@ -0,0 +1,86 @@ +import 'package:flutter/cupertino.dart'; +import 'package:macos_ui/macos_ui.dart'; + +class ResizablePanePage extends StatefulWidget { + const ResizablePanePage({super.key}); + + @override + State createState() => _ResizablePanePageState(); +} + +class _ResizablePanePageState extends State { + @override + Widget build(BuildContext context) { + return MacosScaffold( + toolBar: ToolBar( + title: const Text('Resizable Pane'), + leading: MacosTooltip( + message: 'Toggle Sidebar', + useMousePosition: false, + child: MacosIconButton( + icon: MacosIcon( + CupertinoIcons.sidebar_left, + color: MacosTheme.brightnessOf(context).resolve( + const Color.fromRGBO(0, 0, 0, 0.5), + const Color.fromRGBO(255, 255, 255, 0.5), + ), + size: 20.0, + ), + boxConstraints: const BoxConstraints( + minHeight: 20, + minWidth: 20, + maxWidth: 48, + maxHeight: 38, + ), + onPressed: () => MacosWindowScope.of(context).toggleSidebar(), + ), + ), + ), + children: [ + ResizablePane( + minSize: 180, + startSize: 200, + windowBreakpoint: 700, + resizableSide: ResizableSide.right, + builder: (_, __) { + return const Center( + child: Text('Left Resizable Pane'), + ); + }, + ), + ContentArea( + builder: (_, __) { + return Column( + children: [ + const Flexible( + fit: FlexFit.loose, + child: Center( + child: Text('Content Area'), + ), + ), + ResizablePane( + minSize: 50, + startSize: 200, + //windowBreakpoint: 600, + builder: (_, __) { + return const Center( + child: Text('Bottom Resizable Pane'), + ); + }, + resizableSide: ResizableSide.top, + ), + ], + ); + }, + ), + const ResizablePane.noScrollBar( + minSize: 180, + startSize: 200, + windowBreakpoint: 700, + resizableSide: ResizableSide.right, + child: Center(child: Text('Right non-scrollable Resizable Pane')), + ), + ], + ); + } +} diff --git a/example/lib/pages/selectors_page.dart b/example/lib/pages/selectors_page.dart index 8256a33e..74ed4b47 100644 --- a/example/lib/pages/selectors_page.dart +++ b/example/lib/pages/selectors_page.dart @@ -1,4 +1,8 @@ +import 'package:example/widgets/widget_text_title1.dart'; +import 'package:example/widgets/widget_text_title2.dart'; import 'package:flutter/cupertino.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; import 'package:macos_ui/macos_ui.dart'; class SelectorsPage extends StatefulWidget { @@ -15,16 +19,27 @@ class _SelectorsPageState extends State { toolBar: ToolBar( title: const Text('Selectors'), titleWidth: 150.0, - actions: [ - ToolBarIconButton( - label: 'Toggle Sidebar', - icon: const MacosIcon( + leading: MacosTooltip( + message: 'Toggle Sidebar', + useMousePosition: false, + child: MacosIconButton( + icon: MacosIcon( CupertinoIcons.sidebar_left, + color: MacosTheme.brightnessOf(context).resolve( + const Color.fromRGBO(0, 0, 0, 0.5), + const Color.fromRGBO(255, 255, 255, 0.5), + ), + size: 20.0, + ), + boxConstraints: const BoxConstraints( + minHeight: 20, + minWidth: 20, + maxWidth: 48, + maxHeight: 38, ), onPressed: () => MacosWindowScope.of(context).toggleSidebar(), - showLabel: false, ), - ], + ), ), children: [ ContentArea( @@ -33,23 +48,45 @@ class _SelectorsPageState extends State { controller: scrollController, padding: const EdgeInsets.all(20), child: Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ + Text( + 'Date & Time Pickers', + style: MacosTypography.of(context).title1, + ), + Divider(color: MacosTheme.of(context).dividerColor), Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, crossAxisAlignment: CrossAxisAlignment.start, children: [ - MacosDatePicker( - onDateChanged: (date) => debugPrint('$date'), + Column( + children: [ + const WidgetTextTitle2(widgetName: 'MacosDatePicker'), + const SizedBox(height: 12), + MacosDatePicker( + onDateChanged: (date) => debugPrint('$date'), + ), + ], ), - MacosTimePicker( - onTimeChanged: (time) => debugPrint('$time'), + const SizedBox(width: 50), + Column( + children: [ + const WidgetTextTitle2(widgetName: 'MacosTimePicker'), + const SizedBox(height: 12), + MacosTimePicker( + onTimeChanged: (time) => debugPrint('$time'), + ), + ], ), ], ), - const SizedBox(height: 50), - MacosColorWell( - onColorSelected: (color) => debugPrint('$color'), - ), + const SizedBox(height: 20), + if (!kIsWeb) ...[ + const WidgetTextTitle1(widgetName: 'MacosColorWell'), + Divider(color: MacosTheme.of(context).dividerColor), + MacosColorWell( + onColorSelected: (color) => debugPrint('$color'), + ), + ], ], ), ); diff --git a/example/lib/pages/sliver_toolbar_page.dart b/example/lib/pages/sliver_toolbar_page.dart index 8c2039a5..be2e680c 100644 --- a/example/lib/pages/sliver_toolbar_page.dart +++ b/example/lib/pages/sliver_toolbar_page.dart @@ -3,7 +3,27 @@ import 'package:flutter/material.dart'; import 'package:macos_ui/macos_ui.dart'; class SliverToolbarPage extends StatefulWidget { - const SliverToolbarPage({super.key}); + const SliverToolbarPage({super.key, required this.isVisible}); + + /// Whether this [SliverToolbarPage] is currently visible on the screen + /// (that is, not e.g. hidden by an [IndexedStack]). + /// + /// By default, macos_ui applies wallpaper tinting to the application's + /// window to match macOS' native appearance: + /// + /// + /// + /// However, this effect is realized by inserting `NSVisualEffectView`s behind + /// Flutter's canvas and turning the background of areas that are meant to be + /// affected by wallpaper tinting transparent. Since Flutter's + /// [`ImageFilter.blur`](https://api.flutter.dev/flutter/dart-ui/ImageFilter/ImageFilter.blur.html) + /// does not support transparency, wallpaper tinting is disabled automatically + /// when this widget's [isVisible] is true. + /// + /// This is meant to be a temporary solution until + /// [#16296](https://github.com/flutter/flutter/issues/16296) is resolved in + /// the Flutter project. + final bool isVisible; @override State createState() => _SliverToolbarPageState(); @@ -27,6 +47,7 @@ class _SliverToolbarPageState extends State { floating: floating, pinned: pinned, toolbarOpacity: opacity, + allowWallpaperTintingOverrides: widget.isVisible, actions: [ ToolBarIconButton( label: 'Pinned', diff --git a/example/lib/pages/tabview_page.dart b/example/lib/pages/tabview_page.dart index 64d4d8ff..5e224429 100644 --- a/example/lib/pages/tabview_page.dart +++ b/example/lib/pages/tabview_page.dart @@ -17,8 +17,29 @@ class _TabViewPageState extends State { @override Widget build(BuildContext context) { return MacosScaffold( - toolBar: const ToolBar( - title: Text('TabView'), + toolBar: ToolBar( + title: const Text('TabView'), + leading: MacosTooltip( + message: 'Toggle Sidebar', + useMousePosition: false, + child: MacosIconButton( + icon: MacosIcon( + CupertinoIcons.sidebar_left, + color: MacosTheme.brightnessOf(context).resolve( + const Color.fromRGBO(0, 0, 0, 0.5), + const Color.fromRGBO(255, 255, 255, 0.5), + ), + size: 20.0, + ), + boxConstraints: const BoxConstraints( + minHeight: 20, + minWidth: 20, + maxWidth: 48, + maxHeight: 38, + ), + onPressed: () => MacosWindowScope.of(context).toggleSidebar(), + ), + ), ), children: [ ContentArea( diff --git a/example/lib/pages/toolbar_page.dart b/example/lib/pages/toolbar_page.dart index 6e1754ce..104bd7c1 100644 --- a/example/lib/pages/toolbar_page.dart +++ b/example/lib/pages/toolbar_page.dart @@ -160,9 +160,9 @@ class _ToolbarPageState extends State { return SingleChildScrollView( controller: scrollController, padding: const EdgeInsets.all(30), - child: Center( + child: const Center( child: Column( - children: const [ + children: [ Text( 'A toolbar provides convenient access to frequently used commands and controls that perform actions relevant to the current view.', textAlign: TextAlign.center, diff --git a/example/lib/pages/typography_page.dart b/example/lib/pages/typography_page.dart new file mode 100644 index 00000000..3779cb57 --- /dev/null +++ b/example/lib/pages/typography_page.dart @@ -0,0 +1,394 @@ +import 'package:flutter/cupertino.dart'; +import 'package:macos_ui/macos_ui.dart'; + +class TypographyPage extends StatelessWidget { + const TypographyPage({super.key}); + + @override + Widget build(BuildContext context) { + final typography = MacosTypography.of(context); + final secondaryTypography = MacosTypography( + color: MacosTheme.brightnessOf(context).isDark + ? MacosColors.secondaryLabelColor.darkColor + : MacosColors.secondaryLabelColor, + ); + final tertiaryTypography = MacosTypography( + color: MacosTheme.brightnessOf(context).isDark + ? MacosColors.tertiaryLabelColor.darkColor + : MacosColors.tertiaryLabelColor, + ); + + return MacosScaffold( + toolBar: const ToolBar( + title: Text('Typography'), + ), + children: [ + ContentArea( + builder: (context, scrollController) { + return ListView( + children: [ + Padding( + padding: const EdgeInsets.all(16.0), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text('Label Color'), + const SizedBox(height: 42.0), + Text('LargeTitle', style: typography.largeTitle), + const SizedBox(height: 8.0), + Text( + 'LargeTitle', + style: typography.largeTitle + .copyWith(fontWeight: FontWeight.w700), + ), + const SizedBox(height: 24.0), + Text('Title1', style: typography.title1), + const SizedBox(height: 8.0), + Text( + 'Title1', + style: typography.title1 + .copyWith(fontWeight: FontWeight.w700), + ), + const SizedBox(height: 24.0), + Text('Title2', style: typography.title2), + const SizedBox(height: 8.0), + Text( + 'Title2', + style: typography.title2 + .copyWith(fontWeight: FontWeight.w700), + ), + const SizedBox(height: 24.0), + Text('Title3', style: typography.title3), + const SizedBox(height: 8.0), + Text( + 'Title3', + style: typography.title3 + .copyWith(fontWeight: FontWeight.w600), + ), + const SizedBox(height: 24.0), + Text('Headline', style: typography.headline), + const SizedBox(height: 8.0), + Text( + 'Headline', + style: typography.headline + .copyWith(fontWeight: MacosFontWeight.w860), + ), + const SizedBox(height: 24.0), + Text('Body', style: typography.body), + const SizedBox(height: 8.0), + Text( + 'Body', + style: typography.body + .copyWith(fontWeight: MacosFontWeight.w590), + ), + const SizedBox(height: 24.0), + Text('Callout', style: typography.callout), + const SizedBox(height: 8.0), + Text( + 'Callout', + style: typography.callout + .copyWith(fontWeight: MacosFontWeight.w590), + ), + const SizedBox(height: 24.0), + Text('Subheadline', style: typography.subheadline), + const SizedBox(height: 8.0), + Text( + 'Subheadline', + style: typography.subheadline + .copyWith(fontWeight: MacosFontWeight.w590), + ), + const SizedBox(height: 24.0), + Text('Footnote', style: typography.subheadline), + const SizedBox(height: 8.0), + Text( + 'Footnote', + style: typography.subheadline + .copyWith(fontWeight: MacosFontWeight.w590), + ), + const SizedBox(height: 24.0), + Text('Caption1', style: typography.caption1), + const SizedBox(height: 8.0), + Text( + 'Caption1', + style: typography.caption1 + .copyWith(fontWeight: MacosFontWeight.w510), + ), + const SizedBox(height: 24.0), + Text('Caption2', style: typography.caption2), + const SizedBox(height: 8.0), + Text( + 'Caption2', + style: typography.caption2 + .copyWith(fontWeight: MacosFontWeight.w590), + ), + ], + ), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text('Secondary Label Color'), + const SizedBox(height: 42.0), + Text( + 'LargeTitle', + style: secondaryTypography.largeTitle, + ), + const SizedBox(height: 8.0), + Text( + 'LargeTitle', + style: secondaryTypography.largeTitle + .copyWith(fontWeight: FontWeight.w700), + ), + const SizedBox(height: 24.0), + Text( + 'Title1', + style: secondaryTypography.title1, + ), + const SizedBox(height: 8.0), + Text( + 'Title1', + style: secondaryTypography.title1 + .copyWith(fontWeight: FontWeight.w700), + ), + const SizedBox(height: 24.0), + Text( + 'Title2', + style: secondaryTypography.title2, + ), + const SizedBox(height: 8.0), + Text( + 'Title2', + style: secondaryTypography.title2 + .copyWith(fontWeight: FontWeight.w700), + ), + const SizedBox(height: 24.0), + Text( + 'Title3', + style: secondaryTypography.title3, + ), + const SizedBox(height: 8.0), + Text( + 'Title3', + style: secondaryTypography.title3 + .copyWith(fontWeight: FontWeight.w600), + ), + const SizedBox(height: 24.0), + Text( + 'Headline', + style: secondaryTypography.headline, + ), + const SizedBox(height: 8.0), + Text( + 'Headline', + style: secondaryTypography.headline + .copyWith(fontWeight: MacosFontWeight.w860), + ), + const SizedBox(height: 24.0), + Text( + 'Body', + style: secondaryTypography.body, + ), + const SizedBox(height: 8.0), + Text( + 'Body', + style: secondaryTypography.body + .copyWith(fontWeight: MacosFontWeight.w590), + ), + const SizedBox(height: 24.0), + Text( + 'Callout', + style: secondaryTypography.callout, + ), + const SizedBox(height: 8.0), + Text( + 'Callout', + style: secondaryTypography.callout + .copyWith(fontWeight: MacosFontWeight.w590), + ), + const SizedBox(height: 24.0), + Text( + 'Subheadline', + style: secondaryTypography.subheadline, + ), + const SizedBox(height: 8.0), + Text( + 'Subheadline', + style: secondaryTypography.subheadline + .copyWith(fontWeight: FontWeight.w600), + ), + const SizedBox(height: 24.0), + Text( + 'Footnote', + style: secondaryTypography.footnote, + ), + const SizedBox(height: 8.0), + Text( + 'Footnote', + style: secondaryTypography.footnote + .copyWith(fontWeight: MacosFontWeight.w590), + ), + const SizedBox(height: 24.0), + Text( + 'Caption1', + style: secondaryTypography.caption1, + ), + const SizedBox(height: 8.0), + Text( + 'Caption1', + style: secondaryTypography.caption1 + .copyWith(fontWeight: MacosFontWeight.w510), + ), + const SizedBox(height: 24.0), + Text( + 'Caption2', + style: secondaryTypography.caption2, + ), + const SizedBox(height: 8.0), + Text( + 'Caption2', + style: secondaryTypography.caption2 + .copyWith(fontWeight: MacosFontWeight.w590), + ), + ], + ), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text('Tertiary Label Color'), + const SizedBox(height: 42.0), + Text( + 'LargeTitle', + style: tertiaryTypography.largeTitle, + ), + const SizedBox(height: 8.0), + Text( + 'LargeTitle', + style: tertiaryTypography.largeTitle + .copyWith(fontWeight: FontWeight.w700), + ), + const SizedBox(height: 24.0), + Text( + 'Title1', + style: tertiaryTypography.title1, + ), + const SizedBox(height: 8.0), + Text( + 'Title1', + style: tertiaryTypography.title1 + .copyWith(fontWeight: FontWeight.w700), + ), + const SizedBox(height: 24.0), + Text( + 'Title2', + style: tertiaryTypography.title2, + ), + const SizedBox(height: 8.0), + Text( + 'Title2', + style: tertiaryTypography.title2 + .copyWith(fontWeight: FontWeight.w700), + ), + const SizedBox(height: 24.0), + Text( + 'Title3', + style: tertiaryTypography.title3, + ), + const SizedBox(height: 8.0), + Text( + 'Title3', + style: tertiaryTypography.title3 + .copyWith(fontWeight: FontWeight.w600), + ), + const SizedBox(height: 24.0), + Text( + 'Headline', + style: tertiaryTypography.headline, + ), + const SizedBox(height: 8.0), + Text( + 'Headline', + style: tertiaryTypography.headline + .copyWith(fontWeight: MacosFontWeight.w860), + ), + const SizedBox(height: 24.0), + Text( + 'Body', + style: tertiaryTypography.body, + ), + const SizedBox(height: 8.0), + Text( + 'Body', + style: tertiaryTypography.body + .copyWith(fontWeight: MacosFontWeight.w590), + ), + const SizedBox(height: 24.0), + Text( + 'Callout', + style: tertiaryTypography.callout, + ), + const SizedBox(height: 8.0), + Text( + 'Callout', + style: tertiaryTypography.callout + .copyWith(fontWeight: MacosFontWeight.w590), + ), + const SizedBox(height: 24.0), + Text( + 'Subheadline', + style: tertiaryTypography.subheadline, + ), + const SizedBox(height: 8.0), + Text( + 'Subheadline', + style: tertiaryTypography.subheadline + .copyWith(fontWeight: MacosFontWeight.w590), + ), + const SizedBox(height: 24.0), + Text( + 'Footnote', + style: tertiaryTypography.footnote, + ), + const SizedBox(height: 8.0), + Text( + 'Footnote', + style: tertiaryTypography.footnote + .copyWith(fontWeight: MacosFontWeight.w590), + ), + const SizedBox(height: 24.0), + Text( + 'Caption1', + style: tertiaryTypography.caption1, + ), + const SizedBox(height: 8.0), + Text( + 'Caption1', + style: tertiaryTypography.caption1 + .copyWith(fontWeight: MacosFontWeight.w510), + ), + const SizedBox(height: 24.0), + Text( + 'Caption2', + style: tertiaryTypography.caption2, + ), + const SizedBox(height: 8.0), + Text( + 'Caption2', + style: tertiaryTypography.caption2 + .copyWith(fontWeight: MacosFontWeight.w590), + ), + ], + ), + ], + ), + ), + ], + ); + }, + ), + ], + ); + } +} diff --git a/example/lib/platform_menus.dart b/example/lib/platform_menus.dart new file mode 100644 index 00000000..91ba6a7c --- /dev/null +++ b/example/lib/platform_menus.dart @@ -0,0 +1,52 @@ +import 'package:flutter/foundation.dart' show kIsWeb; +import 'dart:io' as io; + +import 'package:flutter/widgets.dart' + show + PlatformMenu, + PlatformMenuItem, + PlatformProvidedMenuItem, + PlatformProvidedMenuItemType; + +List menuBarItems() { + if (kIsWeb) { + return []; + } else { + if (io.Platform.isMacOS) { + return const [ + PlatformMenu( + label: 'macos_ui Widget Gallery', + menus: [ + PlatformProvidedMenuItem( + type: PlatformProvidedMenuItemType.about, + ), + PlatformProvidedMenuItem( + type: PlatformProvidedMenuItemType.quit, + ), + ], + ), + PlatformMenu( + label: 'View', + menus: [ + PlatformProvidedMenuItem( + type: PlatformProvidedMenuItemType.toggleFullScreen, + ), + ], + ), + PlatformMenu( + label: 'Window', + menus: [ + PlatformProvidedMenuItem( + type: PlatformProvidedMenuItemType.minimizeWindow, + ), + PlatformProvidedMenuItem( + type: PlatformProvidedMenuItemType.zoomWindow, + ), + ], + ), + ]; + } else { + return []; + } + } +} diff --git a/example/lib/widgets/widget_text_title1.dart b/example/lib/widgets/widget_text_title1.dart new file mode 100644 index 00000000..77ab7314 --- /dev/null +++ b/example/lib/widgets/widget_text_title1.dart @@ -0,0 +1,30 @@ +import 'package:flutter/cupertino.dart'; +import 'package:google_fonts/google_fonts.dart'; +import 'package:macos_ui/macos_ui.dart'; + +class WidgetTextTitle1 extends StatelessWidget { + const WidgetTextTitle1({super.key, required this.widgetName}); + + final String widgetName; + + @override + Widget build(BuildContext context) { + return DecoratedBox( + decoration: BoxDecoration( + color: MacosColors.systemGrayColor.withOpacity(0.5), + borderRadius: BorderRadius.circular(4.0), + ), + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 6.0, + ), + child: Text( + widgetName, + style: MacosTypography.of(context) + .title1 + .copyWith(fontFamily: GoogleFonts.jetBrainsMono().fontFamily), + ), + ), + ); + } +} diff --git a/example/lib/widgets/widget_text_title2.dart b/example/lib/widgets/widget_text_title2.dart new file mode 100644 index 00000000..8da1135d --- /dev/null +++ b/example/lib/widgets/widget_text_title2.dart @@ -0,0 +1,30 @@ +import 'package:flutter/cupertino.dart'; +import 'package:google_fonts/google_fonts.dart'; +import 'package:macos_ui/macos_ui.dart'; + +class WidgetTextTitle2 extends StatelessWidget { + const WidgetTextTitle2({super.key, required this.widgetName}); + + final String widgetName; + + @override + Widget build(BuildContext context) { + return DecoratedBox( + decoration: BoxDecoration( + color: MacosColors.systemGrayColor.withOpacity(0.5), + borderRadius: BorderRadius.circular(4.0), + ), + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 6.0, + ), + child: Text( + widgetName, + style: MacosTypography.of(context) + .title2 + .copyWith(fontFamily: GoogleFonts.jetBrainsMono().fontFamily), + ), + ), + ); + } +} diff --git a/example/macos/Flutter/GeneratedPluginRegistrant.swift b/example/macos/Flutter/GeneratedPluginRegistrant.swift index 722b1dd3..0179c12c 100644 --- a/example/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/example/macos/Flutter/GeneratedPluginRegistrant.swift @@ -6,7 +6,13 @@ import FlutterMacOS import Foundation import macos_ui +import macos_window_utils +import path_provider_foundation +import url_launcher_macos func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { MacOSUiPlugin.register(with: registry.registrar(forPlugin: "MacOSUiPlugin")) + MacOSWindowUtilsPlugin.register(with: registry.registrar(forPlugin: "MacOSWindowUtilsPlugin")) + PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) + UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) } diff --git a/example/macos/Podfile b/example/macos/Podfile index 049abe29..7ed4260c 100644 --- a/example/macos/Podfile +++ b/example/macos/Podfile @@ -1,4 +1,4 @@ -platform :osx, '10.14' +platform :osx, '10.14.6' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' diff --git a/example/macos/Podfile.lock b/example/macos/Podfile.lock index 67acca3c..2fa81174 100644 --- a/example/macos/Podfile.lock +++ b/example/macos/Podfile.lock @@ -2,21 +2,40 @@ PODS: - FlutterMacOS (1.0.0) - macos_ui (0.1.0): - FlutterMacOS + - macos_window_utils (1.0.0): + - FlutterMacOS + - path_provider_foundation (0.0.1): + - Flutter + - FlutterMacOS + - url_launcher_macos (0.0.1): + - FlutterMacOS DEPENDENCIES: - FlutterMacOS (from `Flutter/ephemeral`) - macos_ui (from `Flutter/ephemeral/.symlinks/plugins/macos_ui/macos`) + - macos_window_utils (from `Flutter/ephemeral/.symlinks/plugins/macos_window_utils/macos`) + - path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`) + - url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`) EXTERNAL SOURCES: FlutterMacOS: :path: Flutter/ephemeral macos_ui: :path: Flutter/ephemeral/.symlinks/plugins/macos_ui/macos + macos_window_utils: + :path: Flutter/ephemeral/.symlinks/plugins/macos_window_utils/macos + path_provider_foundation: + :path: Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin + url_launcher_macos: + :path: Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos SPEC CHECKSUMS: FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24 macos_ui: 6229a8922cd97bafb7d9636c8eb8dfb0744183ca + macos_window_utils: 933f91f64805e2eb91a5bd057cf97cd097276663 + path_provider_foundation: eaf5b3e458fc0e5fbb9940fb09980e853fe058b8 + url_launcher_macos: d2691c7dd33ed713bf3544850a623080ec693d95 -PODFILE CHECKSUM: 353c8bcc5d5b0994e508d035b5431cfe18c1dea7 +PODFILE CHECKSUM: ff0a9a3ce75ee73f200ca7e2f47745698c917ef9 COCOAPODS: 1.11.3 diff --git a/example/macos/Runner.xcodeproj/project.pbxproj b/example/macos/Runner.xcodeproj/project.pbxproj index e7c1634c..2c562a4e 100644 --- a/example/macos/Runner.xcodeproj/project.pbxproj +++ b/example/macos/Runner.xcodeproj/project.pbxproj @@ -61,7 +61,7 @@ 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = ""; }; 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; 33CC10F72044A3C60003C045 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = Runner/Info.plist; sourceTree = ""; }; - 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainFlutterWindow.swift; sourceTree = ""; }; + 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */ = {isa = PBXFileReference; indentWidth = 2; lastKnownFileType = sourcecode.swift; path = MainFlutterWindow.swift; sourceTree = ""; tabWidth = 2; }; 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Debug.xcconfig"; sourceTree = ""; }; 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Release.xcconfig"; sourceTree = ""; }; 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "Flutter-Generated.xcconfig"; path = "ephemeral/Flutter-Generated.xcconfig"; sourceTree = ""; }; @@ -94,7 +94,6 @@ 94ECD9F878BC8EB5F0E7094E /* Pods-Runner.release.xcconfig */, AFB798A3289226D0E5AB9985 /* Pods-Runner.profile.xcconfig */, ); - name = Pods; path = Pods; sourceTree = ""; }; @@ -405,7 +404,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.14; + MACOSX_DEPLOYMENT_TARGET = 10.14.6; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; SWIFT_COMPILATION_MODE = wholemodule; @@ -484,7 +483,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.14; + MACOSX_DEPLOYMENT_TARGET = 10.14.6; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx; @@ -531,7 +530,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.14; + MACOSX_DEPLOYMENT_TARGET = 10.14.6; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; SWIFT_COMPILATION_MODE = wholemodule; diff --git a/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json index a2ec33f1..0a0928ee 100644 --- a/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json +++ b/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -1,68 +1,68 @@ { "images" : [ { - "size" : "16x16", - "idiom" : "mac", "filename" : "app_icon_16.png", - "scale" : "1x" + "idiom" : "mac", + "scale" : "1x", + "size" : "16x16" }, { - "size" : "16x16", + "filename" : "app_icon_32 1.png", "idiom" : "mac", - "filename" : "app_icon_32.png", - "scale" : "2x" + "scale" : "2x", + "size" : "16x16" }, { - "size" : "32x32", - "idiom" : "mac", "filename" : "app_icon_32.png", - "scale" : "1x" + "idiom" : "mac", + "scale" : "1x", + "size" : "32x32" }, { - "size" : "32x32", - "idiom" : "mac", "filename" : "app_icon_64.png", - "scale" : "2x" + "idiom" : "mac", + "scale" : "2x", + "size" : "32x32" }, { - "size" : "128x128", - "idiom" : "mac", "filename" : "app_icon_128.png", - "scale" : "1x" + "idiom" : "mac", + "scale" : "1x", + "size" : "128x128" }, { - "size" : "128x128", + "filename" : "app_icon_256 1.png", "idiom" : "mac", - "filename" : "app_icon_256.png", - "scale" : "2x" + "scale" : "2x", + "size" : "128x128" }, { - "size" : "256x256", - "idiom" : "mac", "filename" : "app_icon_256.png", - "scale" : "1x" + "idiom" : "mac", + "scale" : "1x", + "size" : "256x256" }, { - "size" : "256x256", + "filename" : "app_icon_512 1.png", "idiom" : "mac", - "filename" : "app_icon_512.png", - "scale" : "2x" + "scale" : "2x", + "size" : "256x256" }, { - "size" : "512x512", - "idiom" : "mac", "filename" : "app_icon_512.png", - "scale" : "1x" + "idiom" : "mac", + "scale" : "1x", + "size" : "512x512" }, { - "size" : "512x512", - "idiom" : "mac", "filename" : "app_icon_1024.png", - "scale" : "2x" + "idiom" : "mac", + "scale" : "2x", + "size" : "512x512" } ], "info" : { - "version" : 1, - "author" : "xcode" + "author" : "xcode", + "version" : 1 } } diff --git a/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png b/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png index 3c4935a7ca84f0976aca34b7f2895d65fb94d1ea..82b6f9d9a33e198f5747104729e1fcef999772a5 100644 GIT binary patch literal 102994 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ⅅ8MglG&ER{i5lN315cO?EdHNLrg? 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 z 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 Pe7HEdAEg|=c=LY&YVNkY literal 46993 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 diff --git a/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png b/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png index ed4cc16421680a50164ba74381b4b35ceaa0ccfc..13b35eba55c6dabc3aac36f33d859266c18fa0d0 100644 GIT binary patch literal 5680 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 literal 3276 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 N;OXk;vd$@?2>?>Ex^Vyi diff --git a/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256 1.png b/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256 1.png new file mode 100644 index 0000000000000000000000000000000000000000..bdb57226d5f2bd20f11934f4903f16459cf52379 GIT binary patch literal 14142 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 diff --git a/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512 1.png b/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512 1.png new file mode 100644 index 0000000000000000000000000000000000000000..326c0e72c9d820600887813b3b98d0dd69c5d4e8 GIT binary patch literal 36406 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 literal 0 HcmV?d00001 diff --git a/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png b/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png index e71a726136a47ed24125c7efc79d68a4a01961b4..326c0e72c9d820600887813b3b98d0dd69c5d4e8 100644 GIT binary patch literal 36406 zcmeGE=RaKU_dbB`8KZ_EB%(x35TbX25d=Z>h)%Q!Av#fJM3Csc_g zC2I6x%$)80`Tkz#KRA!h1FzY`?0es3t!rKDT5EjPe6B=BLPr7s0GW!if;Ip^!AmGW zL;$`Vdre+|FA!I4r6)keFvAx3M#1`}ijBHDzy)3t0gwjl|qC2YB`SSxFKHr(oY#H$)x{L$LL zBdLKTlsOrmb>T0wd=&6l3+_Te>1!j0OU8%b%N342^opKmT)gni(wV($s(>V-fUv@0p8!f`=>PxC|9=nu ze{ToBBj8b<{PLfXV$h8YPgA~E!_sF9bl;QOF{o6t&JdsX?}rW!_&d`#wlB6T_h;Xf zl{4Tz5>qjF4kZgjO7ZiLPRz_~U@k5%?=30+nxEh9?s78gZ07YHB`FV`4%hlQlMJe@J`+e(qzy+h(9yY^ckv_* zb_E6o4p)ZaWfraIoB2)U7_@l(J0O%jm+Or>8}zSSTkM$ASG^w3F|I? z$+eHt7T~04(_WfKh27zqS$6* zzyy-ZyqvSIZ0!kkSvHknm_P*{5TKLQs8S6M=ONuKAUJWtpxbL#2(_huvY(v~Y%%#~ zYgsq$JbLLprKkV)32`liIT$KKEqs$iYxjFlHiRNvBhxbDg*3@Qefw4UM$>i${R5uB zhvTgmqQsKA{vrKN;TSJU2$f9q=y{$oH{<)woSeV>fkIz6D8@KB zf4M%v%f5U2?<8B(xn}xV+gWP?t&oiapJhJbfa;agtz-YM7=hrSuxl8lAc3GgFna#7 zNjX7;`d?oD`#AK+fQ=ZXqfIZFEk{ApzjJF0=yO~Yj{7oQfXl+6v!wNnoqwEvrs81a zGC?yXeSD2NV!ejp{LdZGEtd1TJ)3g{P6j#2jLR`cpo;YX}~_gU&Gd<+~SUJVh+$7S%`zLy^QqndN<_9 zrLwnXrLvW+ew9zX2)5qw7)zIYawgMrh`{_|(nx%u-ur1B7YcLp&WFa24gAuw~& zKJD3~^`Vp_SR$WGGBaMnttT)#fCc^+P$@UHIyBu+TRJWbcw4`CYL@SVGh!X&y%!x~ zaO*m-bTadEcEL6V6*{>irB8qT5Tqd54TC4`h`PVcd^AM6^Qf=GS->x%N70SY-u?qr>o2*OV7LQ=j)pQGv%4~z zz?X;qv*l$QSNjOuQZ>&WZs2^@G^Qas`T8iM{b19dS>DaXX~=jd4B2u`P;B}JjRBi# z_a@&Z5ev1-VphmKlZEZZd2-Lsw!+1S60YwW6@>+NQ=E5PZ+OUEXjgUaXL-E0fo(E* zsjQ{s>n33o#VZm0e%H{`KJi@2ghl8g>a~`?mFjw+$zlt|VJhSU@Y%0TWs>cnD&61fW4e0vFSaXZa4-c}U{4QR8U z;GV3^@(?Dk5uc@RT|+5C8-24->1snH6-?(nwXSnPcLn#X_}y3XS)MI_?zQ$ZAuyg+ z-pjqsw}|hg{$~f0FzmmbZzFC0He_*Vx|_uLc!Ffeb8#+@m#Z^AYcWcZF(^Os8&Z4g zG)y{$_pgrv#=_rV^D|Y<_b@ICleUv>c<0HzJDOsgJb#Rd-Vt@+EBDPyq7dUM9O{Yp zuGUrO?ma2wpuJuwl1M=*+tb|qx7Doj?!F-3Z>Dq_ihFP=d@_JO;vF{iu-6MWYn#=2 zRX6W=`Q`q-+q@Db|6_a1#8B|#%hskH82lS|9`im0UOJn?N#S;Y0$%xZw3*jR(1h5s z?-7D1tnIafviko>q6$UyqVDq1o@cwyCb*})l~x<@s$5D6N=-Uo1yc49p)xMzxwnuZ zHt!(hu-Ek;Fv4MyNTgbW%rPF*dB=;@r3YnrlFV{#-*gKS_qA(G-~TAlZ@Ti~Yxw;k za1EYyX_Up|`rpbZ0&Iv#$;eC|c0r4XGaQ-1mw@M_4p3vKIIpKs49a8Ns#ni)G314Z z8$Ei?AhiT5dQGWUYdCS|IC7r z=-8ol>V?u!n%F*J^^PZ(ONT&$Ph;r6X;pj|03HlDY6r~0g~X#zuzVU%a&!fs_f|m?qYvg^Z{y?9Qh7Rn?T*F%7lUtA6U&={HzhYEzA`knx1VH> z{tqv?p@I(&ObD5L4|YJV$QM>Nh-X3cx{I&!$FoPC_2iIEJfPk-$;4wz>adRu@n`_y z_R6aN|MDHdK;+IJmyw(hMoDCFCQ(6?hCAG5&7p{y->0Uckv# zvooVuu04$+pqof777ftk<#42@KQ((5DPcSMQyzGOJ{e9H$a9<2Qi_oHjl{#=FUL9d z+~0^2`tcvmp0hENwfHR`Ce|<1S@p;MNGInXCtHnrDPXCKmMTZQ{HVm_cZ>@?Wa6}O zHsJc7wE)mc@1OR2DWY%ZIPK1J2p6XDO$ar`$RXkbW}=@rFZ(t85AS>>U0!yt9f49^ zA9@pc0P#k;>+o5bJfx0t)Lq#v4`OcQn~av__dZ-RYOYu}F#pdsl31C^+Qgro}$q~5A<*c|kypzd} ziYGZ~?}5o`S5lw^B{O@laad9M_DuJle- z*9C7o=CJh#QL=V^sFlJ0c?BaB#4bV^T(DS6&Ne&DBM_3E$S^S13qC$7_Z?GYXTpR@wqr70wu$7+qvf-SEUa5mdHvFbu^7ew!Z1a^ zo}xKOuT*gtGws-a{Tx}{#(>G~Y_h&5P@Q8&p!{*s37^QX_Ibx<6XU*AtDOIvk|^{~ zPlS}&DM5$Ffyu-T&0|KS;Wnaqw{9DB&B3}vcO14wn;)O_e@2*9B&0I_ zZz{}CMxx`hv-XouY>^$Y@J(_INeM>lIQI@I>dBAqq1)}?Xmx(qRuX^i4IV%=MF306 z9g)i*79pP%_7Ex?m6ag-4Tlm=Z;?DQDyC-NpUIb#_^~V_tsL<~5<&;Gf2N+p?(msn zzUD~g>OoW@O}y0@Z;RN)wjam`CipmT&O7a|YljZqU=U86 zedayEdY)2F#BJ6xvmW8K&ffdS*0!%N<%RB!2~PAT4AD*$W7yzHbX#Eja9%3aD+Ah2 zf#T;XJW-GMxpE=d4Y>}jE=#U`IqgSoWcuvgaWQ9j1CKzG zDkoMDDT)B;Byl3R2PtC`ip=yGybfzmVNEx{xi_1|Cbqj>=FxQc{g`xj6fIfy`D8fA z##!-H_e6o0>6Su&$H2kQTujtbtyNFeKc}2=|4IfLTnye#@$Au7Kv4)dnA;-fz@D_8 z)>irG$)dkBY~zX zC!ZXLy*L3xr6cb70QqfN#Q>lFIc<>}>la4@3%7#>a1$PU&O^&VszpxLC%*!m-cO{B z-Y}rQr4$84(hvy#R69H{H zJ*O#uJh)TF6fbXy;fZkk%X=CjsTK}o5N1a`d7kgYYZLPxsHx%9*_XN8VWXEkVJZ%A z1A+5(B;0^{T4aPYr8%i@i32h)_)|q?9vws)r+=5u)1YNftF5mknwfd*%jXA2TeP}Z zQ!m?xJ3?9LpPM?_A3$hQ1QxNbR&}^m z!F999s?p^ak#C4NM_x2p9FoXWJ$>r?lJ)2bG)sX{gExgLA2s5RwHV!h6!C~d_H||J z>9{E{mEv{Z1z~65Vix@dqM4ZqiU|!)eWX$mwS5mLSufxbpBqqS!jShq1bmwCR6 z4uBri7ezMeS6ycaXPVu(i2up$L; zjpMtB`k~WaNrdgM_R=e#SN?Oa*u%nQy01?()h4A(jyfeNfx;5o+kX?maO4#1A^L}0 zYNyIh@QVXIFiS0*tE}2SWTrWNP3pH}1Vz1;E{@JbbgDFM-_Mky^7gH}LEhl~Ve5PexgbIyZ(IN%PqcaV@*_`ZFb=`EjspSz%5m2E34BVT)d=LGyHVz@-e%9Ova*{5@RD;7=Ebkc2GP%pIP^P7KzKapnh`UpH?@h z$RBpD*{b?vhohOKf-JG3?A|AX|2pQ?(>dwIbWhZ38GbTm4AImRNdv_&<99ySX;kJ| zo|5YgbHZC#HYgjBZrvGAT4NZYbp}qkVSa;C-LGsR26Co+i_HM&{awuO9l)Ml{G8zD zs$M8R`r+>PT#Rg!J(K6T4xHq7+tscU(}N$HY;Yz*cUObX7J7h0#u)S7b~t^Oj}TBF zuzsugnst;F#^1jm>22*AC$heublWtaQyM6RuaquFd8V#hJ60Z3j7@bAs&?dD#*>H0SJaDwp%U~27>zdtn+ z|8sZzklZy$%S|+^ie&P6++>zbrq&?+{Yy11Y>@_ce@vU4ZulS@6yziG6;iu3Iu`M= zf3rcWG<+3F`K|*(`0mE<$89F@jSq;j=W#E>(R}2drCB7D*0-|D;S;(;TwzIJkGs|q z2qH{m_zZ+el`b;Bv-#bQ>}*VPYC|7`rgBFf2oivXS^>v<&HHTypvd4|-zn|=h=TG{ z05TH2+{T%EnADO>3i|CB zCu60#qk`}GW{n4l-E$VrqgZGbI zbQW690KgZt4U3F^5@bdO1!xu~p@7Y~*_FfWg2CdvED5P5#w#V46LH`<&V0{t&Ml~4 zHNi7lIa+#i+^Z6EnxO7KJQw)wD)4~&S-Ki8)3=jpqxmx6c&zU&<&h%*c$I(5{1HZT zc9WE}ijcWJiVa^Q^xC|WX0habl89qycOyeViIbi(LFsEY_8a|+X^+%Qv+W4vzj>`y zpuRnjc-eHNkvXvI_f{=*FX=OKQzT?bck#2*qoKTHmDe>CDb&3AngA1O)1b}QJ1Tun z_<@yVEM>qG7664Pa@dzL@;DEh`#?yM+M|_fQS<7yv|i*pw)|Z8)9IR+QB7N3v3K(wv4OY*TXnH&X0nQB}?|h2XQeGL^q~N7N zDFa@x0E(UyN7k9g%IFq7Sf+EAfE#K%%#`)!90_)Dmy3Bll&e1vHQyPA87TaF(xbqMpDntVp?;8*$87STop$!EAnGhZ?>mqPJ(X zFsr336p3P{PpZCGn&^LP(JjnBbl_3P3Kcq+m}xVFMVr1zdCPJMDIV_ki#c=vvTwbU z*gKtfic&{<5ozL6Vfpx>o2Tts?3fkhWnJD&^$&+Mh5WGGyO7fG@6WDE`tEe(8<;+q z@Ld~g08XDzF8xtmpIj`#q^(Ty{Hq>t*v`pedHnuj(0%L(%sjkwp%s}wMd!a<*L~9T z9MM@s)Km~ogxlqEhIw5(lc46gCPsSosUFsgGDr8H{mj%OzJz{N#;bQ;KkV+ZWA1(9 zu0PXzyh+C<4OBYQ0v3z~Lr;=C@qmt8===Ov2lJ1=DeLfq*#jgT{YQCuwz?j{&3o_6 zsqp2Z_q-YWJg?C6=!Or|b@(zxTlg$ng2eUQzuC<+o)k<6^9ju_Z*#x+oioZ5T8Z_L zz9^A1h2eFS0O5muq8;LuDKwOv4A9pxmOjgb6L*i!-(0`Ie^d5Fsgspon%X|7 zC{RRXEmYn!5zP9XjG*{pLa)!2;PJB2<-tH@R7+E1cRo=Wz_5Ko8h8bB$QU%t9#vol zAoq?C$~~AsYC|AQQ)>>7BJ@{Cal)ZpqE=gjT+Juf!RD-;U0mbV1ED5PbvFD6M=qj1 zZ{QERT5@(&LQ~1X9xSf&@%r|3`S#ZCE=sWD`D4YQZ`MR`G&s>lN{y2+HqCfvgcw3E z-}Kp(dfGG?V|97kAHQX+OcKCZS`Q%}HD6u*e$~Ki&Vx53&FC!x94xJd4F2l^qQeFO z?&JdmgrdVjroKNJx64C!H&Vncr^w zzR#XI}Dn&o8jB~_YlVM^+#0W(G1LZH5K^|uYT@KSR z^Y5>^*Bc45E1({~EJB(t@4n9gb-eT#s@@7)J^^<_VV`Pm!h7av8XH6^5zO zOcQBhTGr;|MbRsgxCW69w{bl4EW#A~);L?d4*y#j8Ne=Z@fmJP0k4{_cQ~KA|Y#_#BuUiYx8y*za3_6Y}c=GSe7(2|KAfhdzud!Zq&}j)=o4 z7R|&&oX7~e@~HmyOOsCCwy`AR+deNjZ3bf6ijI_*tKP*_5JP3;0d;L_p(c>W1b%sG zJ*$wcO$ng^aW0E(5ldckV9unU7}OB7s?Wx(761?1^&8tA5y0_(ieV>(x-e@}1`lWC z-YH~G$D>#ud!SxK2_Iw{K%92=+{4yb-_XC>ji&j7)1ofp(OGa4jjF;Hd*`6YQL+Jf zffg+6CPc8F@EDPN{Kn96yip;?g@)qgkPo^nVKFqY?8!=h$G$V=<>%5J&iVjwR!7H0 z$@QL|_Q81I;Bnq8-5JyNRv$Y>`sWl{qhq>u+X|)@cMlsG!{*lu?*H`Tp|!uv z9oEPU1jUEj@ueBr}%Y)7Luyi)REaJV>eQ{+uy4uh0ep0){t;OU8D*RZ& zE-Z-&=BrWQLAD^A&qut&4{ZfhqK1ZQB0fACP)=zgx(0(o-`U62EzTkBkG@mXqbjXm z>w`HNeQM?Is&4xq@BB(K;wv5nI6EXas)XXAkUuf}5uSrZLYxRCQPefn-1^#OCd4aO zzF=dQ*CREEyWf@n6h7(uXLNgJIwGp#Xrsj6S<^bzQ7N0B0N{XlT;`=m9Olg<>KL}9 zlp>EKTx-h|%d1Ncqa=wnQEuE;sIO-f#%Bs?g4}&xS?$9MG?n$isHky0caj za8W+B^ERK#&h?(x)7LLpOqApV5F>sqB`sntV%SV>Q1;ax67qs+WcssfFeF3Xk=e4^ zjR2^(%K1oBq%0%Rf!y&WT;lu2Co(rHi|r1_uW)n{<7fGc-c=ft7Z0Q}r4W$o$@tQF#i?jDBwZ8h+=SC}3?anUp3mtRVv9l#H?-UD;HjTF zQ*>|}e=6gDrgI9p%c&4iMUkQa4zziS$bO&i#DI$Wu$7dz7-}XLk%!US^XUIFf2obO zFCTjVEtkvYSKWB;<0C;_B{HHs~ax_48^Cml*mjfBC5*7^HJZiLDir(3k&BerVIZF8zF;0q80eX8c zPN4tc+Dc5DqEAq$Y3B3R&XPZ=AQfFMXv#!RQnGecJONe0H;+!f^h5x0wS<+%;D}MpUbTNUBA}S2n&U59-_5HKr{L^jPsV8B^%NaH|tUr)mq=qCBv_- ziZ1xUp(ZzxUYTCF@C}To;u60?RIfTGS?#JnB8S8@j`TKPkAa)$My+6ziGaBcA@){d z91)%+v2_ba7gNecdj^8*I4#<11l!{XKl6s0zkXfJPxhP+@b+5ev{a>p*W-3*25c&} zmCf{g9mPWVQ$?Sp*4V|lT@~>RR)9iNdN^7KT@>*MU3&v^3e?=NTbG9!h6C|9zO097 zN{Qs6YwR-5$)~ z`b~qs`a1Dbx8P>%V=1XGjBptMf%P~sl1qbHVm1HYpY|-Z^Dar8^HqjIw}xaeRlsYa zJ_@Apy-??`gxPmb`m`0`z`#G7*_C}qiSZe~l2z65tE~IwMw$1|-u&t|z-8SxliH00 zlh1#kuqB56s+E&PWQ7Nz17?c}pN+A@-c^xLqh(j;mS|?>(Pf7(?qd z5q@jkc^nA&!K-}-1P=Ry0yyze0W!+h^iW}7jzC1{?|rEFFWbE^Yu7Y}t?jmP-D$f+ zmqFT7nTl0HL|4jwGm7w@a>9 zKD)V~+g~ysmei$OT5}%$&LK8?ib|8aY|>W3;P+0B;=oD=?1rg+PxKcP(d;OEzq1CKA&y#boc51P^ZJPPS)z5 zAZ)dd2$glGQXFj$`XBBJyl2y-aoBA8121JC9&~|_nY>nkmW>TLi%mWdn-^Jks-Jv| zSR*wij;A3Fcy8KsDjQ15?Z9oOj|Qw2;jgJiq>dxG(2I2RE- z$As!#zSFIskebqU2bnoM^N<4VWD2#>!;saPSsY8OaCCQqkCMdje$C?Sp%V}f2~tG5 z0whMYk6tcaABwu*x)ak@n4sMElGPX1_lmv@bgdI2jPdD|2-<~Jf`L`@>Lj7{<-uLQ zE3S_#3e10q-ra=vaDQ42QUY^@edh>tnTtpBiiDVUk5+Po@%RmuTntOlE29I4MeJI?;`7;{3e4Qst#i-RH6s;>e(Sc+ubF2_gwf5Qi%P!aa89fx6^{~A*&B4Q zKTF|Kx^NkiWx=RDhe<{PWXMQ;2)=SC=yZC&mh?T&CvFVz?5cW~ritRjG2?I0Av_cI z)=s!@MXpXbarYm>Kj0wOxl=eFMgSMc?62U#2gM^li@wKPK9^;;0_h7B>F>0>I3P`{ zr^ygPYp~WVm?Qbp6O3*O2)(`y)x>%ZXtztz zMAcwKDr=TCMY!S-MJ8|2MJCVNUBI0BkJV6?(!~W!_dC{TS=eh}t#X+2D>Kp&)ZN~q zvg!ogxUXu^y(P*;Q+y_rDoGeSCYxkaGPldDDx)k;ocJvvGO#1YKoQLHUf2h_pjm&1 zqh&!_KFH03FcJvSdfgUYMp=5EpigZ*8}7N_W%Ms^WSQ4hH`9>3061OEcxmf~TcYn5_oHtscWn zo5!ayj<_fZ)vHu3!A!7M;4y1QIr8YGy$P2qDD_4+T8^=^dB6uNsz|D>p~4pF3Nrb6 zcpRK*($<~JUqOya#M1=#IhOZ zG)W+rJS-x(6EoVz)P zsSo>JtnChdj9^);su%SkFG~_7JPM zEDz3gk2T7Y%x>1tWyia|op(ilEzvAujW?Xwlw>J6d7yEi8E zv30riR|a_MM%ZZX&n!qm0{2agq(s?x9E@=*tyT$nND+{Djpm7Rsy!+c$j+wqMwTOF zZL8BQ|I`<^bGW)5apO{lh(Asqen?_U`$_n0-Ob~Yd%^89oEe%9yGumQ_8Be+l2k+n zCxT%s?bMpv|AdWP7M1LQwLm|x+igA~;+iK-*+tClF&ueX_V}>=4gvZ01xpubQWXD_ zi?Un>&3=$fu)dgk-Z;0Ll}HK5_YM->l^Czrd0^cJ))(DwL2g3aZuza7ga9^|mT_70 z))}A}r1#-(9cxtn<9jGRwOB4hb9kK@YCgjfOM-90I$8@l=H^`K$cyhe2mTM|FY9vW znH~h)I<_aa#V1xmhk?Ng@$Jw-s%a!$BI4Us+Df+?J&gKAF-M`v}j`OWKP3>6`X`tEmhe#y*(Xm$_^Ybbs=%;L7h zp7q^C*qM}Krqsinq|WolR99>_!GL#Z71Hhz|IwQQv<>Ds09B?Je(lhI1(FInO8mc} zl$RyKCUmfku+Cd^8s0|t+e}5g7M{ZPJQH=UB3(~U&(w#Bz#@DTDHy>_UaS~AtN>4O zJ-I#U@R($fgupHebcpuEBX`SZ>kN!rW$#9>s{^3`86ZRQRtYTY)hiFm_9wU3c`SC8 z-5M%g)h}3Pt|wyj#F%}pGC@VL`9&>9P+_UbudCkS%y2w&*o})hBplrB*@Z?gel5q+ z%|*59(sR9GMk3xME}wd%&k?7~J)OL`rK#4d-haC7uaU8-L@?$K6(r<0e<;y83rK&` z3Q!1rD9WkcB8WBQ|WT|$u^lkr0UL4WH4EQTJyk@5gzHb18cOte4w zS`fLv8q;PvAZyY;*Go3Qw1~5#gP0D0ERla6M6#{; zr1l?bR}Nh+OC7)4bfAs(0ZD(axaw6j9v`^jh5>*Eo&$dAnt?c|Y*ckEORIiJXfGcM zEo`bmIq6rJm`XhkXR-^3d8^RTK2;nmVetHfUNugJG(4XLOu>HJA;0EWb~?&|0abr6 zxqVp@p=b3MN^|~?djPe!=eex(u!x>RYFAj|*T$cTi*Sd3Bme7Pri1tkK9N`KtRmXf zZYNBNtik97ct1R^vamQBfo9ZUR@k*LhIg8OR9d_{iv#t)LQV91^5}K5u{eyxwOFoU zHMVq$C>tfa@uNDW^_>EmO~WYQd(@!nKmAvSSIb&hPO|}g-3985t?|R&WZXvxS}Kt2i^eRe>WHb_;-K5cM4=@AN1>E&1c$k!w4O*oscx(f=<1K6l#8Exi)U(ZiZ zdr#YTP6?m1e1dOKysUjQ^>-MR={OuD00g6+(a^cvcmn#A_%Fh3Of%(qP5nvjS1=(> z|Ld8{u%(J}%2SY~+$4pjy{()5HN2MYUjg1X9umxOMFFPdM+IwOVEs4Z(olynvT%G) zt9|#VR}%O2@f6=+6uvbZv{3U)l;C{tuc zZ{K$rut=eS%3_~fQv^@$HV6#9)K9>|0qD$EV2$G^XUNBLM|5-ZmFF!KV)$4l^KVj@ zZ4fI}Knv*K%zPqK77}B-h_V{66VrmoZP2>@^euu8Rc}#qwRwt5uEBWcJJE5*5rT2t zA4Jpx`QQ~1Sh_n_a9x%Il!t1&B~J6p54zxAJx`REov${jeuL8h8x-z=?qwMAmPK5i z_*ES)BW(NZluu#Bmn1-NUKQip_X&_WzJy~J`WYxEJQ&Gu7DD< z&F9urE;}8S{x4{yB zaq~1Zrz%8)<`prSQv$eu5@1RY2WLu=waPTrn`WK%;G5(jt^FeM;gOdvXQjYhax~_> z{bS_`;t#$RYMu-;_Dd&o+LD<5Afg6v{NK?0d8dD5ohAN?QoocETBj?y{MB)jQ%UQ}#t3j&iL!qr@#6JEajR3@^k5wgLfI9S9dT2^f`2wd z%I#Q*@Ctk@w=(u)@QC}yBvUP&fFRR-uYKJ){Wp3&$s(o~W7OzgsUIPx0|ph2L1(r*_Pa@T@mcH^JxBjh09#fgo|W#gG7}|)k&uD1iZxb0 z@|Y)W79SKj9sS&EhmTD;uI#)FE6VwQ*YAr&foK$RI5H8_ripb$^=;U%gWbrrk4!5P zXDcyscEZoSH~n6VJu8$^6LE6)>+=o#Q-~*jmob^@191+Ot1w454e3)WMliLtY6~^w zW|n#R@~{5K#P+(w+XC%(+UcOrk|yzkEes=!qW%imu6>zjdb!B#`efaliKtN}_c!Jp zfyZa`n+Nx8;*AquvMT2;c8fnYszdDA*0(R`bsof1W<#O{v%O!1IO4WZe=>XBu_D%d zOwWDaEtX%@B>4V%f1+dKqcXT>m2!|&?}(GK8e&R=&w?V`*Vj)sCetWp9lr@@{xe6a zE)JL&;p}OnOO}Nw?vFyoccXT*z*?r}E8{uPtd;4<(hmX;d$rqJhEF}I+kD+m(ke;J z7Cm$W*CSdcD=RYEBhedg>tuT{PHqwCdDP*NkHv4rvQTXkzEn*Mb0oJz&+WfWIOS4@ zzpPJ|e%a-PIwOaOC7uQcHQ-q(SE(e@fj+7oC@34wzaBNaP;cw&gm{Z8yYX?V(lIv5 zKbg*zo1m5aGA4^lwJ|bAU=j3*d8S{vp!~fLFcK8s6%Ng55_qW_d*3R%e=34aDZPfD z&Le39j|ahp6E7B0*9OVdeMNrTErFatiE+=Z!XZ^tv0y%zZKXRTBuPyP&C{5(H?t)S zKV24_-TKpOmCPzU&by8R1Q5HY^@IDoeDA9MbgizgQ*F1Er~HVmvSU>vx}pZVQ&tr| zOtZl8vfY2#L<)gZ=ba&wG~EI*Vd?}lRMCf+!b5CDz$8~be-HKMo5omk$w7p4`Mym*IR8WiTz4^kKcUo^8Hkcsu14u z`Pkg`#-Y^A%CqJ0O@UF|caAulf68@(zhqp~YjzInh7qSN7Ov%Aj(Qz%{3zW|xubJ- ztNE_u_MO7Q_585r;xD?e=Er}@U1G@BKW5v$UM((eByhH2p!^g9W}99OD8VV@7d{#H zv)Eam+^K(5>-Ot~U!R$Um3prQmM)7DyK=iM%vy>BRX4#aH7*oCMmz07YB(EL!^%F7?CA#>zXqiYDhS;e?LYPTf(bte6B ztrfvDXYG*T;ExK-w?Knt{jNv)>KMk*sM^ngZ-WiUN;=0Ev^GIDMs=AyLg2V@3R z7ugNc45;4!RPxvzoT}3NCMeK$7j#q3r_xV(@t@OPRyoKBzHJ#IepkDsm$EJRxL)A* zf{_GQYttu^OXr$jHQn}zs$Eh|s|Z!r?Yi+bS-bi+PE*lH zo|6ztu6$r_?|B~S#m>imI!kQP9`6X426uHRri!wGcK;J;`%sFM(D#*Le~W*t2uH`Q z(HEO9-c_`mhA@4QhbW+tgtt9Pzx=_*3Kh~TB$SKmU4yx-Ay&)n%PZPKg#rD4H{%Ke zdMY@rf5EAFfqtrf?Vmk&N(_d-<=bvfOdPrYwY*;5%j@O6@O#Qj7LJTk-x3LN+dEKy+X z>~U8j3Ql`exr1jR>+S4nEy+4c2f{-Q!3_9)yY758tLGg7k^=nt<6h$YE$ltA+13S<}uOg#XHe6 zZHKdNsAnMQ_RIuB;mdoZ%RWpandzLR-BnjN2j@lkBbBd+?i ze*!5mC}!Qj(Q!rTu`KrRRqp22c=hF6<^v&iCDB`n7mHl;vdclcer%;{;=kA(PwdGG zdX#BWoC!leBC4);^J^tPkPbIe<)~nYb6R3u{HvC!NOQa?DC^Q`|_@ zcz;rk`a!4rSLAS>_=b@g?Yab4%=J3Cc7pRv8?_rHMl_aK*HSPU%0pG2Fyhef_biA!aW|-(( z*RIdG&Lmk(=(nk28Q1k1Oa$8Oa-phG%Mc6dT3>JIylcMMIc{&FsBYBD^n@#~>C?HG z*1&FpYVvXOU@~r2(BUa+KZv;tZ15#RewooEM0LFb>guQN;Z0EBFMFMZ=-m$a3;gVD z)2EBD4+*=6ZF?+)P`z@DOT;azK0Q4p4>NfwDR#Pd;no|{q_qB!zk1O8QojE;>zhPu z1Q=1z^0MYHo1*``H3ex|bW-Zy==5J4fE2;g6sq6YcXMYK5i|S^9(OSw#v!3^!EB<% zZF~J~CleS`V-peStyf*I%1^R88D;+8{{qN6-t!@gTARDg^w2`uSzFZbPQ!)q^oC}m zPo8VOQxq2BaIN`pAVFGu8!{p3}(+iZ`f4ck2ygVpEZMQW38nLpj3NQx+&sAkb8`}P3- zc>N*k6AG?r}bfO6_vccTuKX+*- z7W4Q#2``P0jIHYs)F>uG#AM#I6W2)!Nu2nD5{CRV_PmkDS2ditmbd#pggqEgAo%5oC?|CP zGa0CV)wA*ko!xC7pZYkqo{10CN_e00FX5SjWkI3?@XG}}bze!(&+k2$C-C`6temSk z_YyYpB^wh3woo`B zrMSTd4T?(X-jh`FeO76C(3xsOm9s2BP_b%ospg^!#*2*o9N;tf4(X9$qc_d(()yz5 zDk@1}u_Xd+86vy5RBs?LQCuYKCGPS;E4uFOi@V%1JTK&|eRf~lp$AV#;*#O}iRI2=i3rFL8{ zA^ptDZ0l6k-mq=hUJ0x$Y@J>UNfz~I5l63H(`~*v;qX`Z{zwsQQD-!wp0D&hyB8&Z z7$R07gIKGJ^%AvQ{4KM0edM39iFRx=P^6`!<1(s0t|JbB2tXs_B_IH9#ajH0C=-n+ z`nz`fKMBKLlf?2AC+|83M+0rqR%uhNGD;uKA6jOjp7YDe^4%0fRB<^bcjlS2KF~F; zu09wh1x0&4pG&76M;x8$u`b134t=dEPBn6PV|X29<#T4F1mxGF*HOgiWU8tN@cguI z_F@o+XL7FJztR63wC|j4x_DANzcX94r7Iz-O2x$({&qd*mdLG=-Rv)uZ}UlMR+F&q zU}=lkfb0p1>1Ho){o$@}mSKIV;h*$AND7~Dl)QzpFBlSM99Kx+F7GsVK5xcR? z_4Q(Z%cgk8ST}U;;=!LwyZVu^S$>B-Waeik%wzcKTIqeX=0FP(TGQ=nxi=dsS5BYF zl@?}NT!Y!Iyos^@v7XWXA{_bV~1lxz7gC?xuXxy0_?GaN!AhRRM5>)^t%&ODd;@HN5L{MD3 zc>i2keQZVm#?NrDwbfd}_<*5^U&w0zv~n-y8=GGN-!=_`FU^cM8oVCWRFxw?BM^YD zi=Vxz4q|jwPTg+?q7_XI)-S@gQkh>w0ZUB}a{^ z_i;`Y(~fvpI!vmW*A^|P7(6+@C4UeL2WATf{P1?H5rk`5{TL zcf!CgP6Mi{MvjZS)rfo7JLDZK7M7ANd$3`{j9baD*7{#Zu-33fOYUzjvtKzR2)_T1I1s7fe&z|=)QkX;=`zX8!Byw-veM#yr;|wjO^II>!B*B z0+w%;0(=*G3V@88t!}~zx)&do(uF=073Yeh*fEhZb3Vn>t!m(9p~Y_FdV3IgR)9eT z)~e9xpI%2deTWyHlXA(7srrfc_`7ACm!R>SoIgkuF8 z!wkOhrixFy9y@)GdxAntd!!7@=L_tFD2T5OdSUO)I%yj02le`qeQ=yKq$g^h)NG;# za(0J@#VBi^5YI|QI=rq{KlxwGabZJ0dKmfWDROkcM}lUN$@DV`K7fU?8CP2H23QPi zG?YF*=Vn=kTK*#Y_{AQN&oLju|0#E=fx%YVh>S{puu&K$b;BN*jIo@VYhqPiJPzzM>#kxoy0vW9i;ne2_BIG0zyRFp<3M(iY(%*M_>q0ulV2K}Tg zkG{EWKS{i%4DUuHi%DVKy%e+Q!~Uf`>>F6NgD{{I8~nO4!VgOvtFOc7(O)X`|7n*f zxBa4CJ-v9fUUH+`7sPVvpM_C*udZ@OTGTzx56QM5y~OlrZc&w9=)B?nmd@keRn+^= zvm~4sa5987LFDnU{(N|N zJAR8H@}p1fC+H(yTI4n#%~TbImMpuqYn9cQ<0QQ%=PzZItLkC*ef9WJUvfITKWh#D zc#__8`4am9%#NslIUw+<82#SR8AYG|woLfBg#!-&dqq}@P>|I0%lbdy0lSMmNe+}o zj0zZuFr6Wb?Y{Qy-S=|r`bdrDmhnmvkRnkdn`YCleU>Q$=je}LGhh>_QAj6aa_0Oc z%Swsmui;IRx7bN*=AAS@5yW&Y2hy;3&|HAiA8}!HT6!Z!RVn~MZg`RmI6&%#tBZDx zfD+y@Z~NWlk*4l13vmt3AK2wP!fQlnBbECL>?p)F?T)<`w&QN>cP_V>r7UTcsTaaP zTOb$f!P@zf$6>890NVKbIkG8rE?9!Y97sMSZjfF?A zYR8lp`LMoz~O?iaZN;gcX;LC-%Ia*R%A&SLx!YIf29?P+=XAAojK8!^OU*@?R&DK!#G_lsn!#;S375uZ&B0HH1|BO0R90$U>qs zSvHv>H~mAgNCcjo-e+;RjY6B9NCbQrZ|BHjTkehaU<9CSkdd>Vl*ifA2LNOP&R2Qdy3k3-TQ+ zbq=#vI43x`s=%~cGyN&y4Y!FxhwgDe@i6uv8^BLL&3z*SO=D0aLjih?gY4-9uWp5or)H+v~w6n5X#F-I52z=Z_p4JB(;M| zeaVFhuR2|3UD2MzVc~^nSoD2(dD#uL_1PdnIxeA{V5n`#3xf1Zx@4lw(DsQ&H$h zw#%3O<1173hjg2_nhKi!d1ej=h7y`hVjCNB6|HTnx>SWuCE-kgTnfT+YGX4_Lun({ zDv2`>d3vrS)tTf7ps_vvh!Cx^e1BFuWnEAh0(7fkNk|-3oU|iRWdsC6U)?Raft~HN z;^$U}vZK5O8|LV$>6X5T(uYkblv{zwPxnQBh(BQ5tA~J!vGiAMYP^_ki~pkIxDfOZ zUJDwq%O~WueeV6%uN<54&u*c&E4y431cklBNrb06zGOOy4XNT~JS-q(s6@)F@ovbe ze`fial(O4(-su%6@@1+V0MsdLLMyE8;)nou(7}czU(5ASaZYDT(kUZ0L(&g$nF^n9 z9-Pi`ZZLX&)^*M6As4_2Mmc9S7OT)F8KkL2NJ)KJcnCuWU=Wy402A&45#Q9Id~BBH z0cY*xlv!uXzKrXLH!xQu(OtJvEj|0-DmRj1vjFz{c*I4$Pe(+_V|^b~S!0xm{8lq= zZv)@NlcyL3Xdz+*|L137F7y6L-2VsrKw=q^S>F6i%<{Fr8zk06$Ay-(!L$fY@7mcng!2}L0t zgi|KxfB63Xtk_Q8#ZPipQ@!zgjdpEIbK_?q17Hoi4Eiyun$hrc>T(7pOLVLQE=lgGwA+A308p& z7@=09(|$>eLy5gLe{*|3b(M;1n;C^~v?o88jYib48eR4$QGsBFzd}3QuwO^_XE(=B zq+hMi0UFC|dB{LCwch7;zYT=NK})O%sgi0k#yV;My@24^B1+CuZmYOh0^b)5Ba_)) zC%i#_Iev&nsu%I|1N5=MVc#PrlunKAs&hY|3s5;@}`>sB>}gzxuB zB=2vrRyB3uiyW(hkDUNe1@&(b`;>ZvGgw|@s{zVC#_`HXIN_^J@Etb zA7A+F?ot37T{<-vTy8h&b3e+WKHE1oh;pUQrN4yRRrx?mT_9jRa2i4l1fUnLW^Cbl z!I1>VzyFe?VELWWhM?@?t-YPZkD-Qjo@bC2(o#ZtZmr{KZsdFWItV`rs$gp{724@C zL8K5}E0+DHcWcL^{BGei4>@J-3%a#$y6;I}=upc};-NDv-z#kPX26ylOpH)Ov1uU{ zkLj6oiH6l_s+B~_z;|Jc2oi?naS7#3H63~~lWj4rUnd=fCnKdkik<@R&kch9q##G{ z4u!%=rlM~Yp3jk*t8}1B`Sv6<%Z^}~1e@aq zg|JQ`QO2pSjAm-g*?IrNc$^~sIrNBo2$m|Sxanr?Mfs>2@Auu49 zGXlsS<9XS1&8h(dD*Hl&5HBDG!^pJ*lkau_Ur+7`7z;rcs$hT4we?3bT=7Fe<>{5( z2m2(c+hUz2BTHM8dCe*Z3XX&Av;b~a=$6EF>&^E8%nyxO@m_n!q&XD^A{SRjRZQ0L~qDeC=j&0$j6=LNIz@`ni^>ch|sv}^6 zlm>?28yPl@WmDPR?Y-A9X{U9Dv_IsbXJnzKCjkRksLOg#42uG2mE_acbTQ4)J|1V>%U@K(FP3AYhL0U zdeOCPN1qLv!|#c=p!_+%VNV(GHt`RuLRV^vz<5tt-r)yOK**kUWPspVAf|}ZL{LS= z@k(@@!P&W!>wwe`x{+GrFSWhHov7hu?{KuuT%kl#WO@*WX$i_@retlhQBj++SVNCx z5$78LxP>Z=^aJ)D280r_jj=zFfMJFXCIe^B{~V@d1rl_F(qo&AB4bC-vYL>x2jSKX zpuTG-6kgp3e^T&+dtV*i6a~)v@n?n*MffN59y}<0djUX zt27R+SE#hp8bzc#;rk$jw3r4)Q@eI$*`_)=Pvge8@8|8>H3X)<9YX6cXa=ii#Le;(qKm@%0-7$>2ShnYc`j#zJ7gu_FE^?uAkL|H)UIH#gPu^40!6^J=^ zr`}iwa^!4tzW~vOMZAaKF>*8A{^8m$i(VK)>?=#l`xrVe>wseSvM_aF zATNkY>kM_P3?1kE`uIq#mvr-wuTgUH0N<&JhF=(E9%^NS*HLm!4GZ4_XI zL=R5tlG5Mk_1rPfg)sk^llFuKPMPBhuU|L5q#yP_mzxp1o&pAzi-X31sgFpIHn@($ z_>=`AB5(8tP6p2zS5VEvH5J$M` z_much3>S7t3Yo`Yx!>83-hW9LYzDKP?mKdkD#QAK8*M((sx{eBQdrR<^3ZhFP81+& zBnJMUefQyNBji~$5d88Wfw1Lv59aJN9t2!pABLg;ewJ#LXL-10;QcJl+Y4Mtngb)k6JZlCf)3uD_u)J3sYyN;NN5hNbg$%W!i-GK%e&!Us)2IExWSss$YG(hm3kJ-h%yD z>8q^n$+4I(_y_mbT{du4P%h1j3oSpjhY97{+IZ`aA4ug!vNJ6*p?<2H(2w+GD3j$I z1TUXGyNzdf>_yB3grP~FZUs<2Quw;eEi*7s(-MiIkQ%@J^+WGdQvYSUN+TRiD-xto zJ=OUU+kxGYc!HCLNbCvR4lGTp~#L;DFzGd-#gJe*xf(P3hDQz|y)?b9mwU3WUVnpcqXM<@w%r-k*Wr^gzAv)8T^sqA=Ye z!7qy&exJmAcAt~CwS#@yNmjr8*T*!A6w4~E*ibaLRs0CFo(;R3=ODhDt6zWNodmo0 zXx&bT$6&+5c>a|WJ)F4G-^GjY0H#*tY=UNyYr_q5fsrcjk(c^~e*7Lf`!Jd`)p412 zn|^*hV= zFI4UbwA%X@smDd$cQOiMC%jfitTxTb+#`9`G=2rJDfK!E=5ra|So>lc{X1$~w28i+ z4p&cTGwZ#5VueiXS9O8#;RR$yg7tL9!^)Sz&pZYIzlSh}0}V{LxL$Cu%B4U5_}k}- zm~|CsD<076x@<>m=6w6N?WaThIBP`!u{-;WF)xc=2otx*lwf|5+MkdJePjh(B z9SH+%cHGCMAXNxB{_3^otDWdsV7Ob6n{0 z+&!(;iaHOX__5z_$Qk{%xYV%Ig@7iokGBwR`3642ZP#H#v9QGbWl8<|MS*=@qO@Uj z6+SZ_v9`1paUe5tFN~v(b#J3a_Lx0+;r9giZIx-A5TxdbG>xi#AZ5_z1V}B^n)sxT zz49}eK7EWb6wR!6-qQOrHQHkUvshvq%=G2d&@(#XM*Am1;WbnJ{X_!a{ZkphD$^TQ z=Iskb&}=lBm(RHiwJoGg`*NiQ6#RB$T#LF+>#ef;Jne&MxKPX!#r`&TVEFsp2jnNx>dClzpcPy&G&13a_<0qaR3i+k212~hoQ z8nMk{JP-t04I{GW5gUBqcJW-jSMrlw}>p)ptx?WKuCUV77taMiV zHok9V=6yv+Uts@fMY&A}amC=!Yj}eL@=e%XJ#%?agkt1jWF+10{(E9mHLDa>Ll7Vj zG=3cp%ljIB-6pC}6&`xJ*6WCP|IlglLWJ^?yviI8Ve)?V_i4%n;olzny62_`-|IGi z^=}p_O>Z8M;c4|RExu70E7ePW(HWVS&E$+LL6xSQgB`QfMQJ|4pCTFowA39p5P-|$ zUtM_H2HnP8_RoS~Vwk(FhbG zH41licj%=0a;Ln2STFBvU}Ne&O&%8bYKj!h1FA#sNM`232fX|U3QPp#3C?mN2;hE9 z;)!@5ixSPl<89^7gwhHc2YAX1KJK$#*3`KOMIQ253q7-*RJ5k)zp9GBO|Ga~X*^}US5oN@aG&waHV%vi~r{t^`ptTxb zL}q1W8S7*>7oWwvgV4uFLZ(@k`R*=LO_|Gu`prs~!WQXj-NLIa^2(7IHg>BG^N zc|i{-^=&Cek9dkJFQys|sjG9i>LLz|;yCv{^1i%c*h>8zF91kLvS9HBQi~ZU!JL`B zK8N+U0fr1*6??Ium)AF!6tc1eGhXIYL6IRT7rmKp7+>?%5Pa6zC5)KY$ycF0ZJ`G5nEQDG100U-jLkH8^UE4g6wq?sg%pP=-$&G#bcN`^?w3a6 z((s$6eRKcSEIslW-kk5Qi|5Mg-(xdLF}PxxVh$PuO}#aR6pW1kV4Af!Bqh*btXNNZ z>-4(IUl+L4dw+3LcpGut=qB45O+W)Q5?*zZ2A6rJcg`qkSvWA!j^r2mqKuCm6`Py? z@^T#Ux04HemPGd!Hs7NkZdVn1}8_j`o?)*OKZGS!`ff)gF zG?v-lj$wWNWCcw2Mg2o18D~1?3_b0XzdiKBNkYSDpcv@&kp0POmweJE2ZkIQ3B!a! zIgIoE+Xv?;34kyo^QYjZk+tEqZvq^#QG(OzX4~X+KtsoQoddTWUR(yo8R+ObEF1j<-syWOb>)JQ&Zbdu(sctU%Mt zW&YR0{ttY2TTXYZ?~WNU&cES1Z2q(7SrWDh``!J(JM+Nk$!hu&Y;(7E`ZNKTe0w+% zJc?Qnw2B+%UR}0;cB0Rufa(7-3FF}?629@LgTiEC&2uyL6NxexOp?AKT^aAx3gi(W zao>r>MPw0eQ3>IV02uLsC@>yK_epX6GRg4{NEL2wPPF9=*L2RV3yyK8DhuEK>rmmV z`&Q~#c`lgR&93TdOCja|ewOXmPNRh7!&dMT(1ett#iDr8HZW~VqWW@7fe9B6;7S+? zbC`d4@MEau&mKlOPKd>*10q0c{~^baw6!a*w^sY#0Xim{oOsiXiDOhbG&kl3c$$n1 zMRrD83&QucDSEcV*7LIp8VTA@F<%qe+_c`L;6on(>SjAU^}5c9!BCffT>$VQhe=)z z8(=Ej{5>jhmjB3{xDfj2R@VmHQ!CqjlO4KnuOmvHy3K#po$yp_V;p_MKjh1`(rzj6 zHW956k1yvntz{_g?Xbs`avK(IjlTnsu%htO;D7 z?J#x^EzuvVn&NA=!MEj7cwe5A-Z$Zk2LBZH$~%E* zf`((xH0?`}hs|HA%mtwfOEsZJxxrennkTYcwP#FKO5%Lpc^JXhSpV|ZH$Wr;`}`_( zIP==gd3LYyVtwD|*ZJGi{7~x8{=^bGVqu0RJ`n_BZH9+}kz%-4ZRsImi@rx%=ZEKs zcPnUXo6hbJV>fH;@1|bAHIe0ijYI*&kdT|HkDS$9No9 zCHo=*HWb~U+Dtzxr+Esao}6@|;Pf+E$ay0$kQp#s{wlw+7aIKbMdf`OqhoG*;Tco0 zjrP}VQG#Y2cJuqoJg&5({)S(BA}q9T1lGeWRyu=Je|)I!6a+aj!IP^1({)ZYe&x6w zt3a)Dq^TB+A7CdB0-}#z2Ur$W&h3YVw8==!xONy$uQmDWh-@15iEOt!q2m&?ZLA|w z8loSb(0}7y6Xu0?M5Uf4>VZGluB`wMf2oh;m)ghxVda>3m}4%V)r^0nVQ5V6f3>*) z0&VN!N0~GC^P}vj$`EDMZEmVV;N&RISY2C;$0;2(<{Lt&PKzqRByQdiEHGAbwtbS zPj`Da5%U6k1oEtVzI}QNw;!hT6F+~|@=c@$C4NtO@=xgP?|5MyZAyuCzcvq4rdAv@C06%gZ`9%I);R6UGiGJobfux+<0DLS&|MSG4UH z_~o{^^9>ixMg~mY!-@Fai{xaE4^;qy9iZN15Gbn5ZqHWf>Jc5Rv6(#n8`1NcCsdmG zab*dSXVPaE?)wCalD;$ivF%@nB#7D`@YG04p6ed9m}4iJW|pfVMLE<-c{=-8$e?cH zUdU#mCj4gb zZKA^b9p*9S(}8@tw~1RNPHr7tQr;P+-)D8|sq=*o)G%RGqt> zzP5yf`pVxb)I51D_G~Xp^GNK zVI6sAX)a9s)e{8N3?35YA6aQTXuyszK3ah~CemzA&CII#8F&F#KN41~8I^&_%}6MCNb{W87qAF`zj_Y^szhb> z3p3}KbOxotY|(lD=;)`fYE_*{S}x;f^SW#)SU&5X#o|-R|trpa|L5PS5aa0 zTHw8%SDSVtU4?vyrhnq+^@dgFS)|(y{~(4j%3UEiO-rBM9%`)8(dh33pMLiuurNY# z#10AsQ7%*0Cu_DSAU}P;X(JwA64~Q_^R%d_zSm^6Aux?Pn70PM>9EvLeOX z&w9c)pGmcL22;MO3C_B>=NC0RJpMp8?#ZUf=GWRvy z6RHq3B}=MGVg?9@iKFBpsvnkVh3{Vpp=`CcD=u~@ql{my|6?3ssi3mCOPnjI&E}VC zc@X+Yl>;;DNo0W0`0th!X{?luDhOC{E8N=?!w}K1{V=)+1={m(f`Oc|N=07>}3;z{-(A zm{JL=j?Sro5iecmE2-pWlRf(r%|HEQ7kgwQ9+kt=NBhtQI7OwcZ#3%$Uf%^r2nhjY zoQ08MfC%_X{O9~WcirMZMhn#z^ux4Erx-tf-6bHD)9eH&^L>^jvAd^9A^DCDs?0;k zkm7LE*KjP6`2d17MrQaaLqd_Rka}J$csvUec#hw78<=s(hyR>065~YCVCA9+#Q+; za(*L0IEw!r5P|@-;x33L$Lv9 zcuN8YG&g{<(SeJG18~(b!5yywSqQiLAX0;---;}mF5&b4lg|T?LwKREa{9YX_-zL@ZE?Zqi@HxK^2KO1>0LATu{te=T zprmHtY)bDVfxI1S}KBE7V zznP7KQ8HekWU#W6mw`dr-boV}pMQR==&5=Q5T=_q091jfc;R*jX#&=MQ%~@E@9^?`$v48ks<>(fI(F6L(5ppKy|$HWng*bKOb(4|cMUB&z$#ob#XV z5-mg)gmFIybZf=znm3ZPyUO^GJfxt0kmHjaTZ|sthsxXw&}Y)fOUSg=JhRSR^UjZ- zhqqb}Wsyw4zdnj6@#BAJa#-PdI4_dgafFXh85DsEQ_cT+5)XpZq$fZlBA_9UsE9r6 zEFec5?uqN@QhJ^IzwZrwl-5J`CmVPv{(YDTqEqWR^dI;5hXc~cxP%B3v&~s0`Ct89 z@S`i~a^c%V^N81dDT*ItFS*&IN;@O$EgzX0e7x&}TD=!zS}hTpezBLS>mdX(5< z)8DEI(-o_D)c-UX@dA1MuJ*yc>Hf4|`*B2S_O>w*-tbUwtiu`;W(Ud{HTty@(&x(T(F&;M zJ=?H>6`B7nf-90e8V`WSVp|0oEKB-P2M{}4ZDawzvM&a!y>`Y#jCsD%T_l``@ah(I2nJs~Q|%uSKu@k!m~*8B*IoA{*TgtF<(5sHCGG;n@NE%~Xt(G$^&<87u;}Na zx-8cq0g`uA(&RBFo=-4Y1GUZ<``Zw{xL4jfHkZw~%~wvtGueszcXt)_QwH8g!; z%s&3kSa~R$dO$-%L-)c@_hi7&>{6L_M>OZFkUQu;{sL_bUMStNrt{{&O(Wn~*zPOk zB>dnfszb29NSTf2pqIs68k|p-UrSrxgLHqi?3N-UFa!LHy9n1)=s>`yS+J{MEzS@ zNlfGtpma7kG&LR3JE@wB%rFA*h~~KitlO=IP)ZjN6dQLM6qsry zHkB#cyNh#n`)}bCrN1My*;k)^@>e4gJ`LJK?2)Pwp?4Tl4)4FA0(tvY+#1jOUM)xw zlMz4x-f@g^+yKUN`?Vu)|AwujArnM~Pa@y*Q9S8eS(u{-S%(Z5=R~pRl5ZGDjdqH% zC8rW&{##wOpU_oTIG4WXMk4&%2t1;lWcW5&!yxmOT*!hBcKyTqEcNoO+R2;Q?Yj+W z1-Y4?59fijz4(MIDwGe4-baYf08UCs;r|YefD-Md2ST;=cxwpgW=tR76-dQVAhn^= zG9Wk5lQk%jIR@KNU!UMp6@BfU;r+;y4VQ)D2!Il9HX%yW-9nOzV+m$YKzVaO`B8S7t z$!S2Mz`xw>V(RjE`0>bQp<0y&h~Y=M#jpy!#=dE>`=e_AjSZq6u!Dy1xJf~-7|0F! zPR9|n`e_7D2DIV2H(CESQ}hA>U>n|6`%z?YKEA~)BOVY%y=jPV zT=44R!L?J)736X#csn|lfBJ)o8ixaZclguWgrGO<`TN2FMfO}7;5}d+BlK0yTSH3* z4!=;5rOh85&2|x=46hkNaz?)U8&=bcfh=N_#8BNpZ2v$aVBo;sk^*X`v;4-LU;D>! zM*h12MxXIQy)SfAqE4;jY)wgnppazZkdNNVVF;(PLf^qK$FgY9+VFyBKE7UC|f z`R|?&egV11K3s$rJ6!GvoeW=jV*!-e(wA;x(2=d0E_e_%0x--0o8#~m^H1%AH5Z^B zn!TNPn927*bvaf0pt}zhK0o^V@WlGwwKo(*nQ|Q~4_;>~-8y20`HP>@UJa)3nEnGG z5Hwhs|FcmFG16ZVNb5hL`2Gc1{zWIMM{_OiKewV!hCi}U!VuE?s9wU-QbZ!)+Y^tS zGzp5OSi5iq6hmEr$w}&9DFgoB+i*`q`8TBi^MVS{SKEb8Aw%@K7@XCo(De2A`6%mf&a2#~y1N)+kJLD$1HCP!22)(U}xo2|j?WRzt(11j8Z_*v;P$R+Ug*Gy3VxV4K; zGGUGabnW*`Z}~`ydXL-l9e=GC$pY#z|63vy>E*m=$=j}iWP{sRTh0%H54`t>2xYH% zsk+M&u&pNgMCM@3e)Xc?jBWX-TIR_cQ1Z!RW7!B zBjZX=+^3}?SE)B+$EP+0oi1Fp5blDT?*}nsP>filqXH{ms zxU<$hetC`u)Wi+x|EKL-`y^#aQX+sDYIa{M;V%LqLrOk~lR>u0Q!+pyQSU4zY`?E^ z|5@)C)w6G_=i5YYC5SE_u(7hDNYr}uKT|@DSqF%S++lTIbIk^$a>{~0IH8KNFEy%+ zW#$&!ynpgNJh>6uR~?2c)ZMW+h0OKu231(7L_vETPaR+(P)Zy%0~yGm>E9?@@x!Jy z3PYgS}Q@b}x}E#F27@F+j}0=&Ql4gES&f8acMrPAVlVs9$97`FR))R5wI zc&}KFI1UIewh>3PkhnB7u zS3AT8_*|nexznG|Z*DU0c!K@jsI4J)5#DyNi#|e#`l1Vv1`1)*NVcy0LZ``aL0n8B zecupJ(rhq3u8bW0NIRhKYq$v1li+jp*4hfAd&wxYDE8vn1TQ7S@bTM|I2Ob z8vMOIxA7&_j{AKmD+O@EyXT`|dElt0pED^@IV0m)RPBUs*5jW60>>w1!@_G3aBKzG z_f(KfAPBk}-jQtR*Sroq!*3rbQ_m27e+YdzQjUb<_*k8vc_C)y!@cj5E>NxUhPu&g z@Z2<~esU`)ih+4opWe+K7sbN9n*9@n>#@n3*o z?xoROgDuvhq>jJ;Ve{6i<3roQNfgo5^4Q4(|GNExO2Dr7GjgA2zWuKp_K)K0R(6lv z!l$!zW-+T6mb3gQaAFviTQi{|*t%>{(mhTdy+y;Re4qT@kccy#{b z&zWy~kLO@>*WPj2k#H)|7L&gAJ37DmHQAme#@m;(Y8Nu^`D5vf8sZFW#+lA2!HK=( zJ)#hO6JD*`o~&c*&46d}g=Qj@SsoB5ikC z^1V8E+&<-OzuS_C`p5<<(A6fB`LXT(!kV^0_~hL6PpW4={l%|#xgdh?5EIk~lu8{D z2hiyhv3Yxij_#$Wu>P@7SYsl`-~3;}Ktx{34_NL^Kwin&=?!HDv3elQDbcU*qyYpN z(#yw~f1vFGK-t%CC-qa-4FYHbA^h>bag-I&*qaxwn?Qv|idE$<>1H|Gr6JtUu(he2$eg!N z@HTF@dG1)*y;4fxe)4_ZkpaBHH9hXp9p4|gLrRQyuevRd@gSS}JhRnWqrvm|U@>qM z=yl7RQROTKwQtzP3!zUF)_6Ld#NGA6v~2{J9Dd`h6{%+XsU#qGLh%`fB1Hc?wfayK zN`H4BpDp)npVQuu$DVW1qsBS&AJ2eP%6Qw>;k{)Z$8%HL=Q4(a$Ng2_vHw&vA!1L+9zc8vaX2GtqJ{L-;gvF0IR$em zMQ8@{Qp3+3Quk)TJ$?I<8KmwzD*7#(q<@Mc`dchngW}cRG14(Z6K7{T|LhFXwhqUQ;BET;cYqPcAcMgt6M$V9$(?jHo@Sud$an$U&5F zZ1QNh^ztt)E*d#Ij;<43oSKKnd+WNr$_r}+s_O_x6DZSB10*5Q{ourqq>mTl| zx4y^(cy+9;t@R=*j>3_dmm_m)$k$#937V(sllby&5)Xex^UD-|m|q<(jEd#@DV(of zAd7sSdmS*zUDqJ9|K%O2J2OfdUiK{{b{PCy)pi<;hp~7v1CQj&4-10 zgO<3dqhYH1#-Fa}Q{pjql5>>P6gZH21zLfxZ4$SK4T@7b!|`nWF9b*84Bq8&Eht;9 z*P72x&NUCZ7*@B$`FtE=hz5b}S`|c6Ey+j@D1ZibjJaRlR;{cxAWv z?Nqa>QqV*H-*zzaPvpLMHt~nl(x6?vrPpR?zn7~wow?oj*1TKmx4j71>$hvtC$DLD zUrz0^tiP0792U&dxJxNv@r}Elsjn^aSLUu=9#mD{&9n8|ayIL$!H3s>%KEvbchBFW z%cd?VU83mGF#Dar9*s~w&AnmQRQIOvR+uWsuZ?+|a=TzApXO@q^(r%8=}iv#wCnFq z=K9}JbqU@k99Q%j-}NNk+qLCP)jXfmOO|)@?mHcnynd6({mJisP1_}u7k)|eYHXWK z63eQ)E$ufFi!3CWUY2gw%e>omCv}qEX66aH-k&35f9`Q@Us|NPetVqe8=dX*VxJdn ze`q7b=Dn(UA(2sf&g)cOmQFhNJ#<-aMELJZbA#@to>25@kbW<)&!X01 z%NMJt>1ST)tyX)h@?`DxhbgCHr>S4wv}WC&Nw-!{+Z7$2D}74QAcXTvip=M0%Tp_N zor=k`)t|ra^ySr-+(|R9mB(E=`MX#y(wSw)$!iymzB;^c*>%&^*7HxTnRga=soSZT zdDl+9s;r!v8hk6POtzBaig4pRp7eWF(<8gufvNHPu6xs-=e{;mnHzJyGKE+8L0j}; z@%8-e^UCL5HhMiR>sD3Rve&yVZ#{Q1*CO8c+qSr^Z#CN;)(X5>tGG5yUw3<+CfhaL z%bP;hZ?jvgJU67BWyiy74_)6r)_nSxttxn0`0?HE^5(uydHVgP+HE$V?Lv)Leti43 zWA|;f-RqX``95>)^P-fw!Vi{3KNsII-*5f){gdxqd%gVdB1sOBNe=nEW%;i~g_P8J w!5uhoe-Jcg1nPN%MiEAtgE$;km@@t6ukO)1^!cY^83Pb_y85}Sb4q9e0FIsP9{>OV literal 14800 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 vNcLuCM3Rj?iBw@67X_p?_CF#jIyB-s com.apple.security.network.server + com.apple.security.network.client + diff --git a/example/macos/Runner/MainFlutterWindow.swift b/example/macos/Runner/MainFlutterWindow.swift index 22fb7aee..2722837e 100644 --- a/example/macos/Runner/MainFlutterWindow.swift +++ b/example/macos/Runner/MainFlutterWindow.swift @@ -1,82 +1,15 @@ import Cocoa import FlutterMacOS -class BlurryContainerViewController: NSViewController { - let flutterViewController = FlutterViewController() - - init() { - super.init(nibName: nil, bundle: nil) - } - - required init?(coder: NSCoder) { - fatalError() - } - - override func loadView() { - let blurView = NSVisualEffectView() - blurView.autoresizingMask = [.width, .height] - blurView.blendingMode = .behindWindow - blurView.state = .active - if #available(macOS 10.14, *) { - blurView.material = .sidebar - } - self.view = blurView - } - - override func viewDidLoad() { - super.viewDidLoad() - - self.addChild(flutterViewController) - - flutterViewController.view.frame = self.view.bounds - flutterViewController.backgroundColor = .clear - flutterViewController.view.autoresizingMask = [.width, .height] - self.view.addSubview(flutterViewController.view) - } -} - -class MainFlutterWindow: NSWindow, NSWindowDelegate { +class MainFlutterWindow: NSWindow { override func awakeFromNib() { - delegate = self - let blurryContainerViewController = BlurryContainerViewController() + let flutterViewController = FlutterViewController.init() let windowFrame = self.frame - self.contentViewController = blurryContainerViewController + self.contentViewController = flutterViewController self.setFrame(windowFrame, display: true) - if #available(macOS 10.13, *) { - let customToolbar = NSToolbar() - customToolbar.showsBaselineSeparator = false - self.toolbar = customToolbar - } - self.titleVisibility = .hidden - self.titlebarAppearsTransparent = true - if #available(macOS 11.0, *) { - // Use .expanded if the app will have a title bar, else use .unified - self.toolbarStyle = .unified - } - - self.isMovableByWindowBackground = true - self.styleMask.insert(NSWindow.StyleMask.fullSizeContentView) - - self.isOpaque = false - self.backgroundColor = .clear - - RegisterGeneratedPlugins(registry: blurryContainerViewController.flutterViewController) + RegisterGeneratedPlugins(registry: flutterViewController) super.awakeFromNib() } - - // Hides the toolbar when in fullscreen mode - func window(_ window: NSWindow, willUseFullScreenPresentationOptions proposedOptions: NSApplication.PresentationOptions = []) -> NSApplication.PresentationOptions { - - return [.autoHideToolbar, .autoHideMenuBar, .fullScreen] - } - - func windowWillEnterFullScreen(_ notification: Notification) { - self.toolbar?.isVisible = false - } - - func windowDidExitFullScreen(_ notification: Notification) { - self.toolbar?.isVisible = true - } } diff --git a/example/macos/RunnerTests/RunnerTests.swift b/example/macos/RunnerTests/RunnerTests.swift new file mode 100644 index 00000000..5418c9f5 --- /dev/null +++ b/example/macos/RunnerTests/RunnerTests.swift @@ -0,0 +1,12 @@ +import FlutterMacOS +import Cocoa +import XCTest + +class RunnerTests: XCTestCase { + + func testExample() { + // If you add code to the Runner application, consider adding tests here. + // See https://developer.apple.com/documentation/xctest for more information about using XCTest. + } + +} diff --git a/example/pubspec.lock b/example/pubspec.lock index cf8e59b8..dc586a2a 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -5,10 +5,10 @@ packages: dependency: transitive description: name: async - sha256: bfe67ef28df125b7dddcea62755991f807aa39a2492a23e1550161692950bbe0 + sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" url: "https://pub.dev" source: hosted - version: "2.10.0" + version: "2.11.0" boolean_selector: dependency: transitive description: @@ -21,10 +21,10 @@ packages: dependency: transitive description: name: characters - sha256: e6a326c8af69605aec75ed6c187d06b349707a27fbff8222ca9cc2cff167975c + sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" url: "https://pub.dev" source: hosted - version: "1.2.1" + version: "1.3.0" clock: dependency: transitive description: @@ -37,10 +37,18 @@ packages: dependency: transitive description: name: collection - sha256: cfc915e6923fe5ce6e153b0723c753045de46de1b4d63771530504004a45fae0 + sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c" + url: "https://pub.dev" + source: hosted + version: "1.17.1" + crypto: + dependency: transitive + description: + name: crypto + sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab url: "https://pub.dev" source: hosted - version: "1.17.0" + version: "3.0.3" cupertino_icons: dependency: "direct main" description: @@ -57,6 +65,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.1" + ffi: + dependency: transitive + description: + name: ffi + sha256: ed5337a5660c506388a9f012be0288fb38b49020ce2b45fe1f8b8323fe429f99 + url: "https://pub.dev" + source: hosted + version: "2.0.2" + file: + dependency: transitive + description: + name: file + sha256: "1b92bec4fc2a72f59a8e15af5f52cd441e4a7860b49499d69dfa817af20e925d" + url: "https://pub.dev" + source: hosted + version: "6.1.4" flutter: dependency: "direct main" description: flutter @@ -66,46 +90,83 @@ packages: dependency: "direct dev" description: name: flutter_lints - sha256: aeb0b80a8b3709709c9cc496cdc027c5b3216796bc0af0ce1007eaf24464fd4c + sha256: "2118df84ef0c3ca93f96123a616ae8540879991b8b57af2f81b76a7ada49b2a4" url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "2.0.2" flutter_test: dependency: "direct dev" description: flutter source: sdk version: "0.0.0" + flutter_web_plugins: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + google_fonts: + dependency: "direct main" + description: + name: google_fonts + sha256: e20ff62b158b96f392bfc8afe29dee1503c94fbea2cbe8186fd59b756b8ae982 + url: "https://pub.dev" + source: hosted + version: "5.1.0" + http: + dependency: transitive + description: + name: http + sha256: "759d1a329847dd0f39226c688d3e06a6b8679668e350e2891a6474f8b4bb8525" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + http_parser: + dependency: transitive + description: + name: http_parser + sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b" + url: "https://pub.dev" + source: hosted + version: "4.0.2" js: dependency: transitive description: name: js - sha256: "5528c2f391ededb7775ec1daa69e65a2d61276f7552de2b5f7b8d34ee9fd4ab7" + sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 url: "https://pub.dev" source: hosted - version: "0.6.5" + version: "0.6.7" lints: dependency: transitive description: name: lints - sha256: "5e4a9cd06d447758280a8ac2405101e0e2094d2a1dbdd3756aec3fe7775ba593" + sha256: "6b0206b0bf4f04961fc5438198ccb3a885685cd67d4d4a32cc20ad7f8adbe015" url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "2.1.0" macos_ui: dependency: "direct main" description: path: ".." relative: true source: path - version: "1.12.2" + version: "2.0.0" + macos_window_utils: + dependency: transitive + description: + name: macos_window_utils + sha256: b78a210aa70ca7ccad6e7b7b810fb4689c507f4a46e299214900b2a1eb70ea23 + url: "https://pub.dev" + source: hosted + version: "1.1.3" matcher: dependency: transitive description: name: matcher - sha256: "16db949ceee371e9b99d22f88fa3a73c4e59fd0afed0bd25fc336eb76c198b72" + sha256: "6501fbd55da300384b768785b83e5ce66991266cec21af89ab9ae7f5ce1c4cbb" url: "https://pub.dev" source: hosted - version: "0.12.13" + version: "0.12.15" material_color_utilities: dependency: transitive description: @@ -118,10 +179,10 @@ packages: dependency: transitive description: name: meta - sha256: "6c268b42ed578a53088d834796959e4a1814b5e9e164f147f580a386e5decf42" + sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3" url: "https://pub.dev" source: hosted - version: "1.8.0" + version: "1.9.1" nested: dependency: transitive description: @@ -134,10 +195,82 @@ packages: dependency: transitive description: name: path - sha256: db9d4f58c908a4ba5953fcee2ae317c94889433e5024c27ce74a37f94267945b + sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" + url: "https://pub.dev" + source: hosted + version: "1.8.3" + path_provider: + dependency: transitive + description: + name: path_provider + sha256: "3087813781ab814e4157b172f1a11c46be20179fcc9bea043e0fba36bc0acaa2" + url: "https://pub.dev" + source: hosted + version: "2.0.15" + path_provider_android: + dependency: transitive + description: + name: path_provider_android + sha256: "2cec049d282c7f13c594b4a73976b0b4f2d7a1838a6dd5aaf7bd9719196bee86" + url: "https://pub.dev" + source: hosted + version: "2.0.27" + path_provider_foundation: + dependency: transitive + description: + name: path_provider_foundation + sha256: "1995d88ec2948dac43edf8fe58eb434d35d22a2940ecee1a9fefcd62beee6eb3" + url: "https://pub.dev" + source: hosted + version: "2.2.3" + path_provider_linux: + dependency: transitive + description: + name: path_provider_linux + sha256: ffbb8cc9ed2c9ec0e4b7a541e56fd79b138e8f47d2fb86815f15358a349b3b57 + url: "https://pub.dev" + source: hosted + version: "2.1.11" + path_provider_platform_interface: + dependency: transitive + description: + name: path_provider_platform_interface + sha256: "57585299a729335f1298b43245842678cb9f43a6310351b18fb577d6e33165ec" + url: "https://pub.dev" + source: hosted + version: "2.0.6" + path_provider_windows: + dependency: transitive + description: + name: path_provider_windows + sha256: "1cb68ba4cd3a795033de62ba1b7b4564dace301f952de6bfb3cd91b202b6ee96" url: "https://pub.dev" source: hosted - version: "1.8.2" + version: "2.1.7" + platform: + dependency: transitive + description: + name: platform + sha256: "4a451831508d7d6ca779f7ac6e212b4023dd5a7d08a27a63da33756410e32b76" + url: "https://pub.dev" + source: hosted + version: "3.1.0" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + sha256: "6a2128648c854906c53fa8e33986fc0247a1116122f9534dd20e3ab9e16a32bc" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + process: + dependency: transitive + description: + name: process + sha256: "53fd8db9cec1d37b0574e12f07520d582019cb6c44abf5479a01505099a34a09" + url: "https://pub.dev" + source: hosted + version: "4.2.4" provider: dependency: "direct main" description: @@ -195,10 +328,82 @@ packages: dependency: transitive description: name: test_api - sha256: ad540f65f92caa91bf21dfc8ffb8c589d6e4dc0c2267818b4cc2792857706206 + sha256: eb6ac1540b26de412b3403a163d919ba86f6a973fe6cc50ae3541b80092fdcfb + url: "https://pub.dev" + source: hosted + version: "0.5.1" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c + url: "https://pub.dev" + source: hosted + version: "1.3.2" + url_launcher: + dependency: "direct main" + description: + name: url_launcher + sha256: "781bd58a1eb16069412365c98597726cd8810ae27435f04b3b4d3a470bacd61e" + url: "https://pub.dev" + source: hosted + version: "6.1.12" + url_launcher_android: + dependency: transitive + description: + name: url_launcher_android + sha256: "15f5acbf0dce90146a0f5a2c4a002b1814a6303c4c5c075aa2623b2d16156f03" + url: "https://pub.dev" + source: hosted + version: "6.0.36" + url_launcher_ios: + dependency: transitive + description: + name: url_launcher_ios + sha256: "9af7ea73259886b92199f9e42c116072f05ff9bea2dcb339ab935dfc957392c2" url: "https://pub.dev" source: hosted - version: "0.4.16" + version: "6.1.4" + url_launcher_linux: + dependency: transitive + description: + name: url_launcher_linux + sha256: "207f4ddda99b95b4d4868320a352d374b0b7e05eefad95a4a26f57da413443f5" + url: "https://pub.dev" + source: hosted + version: "3.0.5" + url_launcher_macos: + dependency: transitive + description: + name: url_launcher_macos + sha256: "1c4fdc0bfea61a70792ce97157e5cc17260f61abbe4f39354513f39ec6fd73b1" + url: "https://pub.dev" + source: hosted + version: "3.0.6" + url_launcher_platform_interface: + dependency: transitive + description: + name: url_launcher_platform_interface + sha256: bfdfa402f1f3298637d71ca8ecfe840b4696698213d5346e9d12d4ab647ee2ea + url: "https://pub.dev" + source: hosted + version: "2.1.3" + url_launcher_web: + dependency: transitive + description: + name: url_launcher_web + sha256: cc26720eefe98c1b71d85f9dc7ef0cada5132617046369d9dc296b3ecaa5cbb4 + url: "https://pub.dev" + source: hosted + version: "2.0.18" + url_launcher_windows: + dependency: transitive + description: + name: url_launcher_windows + sha256: "7967065dd2b5fccc18c653b97958fdf839c5478c28e767c61ee879f4e7882422" + url: "https://pub.dev" + source: hosted + version: "3.0.7" vector_math: dependency: transitive description: @@ -207,6 +412,22 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.4" + win32: + dependency: transitive + description: + name: win32 + sha256: dfdf0136e0aa7a1b474ea133e67cb0154a0acd2599c4f3ada3b49d38d38793ee + url: "https://pub.dev" + source: hosted + version: "5.0.5" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + sha256: ee1505df1426458f7f60aac270645098d318a8b4766d85fde75f76f2e21807d1 + url: "https://pub.dev" + source: hosted + version: "1.0.0" sdks: - dart: ">=2.18.0 <3.0.0" - flutter: ">=1.20.0" + dart: ">=3.0.0 <4.0.0" + flutter: ">=3.10.0" diff --git a/example/pubspec.yaml b/example/pubspec.yaml index e68c5535..0a008b51 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -4,7 +4,7 @@ publish_to: 'none' version: 1.0.0+1 environment: - sdk: '>=2.17.0 <3.0.0' + sdk: '>=3.0.0 <4.0.0' dependencies: flutter: @@ -13,12 +13,14 @@ dependencies: cupertino_icons: ^1.0.5 macos_ui: path: .. - provider: ^6.0.3 + provider: ^6.0.5 + google_fonts: ^5.1.0 + url_launcher: ^6.1.12 dev_dependencies: flutter_test: sdk: flutter - flutter_lints: ^2.0.1 + flutter_lints: ^2.0.2 flutter: assets: diff --git a/lib/macos_ui.dart b/lib/macos_ui.dart index 6a71ceec..62dcc602 100644 --- a/lib/macos_ui.dart +++ b/lib/macos_ui.dart @@ -14,6 +14,9 @@ library macos_ui; +export 'package:macos_window_utils/macos/ns_window_delegate.dart'; +export 'package:macos_window_utils/macos_window_utils.dart'; + export 'src/buttons/back_button.dart'; export 'src/buttons/checkbox.dart'; export 'src/buttons/disclosure_button.dart'; @@ -29,6 +32,7 @@ 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/enums/control_size.dart'; export 'src/fields/search_field.dart'; export 'src/fields/text_field.dart'; export 'src/icon/image_icon.dart'; @@ -37,7 +41,6 @@ export 'src/indicators/capacity_indicators.dart'; export 'src/indicators/progress_indicators.dart'; export 'src/indicators/rating_indicator.dart'; export 'src/indicators/relevance_indicator.dart'; -export 'src/layout/scrollbar.dart'; export 'src/indicators/slider.dart'; export 'src/labels/label.dart'; export 'src/labels/tooltip.dart'; @@ -45,6 +48,7 @@ export 'src/layout/content_area.dart'; export 'src/layout/macos_list_tile.dart'; export 'src/layout/resizable_pane.dart'; export 'src/layout/scaffold.dart'; +export 'src/layout/scrollbar.dart'; export 'src/layout/sidebar/sidebar.dart'; export 'src/layout/sidebar/sidebar_item.dart'; export 'src/layout/sidebar/sidebar_items.dart'; @@ -60,8 +64,10 @@ export 'src/layout/toolbar/toolbar_overflow_menu.dart'; export 'src/layout/toolbar/toolbar_overflow_menu_item.dart'; export 'src/layout/toolbar/toolbar_popup.dart'; export 'src/layout/toolbar/toolbar_spacer.dart'; +export 'src/layout/wallpaper_tinted_area.dart'; export 'src/layout/window.dart'; export 'src/macos_app.dart'; +export 'src/macos_window_utils_config.dart'; export 'src/selectors/color_well.dart'; export 'src/selectors/date_picker.dart'; export 'src/selectors/time_picker.dart'; diff --git a/lib/src/buttons/back_button.dart b/lib/src/buttons/back_button.dart index 5bce067b..605356a4 100644 --- a/lib/src/buttons/back_button.dart +++ b/lib/src/buttons/back_button.dart @@ -196,7 +196,7 @@ class MacosBackButtonState extends State : _isHovered ? hoverColor : fillColor, - borderRadius: BorderRadius.circular(7), + borderRadius: const BorderRadius.all(Radius.circular(7)), ), child: Icon( CupertinoIcons.back, diff --git a/lib/src/buttons/checkbox.dart b/lib/src/buttons/checkbox.dart index 93e0b1c0..d7f8bcc3 100644 --- a/lib/src/buttons/checkbox.dart +++ b/lib/src/buttons/checkbox.dart @@ -15,7 +15,7 @@ class MacosCheckbox extends StatelessWidget { super.key, required this.value, required this.onChanged, - this.size = 16.0, + this.size = 14.0, this.activeColor, this.disabledColor = CupertinoColors.quaternaryLabel, this.offBorderColor = CupertinoColors.tertiaryLabel, @@ -106,20 +106,57 @@ class MacosCheckbox extends StatelessWidget { : activeColor ?? theme.primaryColor, context, ), - borderRadius: BorderRadius.circular(4.0), + borderRadius: const BorderRadius.all(Radius.circular(4.0)), ) - : BoxDecoration( - color: isLight ? null : CupertinoColors.tertiaryLabel, - border: Border.all( - style: isLight ? BorderStyle.solid : BorderStyle.none, - width: 0.5, - color: MacosDynamicColor.resolve( - offBorderColor, - context, + : isLight + ? ShapeDecoration( + gradient: LinearGradient( + begin: const Alignment(0.0, -1.0), + end: const Alignment(0, 0), + colors: [ + Colors.white.withOpacity(0.85), + Colors.white.withOpacity(1.0), + ], + ), + shadows: const [ + BoxShadow( + color: Color(0x3F000000), + blurRadius: 1, + blurStyle: BlurStyle.inner, + offset: Offset(0, 0), + spreadRadius: 0.0, + ), + ], + shape: RoundedRectangleBorder( + side: BorderSide( + width: 0.25, + color: Colors.black.withOpacity(0.35000000596046448), + ), + borderRadius: + const BorderRadius.all(Radius.circular(3.5)), + ), + ) + : ShapeDecoration( + gradient: LinearGradient( + begin: const Alignment(0.0, -1.0), + end: const Alignment(0, 1), + colors: [ + Colors.white.withOpacity(0.14000000059604645), + Colors.white.withOpacity(0.2800000011920929), + ], + ), + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(3)), + ), + shadows: const [ + BoxShadow( + color: Color(0x3F000000), + blurRadius: 1, + offset: Offset(0, 0), + spreadRadius: 0, + ), + ], ), - ), - borderRadius: BorderRadius.circular(4.0), - ), child: Icon( isDisabled || value == false ? null diff --git a/lib/src/buttons/disclosure_button.dart b/lib/src/buttons/disclosure_button.dart index 3a8a3d6b..5cef6e0e 100644 --- a/lib/src/buttons/disclosure_button.dart +++ b/lib/src/buttons/disclosure_button.dart @@ -177,7 +177,7 @@ class MacosDisclosureButtonState extends State ? const MacosColor(0xff3C383C) : const MacosColor(0xffE5E5E5) : fillColor, - borderRadius: BorderRadius.circular(7), + borderRadius: const BorderRadius.all(Radius.circular(7)), ), child: RotatedBox( quarterTurns: widget.isPressed ? 1 : 3, diff --git a/lib/src/buttons/help_button.dart b/lib/src/buttons/help_button.dart index 0c5d5ece..f825baf5 100644 --- a/lib/src/buttons/help_button.dart +++ b/lib/src/buttons/help_button.dart @@ -190,6 +190,8 @@ class HelpButtonState extends State constraints: const BoxConstraints( minWidth: 20, minHeight: 20, + maxWidth: 20, + maxHeight: 20, ), child: FadeTransition( opacity: _opacityAnimation, @@ -212,16 +214,14 @@ class HelpButtonState extends State ), ], ), - child: Padding( - padding: const EdgeInsets.all(8), - child: Align( - alignment: widget.alignment, - widthFactor: 1.0, - heightFactor: 1.0, - child: Icon( - CupertinoIcons.question, - color: foregroundColor, - ), + child: Align( + alignment: widget.alignment, + widthFactor: 1.0, + heightFactor: 1.0, + child: Icon( + CupertinoIcons.question, + color: foregroundColor, + size: 13, ), ), ), diff --git a/lib/src/buttons/icon_button.dart b/lib/src/buttons/icon_button.dart index 9b339aa7..88e8ef20 100644 --- a/lib/src/buttons/icon_button.dart +++ b/lib/src/buttons/icon_button.dart @@ -253,7 +253,7 @@ class MacosIconButtonState extends State borderRadius: widget.borderRadius != null ? widget.borderRadius : widget.shape == BoxShape.rectangle - ? BorderRadius.circular(7.0) + ? const BorderRadius.all(Radius.circular(7)) : null, color: !enabled ? disabledColor diff --git a/lib/src/buttons/popup_button.dart b/lib/src/buttons/popup_button.dart index a08cb113..8158e63c 100644 --- a/lib/src/buttons/popup_button.dart +++ b/lib/src/buttons/popup_button.dart @@ -1091,8 +1091,7 @@ class _MacosPopupButtonState extends State> void _handleTap() { final TextDirection? textDirection = Directionality.maybeOf(context); - const EdgeInsetsGeometry menuMargin = - EdgeInsetsDirectional.only(start: 4.0, end: 4.0); + const EdgeInsetsGeometry menuMargin = EdgeInsets.symmetric(horizontal: 4.0); final List<_MenuItem> menuItems = <_MenuItem>[ for (int index = 0; index < widget.items!.length; index += 1) @@ -1239,7 +1238,7 @@ class _MacosPopupButtonState extends State> boxShadow: [ BoxShadow( color: buttonStyles.borderColor, - offset: const Offset(0, .5), + offset: const Offset(0, 0.5), blurRadius: 0.2, spreadRadius: 0, ), @@ -1251,7 +1250,7 @@ class _MacosPopupButtonState extends State> ), borderRadius: _kBorderRadius, ), - padding: const EdgeInsets.fromLTRB(8.0, 0.0, 2.0, 0.0), + padding: const EdgeInsets.only(left: 8.0, right: 2.0), height: _kPopupButtonHeight, child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, diff --git a/lib/src/buttons/pulldown_button.dart b/lib/src/buttons/pulldown_button.dart index dbe7e0d1..91de97a7 100644 --- a/lib/src/buttons/pulldown_button.dart +++ b/lib/src/buttons/pulldown_button.dart @@ -807,8 +807,7 @@ class _MacosPulldownButtonState extends State void _handleTap() { final TextDirection? textDirection = Directionality.maybeOf(context); - const EdgeInsetsGeometry menuMargin = - EdgeInsetsDirectional.only(start: 4.0, end: 4.0); + const EdgeInsetsGeometry menuMargin = EdgeInsets.symmetric(horizontal: 4.0); final List<_MenuItem> menuItems = <_MenuItem>[ for (int index = 0; index < widget.items!.length; index += 1) @@ -904,7 +903,7 @@ class _MacosPulldownButtonState extends State boxShadow: [ BoxShadow( color: buttonStyles.borderColor, - offset: const Offset(0, .5), + offset: const Offset(0, 0.5), blurRadius: 0.2, spreadRadius: 0, ), @@ -913,7 +912,7 @@ class _MacosPulldownButtonState extends State color: buttonStyles.bgColor, borderRadius: borderRadius, ), - padding: const EdgeInsets.fromLTRB(8.0, 0.0, 2.0, 0.0), + padding: const EdgeInsets.only(left: 8.0, right: 2.0), height: buttonHeight, child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, diff --git a/lib/src/buttons/push_button.dart b/lib/src/buttons/push_button.dart index 6f759c1d..19180016 100644 --- a/lib/src/buttons/push_button.dart +++ b/lib/src/buttons/push_button.dart @@ -5,36 +5,120 @@ 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, -} - -const EdgeInsetsGeometry _kSmallButtonPadding = EdgeInsets.symmetric( - vertical: 3.0, - horizontal: 8.0, +const _kMiniButtonSize = Size(26.0, 11.0); +const _kSmallButtonSize = Size(39.0, 14.0); +const _kRegularButtonSize = Size(60.0, 18.0); +const _kLargeButtonSize = Size(48.0, 26.0); + +const _kMiniButtonPadding = EdgeInsets.only(left: 6.0, right: 6.0, bottom: 1.0); +const _kSmallButtonPadding = EdgeInsets.symmetric( + vertical: 1.0, + horizontal: 7.0, ); -const EdgeInsetsGeometry _kLargeButtonPadding = EdgeInsets.symmetric( - vertical: 6.0, - horizontal: 8.0, +const _kRegularButtonPadding = EdgeInsets.only( + left: 8.0, + right: 8.0, + top: 1.0, + bottom: 4.0, ); +const _kLargeButtonPadding = EdgeInsets.only( + right: 8.0, + left: 8.0, + bottom: 1.0, +); + +const _kMiniButtonRadius = BorderRadius.all(Radius.circular(2.0)); +const _kSmallButtonRadius = BorderRadius.all(Radius.circular(2.0)); +const _kRegularButtonRadius = BorderRadius.all(Radius.circular(5.0)); +const _kLargeButtonRadius = BorderRadius.all(Radius.circular(7.0)); + +/// Shortcuts for various [PushButton] properties based on the [ControlSize]. +extension PushButtonControlSizeX on ControlSize { + /// Determines the padding of the button's text. + EdgeInsetsGeometry get padding { + switch (this) { + case ControlSize.mini: + return _kMiniButtonPadding; + case ControlSize.small: + return _kSmallButtonPadding; + case ControlSize.regular: + return _kRegularButtonPadding; + case ControlSize.large: + return _kLargeButtonPadding; + } + } + + /// Determines the button's border radius. + BorderRadiusGeometry get borderRadius { + switch (this) { + case ControlSize.mini: + return _kMiniButtonRadius; + case ControlSize.small: + return _kSmallButtonRadius; + case ControlSize.regular: + return _kRegularButtonRadius; + case ControlSize.large: + return _kLargeButtonRadius; + } + } + + /// Determines the styling of the button's text. + TextStyle textStyle(TextStyle baseStyle) { + switch (this) { + case ControlSize.mini: + return baseStyle.copyWith(fontSize: 9.0); + case ControlSize.small: + return baseStyle.copyWith(fontSize: 11.0); + case ControlSize.regular: + return baseStyle.copyWith(fontSize: 13.0); + case ControlSize.large: + return baseStyle; + } + } -const BorderRadius _kSmallButtonRadius = BorderRadius.all(Radius.circular(5.0)); -const BorderRadius _kLargeButtonRadius = BorderRadius.all(Radius.circular(7.0)); + /// Determines the button's minimum size. + BoxConstraints get constraints { + switch (this) { + case ControlSize.mini: + return BoxConstraints( + minHeight: _kMiniButtonSize.height, + minWidth: _kMiniButtonSize.width, + ); + case ControlSize.small: + return BoxConstraints( + minHeight: _kSmallButtonSize.height, + minWidth: _kSmallButtonSize.width, + ); + case ControlSize.regular: + return BoxConstraints( + minHeight: _kRegularButtonSize.height, + minWidth: _kRegularButtonSize.width, + ); + case ControlSize.large: + return BoxConstraints( + minHeight: _kLargeButtonSize.height, + minWidth: _kLargeButtonSize.width, + ); + } + } +} /// {@template pushButton} -/// A macOS-style button. +/// A control that initiates an action. +/// +/// Push Buttons are the standard button type in macOS. +/// +/// Reference: +/// * [Button (SwiftUI)](https://developer.apple.com/documentation/SwiftUI/Button) +/// * [NSButton (AppKit)](https://developer.apple.com/documentation/appkit/nsbutton) +/// * [Buttons (Human Interface Guidelines)](https://developer.apple.com/design/human-interface-guidelines/buttons) /// {@endtemplate} class PushButton extends StatefulWidget { /// {@macro pushButton} const PushButton({ super.key, required this.child, - required this.buttonSize, + required this.controlSize, this.padding, this.color, this.disabledColor, @@ -44,7 +128,7 @@ class PushButton extends StatefulWidget { this.alignment = Alignment.center, this.semanticLabel, this.mouseCursor = SystemMouseCursors.basic, - this.isSecondary, + this.secondary, }) : assert(pressedOpacity == null || (pressedOpacity >= 0.0 && pressedOpacity <= 1.0)); @@ -55,12 +139,8 @@ class PushButton extends StatefulWidget { /// The size of the button. /// - /// Must be either [ButtonSize.small] or [ButtonSize.large]. /// - /// Small buttons have a `padding` of [_kSmallButtonPadding] and a - /// `borderRadius` of [_kSmallButtonRadius]. Large buttons have a `padding` - /// of [_kLargeButtonPadding] and a `borderRadius` of [_kLargeButtonRadius]. - final ButtonSize buttonSize; + final ControlSize controlSize; /// The amount of space to surround the child inside the bounds of the button. /// @@ -116,8 +196,8 @@ class PushButton extends StatefulWidget { /// Whether the button is used as a secondary action button (e.g. Cancel buttons in dialogs) /// /// Sets its background color to [PushButtonThemeData]'s [secondaryColor] attributes (defaults - /// are gray colors). Can still be overriden if the [color] attribute is non-null. - final bool? isSecondary; + /// are gray colors). Can still be overridden if the [color] attribute is non-null. + final bool? secondary; /// Whether the button is enabled or disabled. Buttons are disabled by default. To /// enable a button, set its [onPressed] property to a non-null value. @@ -126,7 +206,7 @@ class PushButton extends StatefulWidget { @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); - properties.add(EnumProperty('buttonSize', buttonSize)); + properties.add(EnumProperty('controlSize', controlSize)); properties.add(ColorProperty('color', color)); properties.add(ColorProperty('disabledColor', disabledColor)); properties.add(DoubleProperty('pressedOpacity', pressedOpacity)); @@ -138,7 +218,7 @@ class PushButton extends StatefulWidget { value: enabled, ifFalse: 'disabled', )); - properties.add(DiagnosticsProperty('isSecondary', isSecondary)); + properties.add(DiagnosticsProperty('secondary', secondary)); } @override @@ -224,7 +304,7 @@ class PushButtonState extends State Widget build(BuildContext context) { assert(debugCheckHasMacosTheme(context)); final bool enabled = widget.enabled; - final bool isSecondary = widget.isSecondary != null && widget.isSecondary!; + final bool isSecondary = widget.secondary != null && widget.secondary!; final MacosThemeData theme = MacosTheme.of(context); final Color backgroundColor = MacosDynamicColor.resolve( widget.color ?? @@ -234,22 +314,9 @@ class PushButtonState extends State context, ); - final Color disabledColor = MacosDynamicColor.resolve( - widget.disabledColor ?? theme.pushButtonTheme.disabledColor!, - context, - ); - - final EdgeInsetsGeometry? buttonPadding = widget.padding == null - ? widget.buttonSize == ButtonSize.small - ? _kSmallButtonPadding - : _kLargeButtonPadding - : widget.padding; - - final BorderRadiusGeometry? borderRadius = widget.borderRadius == null - ? widget.buttonSize == ButtonSize.small - ? _kSmallButtonRadius - : _kLargeButtonRadius - : widget.borderRadius; + final disabledColor = !isSecondary + ? backgroundColor.withOpacity(0.5) + : backgroundColor.withOpacity(0.25); final Color foregroundColor = widget.enabled ? textLuminance(backgroundColor) @@ -257,8 +324,7 @@ class PushButtonState extends State ? const Color.fromRGBO(255, 255, 255, 0.25) : const Color.fromRGBO(0, 0, 0, 0.25); - final TextStyle textStyle = - theme.typography.headline.copyWith(color: foregroundColor); + final baseStyle = theme.typography.body.copyWith(color: foregroundColor); return MouseRegion( cursor: widget.mouseCursor!, @@ -272,25 +338,25 @@ class PushButtonState extends State button: true, label: widget.semanticLabel, child: ConstrainedBox( - constraints: const BoxConstraints( - minWidth: 49, - minHeight: 20, - ), + constraints: widget.controlSize.constraints, child: FadeTransition( opacity: _opacityAnimation, child: DecoratedBox( - decoration: BoxDecoration( - borderRadius: borderRadius, - color: !enabled ? disabledColor : backgroundColor, + decoration: ShapeDecoration( + shape: RoundedRectangleBorder( + borderRadius: widget.controlSize.borderRadius, + ), + // color: !enabled ? disabledColor : backgroundColor, + color: enabled ? backgroundColor : disabledColor, ), child: Padding( - padding: buttonPadding!, + padding: widget.controlSize.padding, child: Align( alignment: widget.alignment, widthFactor: 1.0, heightFactor: 1.0, child: DefaultTextStyle( - style: textStyle, + style: widget.controlSize.textStyle(baseStyle), child: widget.child, ), ), diff --git a/lib/src/buttons/segmented_control.dart b/lib/src/buttons/segmented_control.dart index 411fd8d1..45f36539 100644 --- a/lib/src/buttons/segmented_control.dart +++ b/lib/src/buttons/segmented_control.dart @@ -54,8 +54,8 @@ class _MacosSegmentedControlState extends State { const Color(0xFFDBDCDE), const Color(0xFF4F5155), ), - offset: const Offset(0, .5), - spreadRadius: .5, + offset: const Offset(0, 0.5), + spreadRadius: 0.5, ), ], borderRadius: const BorderRadius.all( diff --git a/lib/src/buttons/switch.dart b/lib/src/buttons/switch.dart index 6dd4375e..25d7f637 100644 --- a/lib/src/buttons/switch.dart +++ b/lib/src/buttons/switch.dart @@ -1,23 +1,48 @@ -import 'package:flutter/cupertino.dart' as c; -import 'package:flutter/foundation.dart'; +import 'dart:ui'; + import 'package:flutter/gestures.dart'; +import 'package:flutter/rendering.dart'; import 'package:macos_ui/macos_ui.dart'; import 'package:macos_ui/src/library.dart'; +const _kDefaultBorderColor = CupertinoDynamicColor.withBrightness( + color: MacosColor.fromRGBO(215, 215, 215, 1.0), + darkColor: MacosColor.fromRGBO(101, 101, 101, 1.0), +); + +const _kDefaultTrackColor = CupertinoDynamicColor.withBrightness( + color: MacosColor.fromRGBO(228, 226, 228, 1.0), + darkColor: MacosColor.fromRGBO(66, 66, 66, 1.0), +); + +// Dark color might be Color.fromRGBO(255, 255, 255, 0.721)?? +const _kDefaultKnobColor = CupertinoDynamicColor.withBrightness( + color: MacosColors.white, + darkColor: MacosColor.fromRGBO(207, 207, 207, 1.0), +); + /// {@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. +/// A switch is a control that offers a binary choice between two mutually +/// exclusive states — on and off. +/// +/// A switch shows that it's on when the [activeColor] is visible and off when +/// the [trackColor] is visible. +/// +/// Additional Reference: +/// * [Toggles (Human Interface Guidelines)](https://developer.apple.com/design/human-interface-guidelines/components/selection-and-input/toggles) +/// * [Toggles (Apple Developer)](https://developer.apple.com/documentation/swiftui/toggle) /// {@endtemplate} -class MacosSwitch extends StatelessWidget { +class MacosSwitch extends StatefulWidget { /// {@macro macosSwitch} const MacosSwitch({ super.key, required this.value, + this.size = ControlSize.regular, required this.onChanged, this.dragStartBehavior = DragStartBehavior.start, this.activeColor, this.trackColor, + this.knobColor, this.semanticLabel, }); @@ -26,6 +51,13 @@ class MacosSwitch extends StatelessWidget { /// Must not be null. final bool value; + /// The size of the switch, which is [ControlSize.regular] by default. + /// + /// Allowable sizes are [ControlSize.mini], [ControlSize.small], and + /// [ControlSize.regular]. If [ControlSize.large] is used, the switch will + /// size itself as a [ControlSize.regular] switch. + final ControlSize size; + /// Called when the user toggles with switch on or off. /// /// The switch passes the new value to the callback but does not actually @@ -39,7 +71,7 @@ class MacosSwitch extends StatelessWidget { /// gets rebuilt; for example: /// /// ```dart - /// Switch( + /// MacosSwitch( /// value: _giveVerse, /// onChanged: (bool newValue) { /// setState(() { @@ -53,19 +85,25 @@ class MacosSwitch extends StatelessWidget { /// {@macro flutter.cupertino.CupertinoSwitch.dragStartBehavior} final DragStartBehavior dragStartBehavior; - /// The color to use when this switch is on. + /// The color to use for the track when this switch is on. /// /// Defaults to [MacosThemeData.primaryColor] when null. - final Color? activeColor; + final MacosColor? activeColor; - /// The color to use for the background when the switch is off. + /// The color to use for track when this switch is off. /// - /// Defaults to [CupertinoColors.secondarySystemFill] when null. - final Color? trackColor; + /// Defaults to [MacosTheme.primaryColor] when null. + final MacosColor? trackColor; + + /// The color to use for the switch's knob. + final MacosColor? knobColor; /// The semantic label used by screen readers. final String? semanticLabel; + @override + State createState() => _MacosSwitchState(); + @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); @@ -74,6 +112,7 @@ class MacosSwitch extends StatelessWidget { value: value, ifFalse: 'unchecked', )); + properties.add(EnumProperty('size', size)); properties.add(EnumProperty('dragStartBehavior', dragStartBehavior)); properties.add(FlagProperty( 'enabled', @@ -82,26 +121,606 @@ class MacosSwitch extends StatelessWidget { )); properties.add(ColorProperty('activeColor', activeColor)); properties.add(ColorProperty('trackColor', trackColor)); + properties.add(ColorProperty('knobColor', knobColor)); properties.add(StringProperty('semanticLabel', semanticLabel)); } +} + +class _MacosSwitchState extends State + with TickerProviderStateMixin { + late TapGestureRecognizer _tap; + late HorizontalDragGestureRecognizer _drag; + + late AnimationController _positionController; + late CurvedAnimation position; + + late AnimationController _reactionController; + late Animation _reaction; + + bool get isInteractive => widget.onChanged != null; + + // A non-null boolean value that changes to true at the end of a drag if the + // switch must be animated to the position indicated by the widget's value. + bool needsPositionAnimation = false; + + @override + void initState() { + super.initState(); + + _tap = TapGestureRecognizer() + ..onTapDown = _handleTapDown + ..onTapUp = _handleTapUp + ..onTap = _handleTap + ..onTapCancel = _handleTapCancel; + _drag = HorizontalDragGestureRecognizer() + ..onStart = _handleDragStart + ..onUpdate = _handleDragUpdate + ..onEnd = _handleDragEnd + ..dragStartBehavior = widget.dragStartBehavior; + + _positionController = AnimationController( + duration: _kToggleDuration, + value: widget.value ? 1.0 : 0.0, + vsync: this, + ); + position = CurvedAnimation( + parent: _positionController, + curve: Curves.linear, + ); + _reactionController = AnimationController( + duration: _kReactionDuration, + vsync: this, + ); + _reaction = CurvedAnimation( + parent: _reactionController, + curve: Curves.ease, + ); + } + + @override + void didUpdateWidget(MacosSwitch oldWidget) { + super.didUpdateWidget(oldWidget); + _drag.dragStartBehavior = widget.dragStartBehavior; + + if (needsPositionAnimation || oldWidget.value != widget.value) { + _resumePositionAnimation(isLinear: needsPositionAnimation); + } + } + + // `isLinear` must be true if the position animation is trying to move the + // knob to the closest end after the most recent drag animation, so the curve + // does not change when the controller's value is not 0 or 1. + // + // It can be set to false when it's an implicit animation triggered by + // widget.value changes. + void _resumePositionAnimation({bool isLinear = true}) { + needsPositionAnimation = false; + position + ..curve = isLinear ? Curves.linear : Curves.ease + ..reverseCurve = isLinear ? Curves.linear : Curves.ease.flipped; + if (widget.value) { + _positionController.forward(); + } else { + _positionController.reverse(); + } + } + + void _handleTapDown(TapDownDetails details) { + if (isInteractive) { + needsPositionAnimation = false; + } + _reactionController.forward(); + } + + void _handleTap() { + if (isInteractive) { + widget.onChanged!(!widget.value); + } + } + + void _handleTapUp(TapUpDetails details) { + if (isInteractive) { + needsPositionAnimation = false; + _reactionController.reverse(); + } + } + + void _handleTapCancel() { + if (isInteractive) { + _reactionController.reverse(); + } + } + + void _handleDragStart(DragStartDetails details) { + if (isInteractive) { + needsPositionAnimation = false; + _reactionController.forward(); + } + } + + void _handleDragUpdate(DragUpdateDetails details) { + if (isInteractive) { + position + ..curve = Curves.linear + ..reverseCurve = Curves.linear; + final double delta = details.primaryDelta! / widget.size.trackInnerLength; + switch (Directionality.of(context)) { + case TextDirection.rtl: + _positionController.value -= delta; + break; + case TextDirection.ltr: + _positionController.value += delta; + break; + } + } + } + + void _handleDragEnd(DragEndDetails details) { + // Deferring the animation to the next build phase. + setState(() => needsPositionAnimation = true); + // Call onChanged when the user's intent to change value is clear. + if (position.value >= 0.5 != widget.value) { + widget.onChanged!(!widget.value); + } + _reactionController.reverse(); + } + + @override + void dispose() { + _tap.dispose(); + _drag.dispose(); + + _positionController.dispose(); + _reactionController.dispose(); + super.dispose(); + } @override Widget build(BuildContext context) { assert(debugCheckHasMacosTheme(context)); final MacosThemeData theme = MacosTheme.of(context); + MacosColor borderColor = + MacosDynamicColor.resolve(_kDefaultBorderColor, context).toMacosColor(); + MacosColor activeColor = MacosColor(MacosDynamicColor.resolve( + widget.activeColor ?? theme.primaryColor, + context, + ).value); + MacosColor trackColor = widget.trackColor ?? + MacosDynamicColor.resolve(_kDefaultTrackColor, context).toMacosColor(); + MacosColor knobColor = widget.knobColor ?? + MacosDynamicColor.resolve(_kDefaultKnobColor, context).toMacosColor(); + + // Shot in the dark to try and get the border color correct for each + // possible color + if (widget.value) { + if (theme.brightness.isDark) { + borderColor.computeLuminance() > 0.5 + ? borderColor = MacosColor.darken(activeColor, 20) + : borderColor = MacosColor.lighten(activeColor, 20); + } else { + borderColor.computeLuminance() > 0.5 + ? borderColor = MacosColor.darken(activeColor, 20) + : borderColor = MacosColor.lighten(activeColor, 20); + } + } + return Semantics( - label: semanticLabel, - checked: value, - child: c.CupertinoSwitch( - value: value, - onChanged: onChanged, - dragStartBehavior: dragStartBehavior, - activeColor: MacosDynamicColor.resolve( - activeColor ?? theme.primaryColor, - context, - ), + label: widget.semanticLabel, + checked: widget.value, + child: _MacosSwitchRenderObjectWidget( + value: widget.value, + size: widget.size, + activeColor: activeColor, trackColor: trackColor, + knobColor: knobColor, + borderColor: borderColor, + onChanged: widget.onChanged, + textDirection: Directionality.of(context), + state: this, ), ); } } + +class _MacosSwitchRenderObjectWidget extends LeafRenderObjectWidget { + const _MacosSwitchRenderObjectWidget({ + required this.value, + required this.size, + required this.activeColor, + required this.trackColor, + required this.knobColor, + required this.borderColor, + required this.onChanged, + required this.textDirection, + required this.state, + }); + final bool value; + final ControlSize size; + final MacosColor activeColor; + final MacosColor trackColor; + final MacosColor knobColor; + final MacosColor borderColor; + final ValueChanged? onChanged; + final TextDirection textDirection; + final _MacosSwitchState state; + + @override + _RenderMacosSwitch createRenderObject(BuildContext context) { + return _RenderMacosSwitch( + value: value, + size: size, + activeColor: activeColor, + trackColor: trackColor, + knobColor: knobColor, + borderColor: borderColor, + onChanged: onChanged, + textDirection: textDirection, + state: state, + ); + } + + @override + void updateRenderObject( + BuildContext context, + _RenderMacosSwitch renderObject, + ) { + assert(renderObject._state == state); + renderObject + ..value = value + ..controlSize = size + ..activeColor = activeColor + ..trackColor = trackColor + ..knobColor = knobColor + ..borderColor = borderColor + ..onChanged = onChanged + ..textDirection = textDirection; + } +} + +const Size _kMiniTrackSize = Size(26.0, 15.0); +const Size _kSmallTrackSize = Size(32.0, 18.0); +const Size _kRegularTrackSize = Size(38.0, 22.0); + +const double _kMiniKnobSize = 13.0; +const double _kSmallKnobSize = 16.0; +const double _kRegularKnobSize = 20.0; + +// Shortcuts for details about how to create the switch, based on the control +// size. +extension _ControlSizeX on ControlSize { + Size get trackSize { + switch (this) { + case ControlSize.mini: + return _kMiniTrackSize; + case ControlSize.small: + return _kSmallTrackSize; + default: + return _kRegularTrackSize; + } + } + + double get knobSize { + switch (this) { + case ControlSize.mini: + return _kMiniKnobSize; + case ControlSize.small: + return _kSmallKnobSize; + default: + return _kRegularKnobSize; + } + } + + double get knobRadius => knobSize / 2.0; + double get trackInnerStart => trackSize.height / 2.0; + double get trackInnerEnd => trackSize.width - trackInnerStart; + double get trackInnerLength => trackInnerEnd - trackInnerStart; +} + +const Duration _kReactionDuration = Duration(milliseconds: 400); +const Duration _kToggleDuration = Duration(milliseconds: 300); + +class _RenderMacosSwitch extends RenderConstrainedBox { + _RenderMacosSwitch({ + required bool value, + required ControlSize size, + required MacosColor activeColor, + required MacosColor trackColor, + required MacosColor knobColor, + required MacosColor borderColor, + required ValueChanged? onChanged, + required TextDirection textDirection, + required _MacosSwitchState state, + }) : _value = value, + _size = size, + _activeColor = activeColor, + _trackColor = trackColor, + _knobPainter = MacosSwitchKnobPainter(color: knobColor), + _borderColor = borderColor, + _onChanged = onChanged, + _textDirection = textDirection, + _state = state, + super( + additionalConstraints: BoxConstraints.tightFor( + width: size.trackSize.width, + height: size.trackSize.height, + ), + ) { + state.position.addListener(markNeedsPaint); + state._reaction.addListener(markNeedsPaint); + } + + final _MacosSwitchState _state; + + bool get value => _value; + bool _value; + set value(bool newValue) { + if (newValue == _value) { + return; + } + _value = newValue; + markNeedsSemanticsUpdate(); + } + + ControlSize get controlSize => _size; + ControlSize _size; + set controlSize(ControlSize value) { + if (value == _size) { + return; + } + _size = value; + markNeedsPaint(); + } + + MacosColor get activeColor => _activeColor; + MacosColor _activeColor; + set activeColor(MacosColor value) { + if (value == _activeColor) { + return; + } + _activeColor = value; + markNeedsPaint(); + } + + MacosColor get trackColor => _trackColor; + MacosColor _trackColor; + set trackColor(MacosColor value) { + if (value == _trackColor) { + return; + } + _trackColor = value; + markNeedsPaint(); + } + + MacosColor get knobColor => _knobPainter.color; + MacosSwitchKnobPainter _knobPainter; + set knobColor(MacosColor value) { + if (value == knobColor) { + return; + } + _knobPainter = MacosSwitchKnobPainter(color: value); + markNeedsPaint(); + } + + MacosColor get borderColor => _borderColor; + MacosColor _borderColor; + set borderColor(MacosColor value) { + if (value == borderColor) { + return; + } + _borderColor = value; + markNeedsPaint(); + } + + ValueChanged? get onChanged => _onChanged; + ValueChanged? _onChanged; + set onChanged(ValueChanged? value) { + if (value == _onChanged) { + return; + } + final bool wasInteractive = isInteractive; + _onChanged = value; + if (wasInteractive != isInteractive) { + markNeedsPaint(); + markNeedsSemanticsUpdate(); + } + } + + TextDirection get textDirection => _textDirection; + TextDirection _textDirection; + set textDirection(TextDirection value) { + if (value == _textDirection) { + return; + } + _textDirection = value; + markNeedsPaint(); + } + + bool get isInteractive => onChanged != null; + + @override + bool hitTestSelf(Offset position) => true; + + @override + void handleEvent(PointerEvent event, BoxHitTestEntry entry) { + assert(debugHandleEvent(event, entry)); + if (event is PointerDownEvent && isInteractive) { + _state._drag.addPointer(event); + _state._tap.addPointer(event); + } + } + + @override + void describeSemanticsConfiguration(SemanticsConfiguration config) { + super.describeSemanticsConfiguration(config); + + if (isInteractive) { + config.onTap = _state._handleTap; + } + + config.isEnabled = isInteractive; + config.isToggled = _value; + } + + @override + void paint(PaintingContext context, Offset offset) { + final Canvas canvas = context.canvas; + final double currentValue = _state.position.value; + final trackSize = controlSize.trackSize; + final innerStart = controlSize.trackInnerStart; + final innerEnd = controlSize.trackInnerEnd; + + final double visualPosition; + switch (textDirection) { + case TextDirection.rtl: + visualPosition = 1.0 - currentValue; + break; + case TextDirection.ltr: + visualPosition = currentValue; + break; + } + + final Paint paint = Paint() + ..color = MacosColor.lerp(trackColor, activeColor, currentValue); + + final Rect trackRect = Rect.fromLTWH( + offset.dx + (size.width - trackSize.width) / 2.0, + offset.dy + (size.height - trackSize.height) / 2.0, + trackSize.width, + trackSize.height, + ); + final RRect trackRRect = RRect.fromRectAndRadius( + trackRect, + Radius.circular(trackSize.height / 2.0), + ); + canvas.drawRRect(trackRRect, paint); + canvas.drawRRect( + trackRRect, + Paint() + ..color = borderColor + ..style = PaintingStyle.stroke, + ); + + final double knobLeft = lerpDouble( + trackRect.left + innerStart - controlSize.knobRadius, + trackRect.left + innerEnd - controlSize.knobRadius, + visualPosition, + )!; + final double knobRight = lerpDouble( + trackRect.left + innerStart + controlSize.knobRadius, + trackRect.left + innerEnd + controlSize.knobRadius, + visualPosition, + )!; + final double knobCenterY = offset.dy + size.height / 2.0; + final Rect knobBounds = Rect.fromLTRB( + knobLeft, + knobCenterY - controlSize.knobRadius, + knobRight, + knobCenterY + controlSize.knobRadius, + ); + + _clipRRectLayer.layer = context.pushClipRRect( + needsCompositing, + Offset.zero, + knobBounds, + trackRRect, + (PaintingContext innerContext, Offset offset) { + _knobPainter.paint( + innerContext.canvas, + knobBounds, + visualPosition == 1.0, + ); + }, + oldLayer: _clipRRectLayer.layer, + ); + } + + final LayerHandle _clipRRectLayer = + LayerHandle(); + + @override + void dispose() { + _clipRRectLayer.layer = null; + super.dispose(); + } + + @override + void debugFillProperties(DiagnosticPropertiesBuilder description) { + super.debugFillProperties(description); + description.add(FlagProperty( + 'value', + value: value, + ifTrue: 'checked', + ifFalse: 'unchecked', + showName: true, + )); + description.add(FlagProperty( + 'isInteractive', + value: isInteractive, + ifTrue: 'enabled', + ifFalse: 'disabled', + showName: true, + defaultValue: true, + )); + } +} + +const List _kSwitchOffBoxShadows = [ + BoxShadow( + color: Color(0x26000000), + // offset: Offset(1, 1), + blurRadius: 8.0, + blurStyle: BlurStyle.inner, + ), + BoxShadow( + color: Color(0x0F000000), + // offset: Offset(1, 1), + blurRadius: 1.0, + blurStyle: BlurStyle.inner, + ), +]; + +const List _kSwitchOnBoxShadows = [ + BoxShadow( + color: Color(0x26000000), + // offset: Offset(-3, 1), + blurRadius: 8.0, + blurStyle: BlurStyle.inner, + ), + BoxShadow( + color: Color(0x0F000000), + // offset: Offset(-1, 1), + blurRadius: 1.0, + blurStyle: BlurStyle.inner, + ), +]; + +/// Paints a macOS-style switch knob. +/// +/// Used by [MacosSwitch]. +class MacosSwitchKnobPainter { + /// Creates an object that paints a macOS-style switch knob. + const MacosSwitchKnobPainter({required this.color}); + + /// The color of the interior of the knob. + final MacosColor color; + + /// Paints the knob onto the given canvas in the given rectangle. + void paint(Canvas canvas, Rect rect, bool isOn) { + final RRect rrect = RRect.fromRectAndRadius( + rect, + Radius.circular(rect.shortestSide / 2.0), + ); + + if (isOn) { + for (final BoxShadow shadow in _kSwitchOnBoxShadows) { + canvas.drawRRect(rrect.shift(shadow.offset), shadow.toPaint()); + } + } else { + for (final BoxShadow shadow in _kSwitchOffBoxShadows) { + canvas.drawRRect(rrect.shift(shadow.offset), shadow.toPaint()); + } + } + + canvas.drawRRect(rrect, Paint()..color = color); + } +} diff --git a/lib/src/buttons/toolbar/toolbar_icon_button.dart b/lib/src/buttons/toolbar/toolbar_icon_button.dart index 3d94a1f3..4b165a3d 100644 --- a/lib/src/buttons/toolbar/toolbar_icon_button.dart +++ b/lib/src/buttons/toolbar/toolbar_icon_button.dart @@ -77,7 +77,7 @@ class ToolBarIconButton extends ToolbarItem { if (showLabel) { iconButton = Padding( - padding: const EdgeInsets.fromLTRB(6.0, 6.0, 6.0, 0.0), + padding: const EdgeInsets.only(left: 6.0, top: 6.0, right: 6.0), child: Column( children: [ iconButton, diff --git a/lib/src/dialogs/macos_alert_dialog.dart b/lib/src/dialogs/macos_alert_dialog.dart index 057f04df..0dab2588 100644 --- a/lib/src/dialogs/macos_alert_dialog.dart +++ b/lib/src/dialogs/macos_alert_dialog.dart @@ -3,6 +3,10 @@ import 'package:macos_ui/macos_ui.dart'; import 'package:macos_ui/src/library.dart'; const _kDialogBorderRadius = BorderRadius.all(Radius.circular(12.0)); +const _kDefaultDialogConstraints = BoxConstraints( + minWidth: 260, + maxWidth: 260, +); /// A macOS-style AlertDialog. /// @@ -17,14 +21,12 @@ const _kDialogBorderRadius = BorderRadius.all(Radius.circular(12.0)); /// appIcon: FlutterLogo( /// size: 56, /// ), -/// title: Text( -/// 'Alert Dialog with Primary Action', -/// ), +/// title: Text('Alert Dialog with Primary Action'), /// message: Text( /// 'This is an alert dialog with a primary action and no secondary action', /// ), /// primaryButton: PushButton( -/// buttonSize: ButtonSize.large, +/// controlSize: ControlSize.large, /// child: Text('Primary'), /// onPressed: Navigator.of(context).pop, /// ), @@ -46,7 +48,7 @@ class MacosAlertDialog extends StatelessWidget { /// This should be your application's icon. /// - /// The size of this widget should be 56x56. + /// The size of this widget should be 64x64. final Widget appIcon; /// The title for the dialog. @@ -61,13 +63,13 @@ class MacosAlertDialog extends StatelessWidget { /// The primary action a user can take. /// - /// Typically a [PushButton]. - final Widget primaryButton; + /// Must a [PushButton] with a [ControlSize] of `large`. + final PushButton primaryButton; /// The secondary action a user can take. /// - /// Typically a [PushButton]. - final Widget? secondaryButton; + /// Must a [PushButton] with a [ControlSize] of `large`. + final PushButton? secondaryButton; /// Determines whether to lay out [primaryButton] and [secondaryButton] /// horizontally or vertically. @@ -116,6 +118,11 @@ class MacosAlertDialog extends StatelessWidget { @override Widget build(BuildContext context) { assert(debugCheckHasMacosTheme(context)); + assert(primaryButton.controlSize == ControlSize.large); + if (secondaryButton != null) { + assert(secondaryButton is PushButton); + assert(secondaryButton!.controlSize == ControlSize.large); + } final brightness = MacosTheme.brightnessOf(context); final outerBorderColor = brightness.resolve( @@ -153,39 +160,35 @@ class MacosAlertDialog extends StatelessWidget { borderRadius: _kDialogBorderRadius, ), child: ConstrainedBox( - constraints: const BoxConstraints( - maxWidth: 260, - ), + constraints: _kDefaultDialogConstraints, child: Column( mainAxisSize: MainAxisSize.min, children: [ - const SizedBox(height: 28), + const SizedBox(height: 20), ConstrainedBox( constraints: const BoxConstraints( - maxHeight: 56, - maxWidth: 56, + maxHeight: 64, + maxWidth: 64, ), child: appIcon, ), - const SizedBox(height: 28), + const SizedBox(height: 16), DefaultTextStyle( style: MacosTheme.of(context).typography.headline, textAlign: TextAlign.center, child: title, ), - const SizedBox(height: 16), + const SizedBox(height: 10), DefaultTextStyle( textAlign: TextAlign.center, style: MacosTheme.of(context).typography.headline, child: message, ), - const SizedBox(height: 18), + const SizedBox(height: 16), if (secondaryButton == null) ...[ Row( children: [ - Expanded( - child: primaryButton, - ), + Expanded(child: primaryButton), ], ), ] else ...[ @@ -193,9 +196,7 @@ class MacosAlertDialog extends StatelessWidget { Row( children: [ if (secondaryButton != null) ...[ - Expanded( - child: secondaryButton!, - ), + Expanded(child: secondaryButton!), const SizedBox(width: 8.0), ], Expanded( diff --git a/lib/src/enums/control_size.dart b/lib/src/enums/control_size.dart new file mode 100644 index 00000000..835812bf --- /dev/null +++ b/lib/src/enums/control_size.dart @@ -0,0 +1,25 @@ +/// The out-of-the-box sizes that certain "control" widgets can be. +/// +/// +/// +/// Not all controls support all sizes. For example, a [PushButton] can be any +/// size, but a [MacosSwitch] can be all but large. In cases where a control +/// doesn't support a certain size, the control will automatically fall back to +/// the nearest supported size. +/// +/// Reference: +/// * https://developer.apple.com/documentation/swiftui/controlsize +/// * https://developer.apple.com/documentation/swiftui/view/controlsize(_:) +enum ControlSize { + /// A control that is minimally sized. + mini, + + /// A control that is proportionally smaller size for space-constrained views. + small, + + /// A control that is the default size. + regular, + + /// A control that is prominently sized. + large, +} diff --git a/lib/src/fields/text_field.dart b/lib/src/fields/text_field.dart index 9387b029..8564301d 100644 --- a/lib/src/fields/text_field.dart +++ b/lib/src/fields/text_field.dart @@ -105,7 +105,7 @@ class _TextFieldSelectionGestureDetectorBuilder final _MacosTextFieldState _state; @override - void onSingleTapUp(TapUpDetails details) { + void onSingleTapUp(TapDragUpDetails details) { // Because TextSelectionGestureDetector listens to taps that happen on // widgets in front of it, tapping the clear button will also trigger // this handler. If the clear button widget recognizes the up event, @@ -124,11 +124,14 @@ class _TextFieldSelectionGestureDetectorBuilder } _state._requestKeyboard(); if (_state.widget.onTap != null) _state.widget.onTap!(); + + super.onSingleTapUp(details); } @override - void onDragSelectionEnd(DragEndDetails details) { + void onDragSelectionEnd(TapDragEndDetails details) { _state._requestKeyboard(); + super.onDragSelectionEnd(details); } } @@ -358,7 +361,7 @@ class MacosTextField extends StatefulWidget { this.focusNode, this.decoration, this.focusedDecoration, - this.padding = const EdgeInsets.fromLTRB(2.0, 4.0, 2.0, 4.0), + this.padding = const EdgeInsets.symmetric(horizontal: 2.0, vertical: 4.0), this.placeholder, this.placeholderStyle = _kDefaultPlaceholderStyle, this.prefix, @@ -756,7 +759,7 @@ class MacosTextField extends StatefulWidget { 'clearButtonMode', clearButtonMode, )); - properties.add(EnumProperty( + properties.add(DiagnosticsProperty( 'keyboardType', keyboardType, defaultValue: TextInputType.text, @@ -1157,6 +1160,7 @@ class _MacosTextFieldState extends State void dispose() { _focusNode?.dispose(); _controller?.dispose(); + _effectiveFocusNode.removeListener(_handleFocusChanged); super.dispose(); } diff --git a/lib/src/indicators/progress_indicators.dart b/lib/src/indicators/progress_indicators.dart index 9bc9196e..6fe47cd9 100644 --- a/lib/src/indicators/progress_indicators.dart +++ b/lib/src/indicators/progress_indicators.dart @@ -107,7 +107,7 @@ class _DeterminateCirclePainter extends CustomPainter { final Color? borderColor; static const double _twoPi = math.pi * 2.0; - static const double _epsilon = .001; + static const double _epsilon = 0.001; static const double _sweep = _twoPi - _epsilon; static const double _startAngle = -math.pi / 2.0; @@ -240,7 +240,7 @@ class _DeterminateBarPainter extends CustomPainter { void paint(Canvas canvas, Size size) { // Draw the background line canvas.drawRRect( - BorderRadius.circular(100).toRRect( + const BorderRadius.all(Radius.circular(100)).toRRect( Offset.zero & size, ), Paint() diff --git a/lib/src/indicators/relevance_indicator.dart b/lib/src/indicators/relevance_indicator.dart index fa4d6e19..5d8d0dd2 100644 --- a/lib/src/indicators/relevance_indicator.dart +++ b/lib/src/indicators/relevance_indicator.dart @@ -5,6 +5,7 @@ import 'package:macos_ui/src/library.dart'; /// A relevance indicator communicates relevancy using a series /// of vertical bars. It often appears in a list of search results /// for reference when sorting and comparing multiple items. +@Deprecated('Apple no longer supports this component.') class RelevanceIndicator extends StatelessWidget { /// Creates a relevance indicator. /// diff --git a/lib/src/indicators/slider.dart b/lib/src/indicators/slider.dart index 8a172c2e..9e5e7514 100644 --- a/lib/src/indicators/slider.dart +++ b/lib/src/indicators/slider.dart @@ -178,8 +178,9 @@ class MacosSlider extends StatelessWidget { backgroundColor, context, ), - borderRadius: - BorderRadius.circular(_kSliderBorderRadius), + borderRadius: const BorderRadius.all( + Radius.circular(_kSliderBorderRadius), + ), ), ), ), @@ -192,8 +193,9 @@ class MacosSlider extends StatelessWidget { width: width * _percentage, decoration: BoxDecoration( color: MacosDynamicColor.resolve(color, context), - borderRadius: - BorderRadius.circular(_kSliderBorderRadius), + borderRadius: const BorderRadius.all( + Radius.circular(_kSliderBorderRadius), + ), ), ), ), @@ -273,7 +275,8 @@ class _ContinuousThumb extends StatelessWidget { width: _kContinuousThumbSize, decoration: BoxDecoration( color: color, - borderRadius: BorderRadius.circular(_kContinuousThumbSize), + borderRadius: + const BorderRadius.all(Radius.circular(_kContinuousThumbSize)), boxShadow: const [ BoxShadow( color: Color.fromRGBO(0, 0, 0, 0.1), @@ -300,7 +303,9 @@ class _DiscreteThumb extends StatelessWidget { width: _kDiscreteThumbWidth, decoration: BoxDecoration( color: color, - borderRadius: BorderRadius.circular(_kDiscreteThumbBorderRadius), + borderRadius: const BorderRadius.all( + Radius.circular(_kDiscreteThumbBorderRadius), + ), boxShadow: const [ BoxShadow( color: Color.fromRGBO(0, 0, 0, 0.1), diff --git a/lib/src/layout/resizable_pane.dart b/lib/src/layout/resizable_pane.dart index 68ccfbed..97836bdd 100644 --- a/lib/src/layout/resizable_pane.dart +++ b/lib/src/layout/resizable_pane.dart @@ -30,10 +30,15 @@ enum ResizableSide { /// The [startSize] is the initial width or height depending on the orientation of the pane. /// {@endtemplate} class ResizablePane extends StatefulWidget { - /// {@macro resizablePane} + /// Creates a [ResizablePane] with an internal [MacosScrollbar]. + /// + /// Consider using [ResizablePane.noScrollBar] constructor when the internal + /// [MacosScrollbar] is not needed or when working with widgets which do not + /// expose their scroll controllers. + /// {@macro resizablePane}. const ResizablePane({ super.key, - required this.builder, + required ScrollableWidgetBuilder this.builder, this.decoration, this.maxSize = 500.0, required this.minSize, @@ -41,7 +46,38 @@ class ResizablePane extends StatefulWidget { required this.resizableSide, this.windowBreakpoint, required this.startSize, - }) : assert( + }) : child = null, + useScrollBar = true, + assert( + maxSize >= minSize, + 'minSize should not be more than maxSize.', + ), + assert( + (startSize >= minSize) && (startSize <= maxSize), + 'startSize must not be less than minSize or more than maxWidth', + ); + + /// Creates a [ResizablePane] without an internal [MacosScrollbar]. + /// + /// Useful when working with widgets which do not expose their scroll + /// controllers or when not using the platform scroll bar is preferred. + /// + /// Consider using the default constructor if showing a [MacosScrollbar] + /// when scrolling the content of this widget is the expected behavior. + /// {@macro resizablePane}. + const ResizablePane.noScrollBar({ + super.key, + required Widget this.child, + this.decoration, + this.maxSize = 500.0, + required this.minSize, + this.isResizable = true, + required this.resizableSide, + this.windowBreakpoint, + required this.startSize, + }) : builder = null, + useScrollBar = false, + assert( maxSize >= minSize, 'minSize should not be more than maxSize.', ), @@ -55,7 +91,15 @@ class ResizablePane extends StatefulWidget { /// /// Pass the [scrollController] obtained from this method, to a scrollable /// widget used in this method to work with the internal [MacosScrollbar]. - final ScrollableWidgetBuilder builder; + final ScrollableWidgetBuilder? builder; + + /// The child to display in this widget. + /// + /// This is only referenced when the constructor used is [ResizablePane.noScrollbar]. + final Widget? child; + + /// Specify if this [ResizablePane] should have an internal [MacosScrollbar]. + final bool useScrollBar; /// The [BoxDecoration] to paint behind the child in the [builder]. final BoxDecoration? decoration; @@ -238,10 +282,8 @@ class _ResizablePaneState extends State { oldWidget.minSize != widget.minSize || oldWidget.maxSize != widget.maxSize || oldWidget.resizableSide != widget.resizableSide) { - setState(() { - if (widget.minSize > _size) _size = widget.minSize; - if (widget.maxSize < _size) _size = widget.maxSize; - }); + if (widget.minSize > _size) _size = widget.minSize; + if (widget.maxSize < _size) _size = widget.maxSize; } } @@ -279,10 +321,12 @@ class _ResizablePaneState extends State { SafeArea( left: false, right: false, - child: MacosScrollbar( - controller: _scrollController, - child: widget.builder(context, _scrollController), - ), + child: widget.useScrollBar + ? MacosScrollbar( + controller: _scrollController, + child: widget.builder!(context, _scrollController), + ) + : widget.child!, ), if (widget.isResizable && !_resizeOnRight && !_resizeOnTop) Positioned( diff --git a/lib/src/layout/scaffold.dart b/lib/src/layout/scaffold.dart index 712ab1a7..59678372 100644 --- a/lib/src/layout/scaffold.dart +++ b/lib/src/layout/scaffold.dart @@ -1,14 +1,9 @@ import 'dart:math' as math; +import 'package:flutter/foundation.dart'; import 'package:flutter/rendering.dart'; -import 'package:macos_ui/src/layout/content_area.dart'; -import 'package:macos_ui/src/layout/resizable_pane.dart'; -import 'package:macos_ui/src/layout/sidebar/sidebar.dart'; -import 'package:macos_ui/src/layout/title_bar.dart'; -import 'package:macos_ui/src/layout/toolbar/toolbar.dart'; -import 'package:macos_ui/src/layout/window.dart'; +import 'package:macos_ui/macos_ui.dart'; import 'package:macos_ui/src/library.dart'; -import 'package:macos_ui/src/theme/macos_theme.dart'; /// A macOS page widget. /// @@ -69,7 +64,7 @@ class _MacosScaffoldState extends State { ); final MacosThemeData theme = MacosTheme.of(context); - late Color backgroundColor = widget.backgroundColor ?? theme.canvasColor; + Color backgroundColor = widget.backgroundColor ?? theme.canvasColor; return LayoutBuilder( builder: (context, constraints) { @@ -82,23 +77,42 @@ class _MacosScaffoldState extends State { return Stack( children: [ - // Background color - Positioned.fill( - child: ColoredBox(color: backgroundColor), - ), - - // Content Area - Positioned( - top: 0, - width: width, - height: height, - child: MediaQuery( - data: mediaQuery.copyWith( - padding: EdgeInsets.only(top: topPadding), + if (!kIsWeb) ...[ + // Content Area + Positioned( + top: 0, + width: width, + height: height, + child: WallpaperTintedArea( + backgroundColor: backgroundColor, + insertRepaintBoundary: true, + child: MediaQuery( + data: mediaQuery.copyWith( + padding: EdgeInsets.only(top: topPadding), + ), + child: _ScaffoldBody(children: children), + ), + ), + ), + ] else ...[ + // Background color + Positioned.fill( + child: ColoredBox(color: backgroundColor), + ), + + // Content Area + Positioned( + top: 0, + width: width, + height: height, + child: MediaQuery( + data: mediaQuery.copyWith( + padding: EdgeInsets.only(top: topPadding), + ), + child: _ScaffoldBody(children: children), ), - child: _ScaffoldBody(children: children), ), - ), + ], // Toolbar if (widget.toolBar != null) @@ -115,7 +129,7 @@ class _MacosScaffoldState extends State { } class _ScaffoldBody extends MultiChildRenderObjectWidget { - _ScaffoldBody({ + const _ScaffoldBody({ super.children, }); diff --git a/lib/src/layout/scrollbar.dart b/lib/src/layout/scrollbar.dart index 334aced5..1cfc819b 100644 --- a/lib/src/layout/scrollbar.dart +++ b/lib/src/layout/scrollbar.dart @@ -156,7 +156,7 @@ class _RawMacosScrollBarState extends RawScrollbarState<_RawMacosScrollBar> { ); _trackColorTween = ColorTween( begin: MacosColors.transparent, - end: widget.effectiveThumbColor.withOpacity(.15), + end: widget.effectiveThumbColor.withOpacity(0.15), ).animate(_trackColorAnimationController); _thumbThicknessAnimationController.addListener(() { updateScrollbarPainter(); diff --git a/lib/src/layout/sidebar/sidebar_items.dart b/lib/src/layout/sidebar/sidebar_items.dart index f5954951..65b3dadb 100644 --- a/lib/src/layout/sidebar/sidebar_items.dart +++ b/lib/src/layout/sidebar/sidebar_items.dart @@ -206,9 +206,7 @@ class _SidebarItem extends StatelessWidget { /// Typically a [Navigator] call final VoidCallback? onClick; - void _handleActionTap() async { - onClick?.call(); - } + void _handleActionTap() => onClick?.call(); Map> get _actionMap => >{ ActivateIntent: CallbackAction( @@ -286,9 +284,8 @@ class _SidebarItem extends StatelessWidget { padding: EdgeInsets.only(right: spacing), child: MacosIconTheme.merge( data: MacosIconThemeData( - color: selected - ? MacosColors.white - : theme.primaryColor, + color: + selected ? MacosColors.white : theme.primaryColor, size: itemSize.iconSize, ), child: item.leading!, diff --git a/lib/src/layout/toolbar/sliver_toolbar.dart b/lib/src/layout/toolbar/sliver_toolbar.dart index 8ec1b087..a96d571b 100644 --- a/lib/src/layout/toolbar/sliver_toolbar.dart +++ b/lib/src/layout/toolbar/sliver_toolbar.dart @@ -40,6 +40,7 @@ class SliverToolBar extends StatefulWidget with Diagnosticable { this.pinned = true, this.floating = false, this.toolbarOpacity = 0.9, + this.allowWallpaperTintingOverrides = true, }); /// Specifies the height of this [ToolBar]. @@ -138,6 +139,30 @@ class SliverToolBar extends StatefulWidget with Diagnosticable { /// Defaults to `0.9`. final double toolbarOpacity; + /// Whether this [SliverToolBar] is allowed to perform wallpaper tinting + /// overrides. + /// + /// This property is supposed to be set to true when this [SliverToolBar] is + /// currently visible on the screen (that is, not e.g. hidden by an + /// [IndexedStack]). + /// + /// By default, macos_ui applies wallpaper tinting to the application's + /// window to match macOS' native appearance: + /// + /// + /// + /// However, this effect is realized by inserting `NSVisualEffectView`s behind + /// Flutter's canvas and turning the background of areas that are meant to be + /// affected by wallpaper tinting transparent. Since Flutter's + /// [`ImageFilter.blur`](https://api.flutter.dev/flutter/dart-ui/ImageFilter/ImageFilter.blur.html) + /// does not support transparency, wallpaper tinting is disabled automatically + /// when this widget's [allowWallpaperTintingOverrides] is true. + /// + /// This is meant to be a temporary solution until + /// [#16296](https://github.com/flutter/flutter/issues/16296) is resolved in + /// the Flutter project. + final bool allowWallpaperTintingOverrides; + @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); @@ -204,6 +229,7 @@ class _SliverToolBarState extends State floating: widget.floating, pinned: widget.pinned, toolbarOpacity: widget.toolbarOpacity, + allowWallpaperTintingOverrides: widget.allowWallpaperTintingOverrides, vsync: this, ), ), @@ -228,6 +254,7 @@ class _SliverToolBarDelegate extends SliverPersistentHeaderDelegate { required this.floating, required this.pinned, required this.toolbarOpacity, + required this.allowWallpaperTintingOverrides, }); final double height; @@ -244,6 +271,7 @@ class _SliverToolBarDelegate extends SliverPersistentHeaderDelegate { final bool floating; final bool pinned; final double toolbarOpacity; + final bool allowWallpaperTintingOverrides; @override double get minExtent => _kToolbarHeight; @@ -275,7 +303,7 @@ class _SliverToolBarDelegate extends SliverPersistentHeaderDelegate { ); } - final Widget toolBar = FlexibleSpaceBar.createSettings( + return FlexibleSpaceBar.createSettings( minExtent: minExtent, maxExtent: maxExtent, currentExtent: math.max(minExtent, maxExtent - shrinkOffset), @@ -293,9 +321,10 @@ class _SliverToolBarDelegate extends SliverPersistentHeaderDelegate { dividerColor: dividerColor, alignment: alignment, height: height, + enableBlur: true, + allowWallpaperTintingOverrides: allowWallpaperTintingOverrides, ), ); - return toolBar; } @override @@ -312,6 +341,8 @@ class _SliverToolBarDelegate extends SliverPersistentHeaderDelegate { centerTitle != oldDelegate.centerTitle || dividerColor != oldDelegate.dividerColor || floating != oldDelegate.floating || - pinned != oldDelegate.pinned; + pinned != oldDelegate.pinned || + allowWallpaperTintingOverrides != + oldDelegate.allowWallpaperTintingOverrides; } } diff --git a/lib/src/layout/toolbar/toolbar.dart b/lib/src/layout/toolbar/toolbar.dart index a9685f6a..efd62e3c 100644 --- a/lib/src/layout/toolbar/toolbar.dart +++ b/lib/src/layout/toolbar/toolbar.dart @@ -3,6 +3,7 @@ import 'dart:ui'; import 'package:flutter/foundation.dart'; import 'package:macos_ui/macos_ui.dart'; import 'package:macos_ui/src/layout/toolbar/overflow_handler.dart'; +import 'package:macos_ui/src/layout/wallpaper_tinting_settings/wallpaper_tinting_override.dart'; import 'package:macos_ui/src/library.dart'; /// Defines the height of a regular-sized [ToolBar] @@ -43,6 +44,8 @@ class ToolBar extends StatefulWidget with Diagnosticable { this.actions, this.centerTitle = false, this.dividerColor, + this.allowWallpaperTintingOverrides = true, + this.enableBlur = false, }); /// Specifies the height of this [ToolBar]. @@ -120,6 +123,35 @@ class ToolBar extends StatefulWidget with Diagnosticable { /// Set this to `MacosColors.transparent` to remove. final Color? dividerColor; + /// Whether this [ToolBar] is allowed to perform wallpaper tinting overrides. + /// + /// This property is supposed to be set to true when this [ToolBar] is + /// currently visible on the screen (that is, not e.g. hidden by an + /// [IndexedStack]). + /// + /// This parameter only needs to be supplied when [enableBlur] is true. + /// + /// By default, macos_ui applies wallpaper tinting to the application's + /// window to match macOS' native appearance: + /// + /// + /// + /// However, this effect is realized by inserting `NSVisualEffectView`s behind + /// Flutter's canvas and turning the background of areas that are meant to be + /// affected by wallpaper tinting transparent. Since Flutter's + /// [`ImageFilter.blur`](https://api.flutter.dev/flutter/dart-ui/ImageFilter/ImageFilter.blur.html) + /// does not support transparency, wallpaper tinting is disabled automatically + /// when this widget's [enableBlur] and [allowWallpaperTintingOverrides] is + /// true. + /// + /// This is meant to be a temporary solution until + /// [#16296](https://github.com/flutter/flutter/issues/16296) is resolved in + /// the Flutter project. + final bool allowWallpaperTintingOverrides; + + /// Whether this [ToolBar] should have a blur backdrop filter applied to it. + final bool enableBlur; + @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); @@ -191,13 +223,10 @@ class _ToolBarState extends State { title = SizedBox( width: widget.titleWidth, child: DefaultTextStyle( - style: MacosTheme.of(context).typography.headline.copyWith( - fontSize: 15, - fontWeight: FontWeight.w600, - color: theme.brightness.isDark - ? const Color(0xFFEAEAEA) - : const Color(0xFF4D4D4D), - ), + style: theme.typography.title3.copyWith( + fontSize: 15, + fontWeight: MacosFontWeight.w590, + ), child: title, ), ); @@ -233,57 +262,56 @@ class _ToolBarState extends State { left: !kIsWeb && isMacOS ? 70 : 0, ), ), - child: ClipRect( - child: BackdropFilter( - filter: widget.decoration?.color?.opacity == 1 - ? ImageFilter.blur() - : ImageFilter.blur(sigmaX: 5.0, sigmaY: 5.0), - child: Container( - alignment: widget.alignment, - padding: widget.padding, - decoration: BoxDecoration( - color: theme.canvasColor, - border: Border(bottom: BorderSide(color: dividerColor)), - ).copyWith( - color: widget.decoration?.color, - image: widget.decoration?.image, - border: widget.decoration?.border, - borderRadius: widget.decoration?.borderRadius, - boxShadow: widget.decoration?.boxShadow, - gradient: widget.decoration?.gradient, - ), - child: NavigationToolbar( - middle: title, - centerMiddle: widget.centerTitle, - trailing: OverflowHandler( - overflowBreakpoint: overflowBreakpoint, - overflowWidget: ToolbarOverflowButton( - isDense: doAllItemsShowLabel, - overflowContentBuilder: (context) => ToolbarOverflowMenu( - children: overflowedActions - .map((action) => action.build( - context, - ToolbarItemDisplayMode.overflowed, - )) - .toList(), - ), + child: _WallpaperTintedAreaOrBlurFilter( + enableWallpaperTintedArea: kIsWeb ? false : !widget.enableBlur, + isWidgetVisible: widget.allowWallpaperTintingOverrides, + backgroundColor: theme.canvasColor, + widgetOpacity: widget.decoration?.color?.opacity, + child: Container( + alignment: widget.alignment, + padding: widget.padding, + decoration: BoxDecoration( + border: Border(bottom: BorderSide(color: dividerColor)), + ).copyWith( + color: widget.decoration?.color, + image: widget.decoration?.image, + border: widget.decoration?.border, + borderRadius: widget.decoration?.borderRadius, + boxShadow: widget.decoration?.boxShadow, + gradient: widget.decoration?.gradient, + ), + child: NavigationToolbar( + middle: title, + centerMiddle: widget.centerTitle, + trailing: OverflowHandler( + overflowBreakpoint: overflowBreakpoint, + overflowWidget: ToolbarOverflowButton( + isDense: doAllItemsShowLabel, + overflowContentBuilder: (context) => ToolbarOverflowMenu( + children: overflowedActions + .map((action) => action.build( + context, + ToolbarItemDisplayMode.overflowed, + )) + .toList(), ), - children: inToolbarActions - .map((e) => - e.build(context, ToolbarItemDisplayMode.inToolbar)) - .toList(), - overflowChangedCallback: (hiddenItems) { - setState(() => overflowedActionsCount = hiddenItems.length); - }, - ), - middleSpacing: 8, - leading: SafeArea( - top: false, - right: false, - bottom: false, - left: !(scope?.isSidebarShown ?? false), - child: leading ?? const SizedBox.shrink(), ), + children: inToolbarActions + .map( + (e) => e.build(context, ToolbarItemDisplayMode.inToolbar), + ) + .toList(), + overflowChangedCallback: (hiddenItems) { + setState(() => overflowedActionsCount = hiddenItems.length); + }, + ), + middleSpacing: 8, + leading: SafeArea( + top: false, + right: false, + bottom: false, + left: !(scope?.isSidebarShown ?? false), + child: leading ?? const SizedBox.shrink(), ), ), ), @@ -319,3 +347,50 @@ abstract class ToolbarItem with Diagnosticable { /// for the given display mode (in toolbar or overflowed). Widget build(BuildContext context, ToolbarItemDisplayMode displayMode); } + +/// Wraps the widget in either a [WallpaperTintingOverride] or a blurry backdrop +/// filter. +class _WallpaperTintedAreaOrBlurFilter extends StatelessWidget { + const _WallpaperTintedAreaOrBlurFilter({ + required this.child, + required this.enableWallpaperTintedArea, + required this.backgroundColor, + required this.widgetOpacity, + required this.isWidgetVisible, + }); + + final Widget child; + final bool enableWallpaperTintedArea; + final Color backgroundColor; + final double? widgetOpacity; + final bool isWidgetVisible; + + @override + Widget build(BuildContext context) { + if (enableWallpaperTintedArea) { + return WallpaperTintedArea( + backgroundColor: backgroundColor, + insertRepaintBoundary: true, + child: child, + ); + } + + if (!isWidgetVisible) { + return child; + } + + return WallpaperTintingOverride( + child: ClipRect( + child: BackdropFilter( + filter: widgetOpacity == 1.0 + ? ImageFilter.blur() + : ImageFilter.blur( + sigmaX: 5.0, + sigmaY: 5.0, + ), + child: child, + ), + ), + ); + } +} diff --git a/lib/src/layout/toolbar/toolbar_divider.dart b/lib/src/layout/toolbar/toolbar_divider.dart index d20a3990..346a999c 100644 --- a/lib/src/layout/toolbar/toolbar_divider.dart +++ b/lib/src/layout/toolbar/toolbar_divider.dart @@ -23,15 +23,9 @@ class ToolBarDivider extends ToolbarItem { const Color.fromRGBO(255, 255, 255, 0.25), ); if (displayMode == ToolbarItemDisplayMode.inToolbar) { - return Padding( - padding: padding!, - child: Container(color: color, width: 1, height: 28), - ); + return Container(color: color, width: 1, height: 28, padding: padding!); } else { - return Padding( - padding: padding!, - child: Container(color: color, height: 1), - ); + return Container(color: color, height: 1, padding: padding!); } } } diff --git a/lib/src/layout/toolbar/toolbar_popup.dart b/lib/src/layout/toolbar/toolbar_popup.dart index 9d618128..87f1158b 100644 --- a/lib/src/layout/toolbar/toolbar_popup.dart +++ b/lib/src/layout/toolbar/toolbar_popup.dart @@ -203,7 +203,7 @@ class _ToolbarPopupMenuState extends State<_ToolbarPopupMenu> { super.initState(); _fadeOpacity = CurvedAnimation( parent: widget.route.animation!, - curve: const Interval(0.0, 0.50), + curve: const Interval(0.0, 0.5), reverseCurve: const Interval(0.75, 1.0), ); } @@ -335,7 +335,7 @@ class _ToolbarPopupRoute extends PopupRoute { @override Widget buildPage(context, animation, secondaryAnimation) { return LayoutBuilder(builder: (context, constraints) { - final page = _ToolbarPopupRoutePage( + return _ToolbarPopupRoutePage( target: target, placementOffset: placementOffset, placement: placement, @@ -349,7 +349,6 @@ class _ToolbarPopupRoute extends PopupRoute { horizontalOffset: horizontalOffset, position: position, ); - return page; }); } diff --git a/lib/src/layout/wallpaper_tinted_area.dart b/lib/src/layout/wallpaper_tinted_area.dart new file mode 100644 index 00000000..607ae808 --- /dev/null +++ b/lib/src/layout/wallpaper_tinted_area.dart @@ -0,0 +1,156 @@ +import 'package:flutter/cupertino.dart'; +import 'package:macos_ui/src/layout/wallpaper_tinting_settings/global_wallpaper_tinting_settings.dart'; +import 'package:macos_ui/src/layout/wallpaper_tinting_settings/wallpaper_tinting_settings_builder.dart'; +import 'package:macos_window_utils/macos/ns_visual_effect_view_material.dart'; +import 'package:macos_window_utils/widgets/visual_effect_subview_container/visual_effect_subview_container.dart'; + +/// A widget that applies a wallpaper tint to its child widget. +/// +/// This widget only works on macOS. +/// +/// The [backgroundColor] is the color to apply to the background when wallpaper +/// tinting is disabled. If [insertRepaintBoundary] is true, a [RepaintBoundary] +/// is inserted above this widget in the widget tree. In some instances, it may +/// be necessary to insert a [RepaintBoundary] to ensure proper rendering. +/// The [child] is the widget below this widget in the tree. +/// +/// Example: +/// +/// ```dart +/// WallpaperTintedArea( +/// backgroundColor: MacosColors.white, +/// child: Text('Hello World'), +/// ) +/// ``` +class WallpaperTintedArea extends StatelessWidget { + /// Creates a [WallpaperTintedArea]. + /// + /// Widgets wrapped in this widget will have a wallpaper tint applied to them. + /// + /// **Note:** This widget only works on macOS. + const WallpaperTintedArea({ + super.key, + required this.backgroundColor, + this.insertRepaintBoundary = false, + this.child, + }); + + /// The color to apply to the background when wallpaper tinting is disabled. + final Color backgroundColor; + + /// Whether to insert a [RepaintBoundary] above this widget in the widget + /// tree. + /// + /// In some instances, it may be necessary to insert a [RepaintBoundary] above + /// this widget into the widget tree to ensure that this widget is rendered + /// properly. + final bool insertRepaintBoundary; + + /// The widget below this widget in the tree. + final Widget? child; + + @override + Widget build(BuildContext context) { + if (insertRepaintBoundary) { + return RepaintBoundary( + child: _WallpaperTintedAreaLayoutBuilder( + backgroundColor: backgroundColor, + child: child, + ), + ); + } + + return _WallpaperTintedAreaLayoutBuilder( + backgroundColor: backgroundColor, + child: child, + ); + } +} + +class _WallpaperTintedAreaLayoutBuilder extends StatelessWidget { + const _WallpaperTintedAreaLayoutBuilder({ + required this.backgroundColor, + required this.child, + }); + + /// The color to apply to the background when wallpaper tinting is disabled. + final Color backgroundColor; + + /// The widget below this widget in the tree. + final Widget? child; + + @override + Widget build(BuildContext context) { + if (GlobalWallpaperTintingSettings + .data.isWallpaperTintingDisabledByWindow) { + return Container( + decoration: BoxDecoration( + color: backgroundColor, + ), + child: child, + ); + } + + // This LayoutBuilder forces the widget to be rebuilt when a layout change + // is detected. This is necessary for the VisualEffectSubviewContainer to + // be updated. + return LayoutBuilder( + builder: (context, _) { + return VisualEffectSubviewContainer( + material: NSVisualEffectViewMaterial.windowBackground, + child: WallpaperTintingSettingsBuilder( + builder: (context, data) { + final isWallpaperTintingEnabled = data.isWallpaperTintingEnabled; + + return _WallpaperTintedAreaTweenAnimationBuilder( + isWallpaperTintingEnabled: isWallpaperTintingEnabled, + backgroundColor: backgroundColor, + child: child, + ); + }, + ), + ); + }, + ); + } +} + +class _WallpaperTintedAreaTweenAnimationBuilder extends StatelessWidget { + const _WallpaperTintedAreaTweenAnimationBuilder({ + required this.isWallpaperTintingEnabled, + required this.backgroundColor, + required this.child, + }); + + /// Whether wallpaper tinting is enabled. + final bool isWallpaperTintingEnabled; + + /// The color to apply to the background when wallpaper tinting is disabled. + final Color backgroundColor; + + /// The widget below this widget in the tree. + final Widget? child; + + @override + Widget build(BuildContext context) { + return TweenAnimationBuilder( + duration: const Duration(milliseconds: 100), + tween: Tween( + begin: isWallpaperTintingEnabled ? 0.0 : 1.0, + end: isWallpaperTintingEnabled ? 0.0 : 1.0, + ), + builder: (context, value, child) { + return Container( + decoration: BoxDecoration( + color: backgroundColor.withOpacity(value), + backgroundBlendMode: BlendMode.src, + ), + child: child, + ); + }, + child: RepaintBoundary( + child: child, + ), + ); + } +} diff --git a/lib/src/layout/wallpaper_tinting_settings/global_wallpaper_tinting_settings.dart b/lib/src/layout/wallpaper_tinting_settings/global_wallpaper_tinting_settings.dart new file mode 100644 index 00000000..78ec46f1 --- /dev/null +++ b/lib/src/layout/wallpaper_tinting_settings/global_wallpaper_tinting_settings.dart @@ -0,0 +1,46 @@ +import 'dart:async'; + +import 'package:macos_ui/src/layout/wallpaper_tinting_settings/wallpaper_tinting_settings_data.dart'; + +/// A class that provides a global instance of [WallpaperTintingSettingsData]. +class GlobalWallpaperTintingSettings { + /// The [WallpaperTintingSettingsData] instance. + static final WallpaperTintingSettingsData data = + WallpaperTintingSettingsData(); + + /// The [StreamController] for an event stream that is triggered when [data] + /// changes. + static final _onDataChangedStreamController = + StreamController.broadcast(); + + /// A stream that can be used to listen to [data] changes. + static Stream get onDataChangedStream => + _onDataChangedStreamController.stream; + + /// Gets whether wallpaper tinting should be enabled. + static bool get isWallpaperTintingEnabled => data.isWallpaperTintingEnabled; + + /// Increments the number of active overrides. + static void addWallpaperTintingOverride() { + data.addOverride(); + _onDataChangedStreamController.add(data); + } + + /// Decrements the number of active overrides. + static void removeWallpaperTintingOverride() { + data.removeOverride(); + _onDataChangedStreamController.add(data); + } + + /// Disables wallpaper tinting altogether. + static void disableWallpaperTinting() { + data.disableWallpaperTinting(); + _onDataChangedStreamController.add(data); + } + + /// Allows wallpaper tinting, unless overridden. + static void allowWallpaperTinting() { + data.allowWallpaperTinting(); + _onDataChangedStreamController.add(data); + } +} diff --git a/lib/src/layout/wallpaper_tinting_settings/wallpaper_tinting_override.dart b/lib/src/layout/wallpaper_tinting_settings/wallpaper_tinting_override.dart new file mode 100644 index 00000000..7cd05629 --- /dev/null +++ b/lib/src/layout/wallpaper_tinting_settings/wallpaper_tinting_override.dart @@ -0,0 +1,41 @@ +import 'package:flutter/widgets.dart'; +import 'package:macos_ui/src/layout/wallpaper_tinting_settings/global_wallpaper_tinting_settings.dart'; + +class WallpaperTintingOverride extends StatefulWidget { + /// Creates a [WallpaperTintingOverride]. + /// + /// Including this widget in the widget tree will disable wallpaper tinting + /// globally. It is intended to be used by [MacosOverlayFilter] to disable + /// wallpaper tinting when an overlay filter is active, since + /// [`ImageFilter.blur`](https://api.flutter.dev/flutter/dart-ui/ImageFilter/ImageFilter.blur.html) + /// does not support transparency. + const WallpaperTintingOverride({super.key, this.child}); + + /// The widget below this widget in the tree. + final Widget? child; + + @override + State createState() => + _WallpaperTintingOverrideState(); +} + +class _WallpaperTintingOverrideState extends State { + @override + void initState() { + super.initState(); + + GlobalWallpaperTintingSettings.addWallpaperTintingOverride(); + } + + @override + void dispose() { + GlobalWallpaperTintingSettings.removeWallpaperTintingOverride(); + + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return widget.child ?? const SizedBox(); + } +} diff --git a/lib/src/layout/wallpaper_tinting_settings/wallpaper_tinting_settings_builder.dart b/lib/src/layout/wallpaper_tinting_settings/wallpaper_tinting_settings_builder.dart new file mode 100644 index 00000000..19fe2873 --- /dev/null +++ b/lib/src/layout/wallpaper_tinting_settings/wallpaper_tinting_settings_builder.dart @@ -0,0 +1,46 @@ +import 'package:flutter/widgets.dart'; +import 'package:macos_ui/src/layout/wallpaper_tinting_settings/global_wallpaper_tinting_settings.dart'; + +import 'wallpaper_tinting_settings_data.dart'; + +/// A widget that listens for changes to [WallpaperTintingSettingsData] and +/// rebuilds with the latest data when a change is detected. +/// +/// The [builder] callback is called whenever [WallpaperTintingSettingsData] +/// changes. It should build a widget using the latest +/// [WallpaperTintingSettingsData]. +/// +/// Example: +/// +/// ```dart +/// WallpaperTintingSettingsBuilder( +/// builder: (context, data) { +/// return Text( +/// 'isWallpaperTintingEnabled: ${data.isWallpaperTintingEnabled}', +/// ); +/// }, +/// ) +/// ``` +class WallpaperTintingSettingsBuilder extends StatelessWidget { + /// Creates a [WallpaperTintingSettingsBuilder]. + /// + /// This widget can be used to listen to [WallpaperTintingSettingsData] + /// changes and rebuild if a change has been detected. + const WallpaperTintingSettingsBuilder({super.key, required this.builder}); + + /// Called when [WallpaperTintingSettingsData] changes. + final Widget Function(BuildContext, WallpaperTintingSettingsData) builder; + + @override + Widget build(BuildContext context) { + return StreamBuilder( + stream: GlobalWallpaperTintingSettings.onDataChangedStream, + initialData: GlobalWallpaperTintingSettings.data, + builder: (context, snapshot) { + final data = snapshot.data ?? GlobalWallpaperTintingSettings.data; + + return builder(context, data); + }, + ); + } +} diff --git a/lib/src/layout/wallpaper_tinting_settings/wallpaper_tinting_settings_data.dart b/lib/src/layout/wallpaper_tinting_settings/wallpaper_tinting_settings_data.dart new file mode 100644 index 00000000..07c8aea4 --- /dev/null +++ b/lib/src/layout/wallpaper_tinting_settings/wallpaper_tinting_settings_data.dart @@ -0,0 +1,38 @@ +/// Holds data related to wallpaper tinting. +class WallpaperTintingSettingsData { + /// The number of wallpaper tinting overrides that are currently active. + /// + /// A wallpaper tinting override causes wallpaper tinting to be disabled. + int _numberOfWallpaperTintingOverrides = 0; + + /// Whether wallpaper tinting is disabled by the application's window. + bool _isWallpaperTintingDisabledByWindow = false; + + /// Gets whether wallpaper tinting should be enabled. + bool get isWallpaperTintingEnabled => + !_isWallpaperTintingDisabledByWindow && + _numberOfWallpaperTintingOverrides == 0; + + /// Gets whether wallpaper tinting is disabled by the application's window. + bool get isWallpaperTintingDisabledByWindow => + _isWallpaperTintingDisabledByWindow; + + /// Increments the number of active overrides. + void addOverride() => _numberOfWallpaperTintingOverrides += 1; + + /// Decrements the number of active overrides. + void removeOverride() { + _numberOfWallpaperTintingOverrides -= 1; + assert(_numberOfWallpaperTintingOverrides >= 0); + } + + /// Disables wallpaper tinting altogether. + void disableWallpaperTinting() { + _isWallpaperTintingDisabledByWindow = true; + } + + /// Allows wallpaper tinting, unless overridden. + void allowWallpaperTinting() { + _isWallpaperTintingDisabledByWindow = false; + } +} diff --git a/lib/src/layout/window.dart b/lib/src/layout/window.dart index 9ce38eb0..85c20417 100644 --- a/lib/src/layout/window.dart +++ b/lib/src/layout/window.dart @@ -2,14 +2,10 @@ import 'dart:math' as math; import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; -import 'package:macos_ui/src/layout/scrollbar.dart'; -import 'package:macos_ui/src/layout/content_area.dart'; -import 'package:macos_ui/src/layout/resizable_pane.dart'; -import 'package:macos_ui/src/layout/scaffold.dart'; -import 'package:macos_ui/src/layout/sidebar/sidebar.dart'; -import 'package:macos_ui/src/layout/title_bar.dart'; +import 'package:macos_ui/macos_ui.dart'; +import 'package:macos_ui/src/layout/wallpaper_tinting_settings/global_wallpaper_tinting_settings.dart'; import 'package:macos_ui/src/library.dart'; -import 'package:macos_ui/src/theme/macos_theme.dart'; +import 'package:macos_window_utils/widgets/transparent_macos_sidebar.dart'; /// A basic frame layout. /// @@ -28,6 +24,8 @@ class MacosWindow extends StatefulWidget { this.sidebar, this.backgroundColor, this.endSidebar, + this.disableWallpaperTinting = false, + this.sidebarState = NSVisualEffectViewState.followsWindowActiveState, }); /// Specifies the background color for the Window. @@ -47,13 +45,47 @@ class MacosWindow extends StatefulWidget { /// A sidebar to display at the right of the window. final Sidebar? endSidebar; + /// Whether wallpaper tinting should be disabled. + /// + /// By default, `macos_ui` applies wallpaper tinting to the application's + /// window to match macOS' native appearance: + /// + /// + /// + /// However, this effect is realized by inserting `NSVisualEffectView`s behind + /// Flutter's canvas and turning the background of areas that are meant to be + /// affected by wallpaper tinting transparent. Since Flutter's + /// [`ImageFilter.blur`](https://api.flutter.dev/flutter/dart-ui/ImageFilter/ImageFilter.blur.html) + /// does not support transparency, wallpaper tinting is disabled automatically + /// when a [MacosOverlayFilter] is present in the widget tree. + /// + /// This is meant to be a temporary solution until + /// [#16296](https://github.com/flutter/flutter/issues/16296) is resolved in + /// the Flutter project. + /// + /// Since the disabling of wallpaper tinting may be found to be too noticeable, + /// this property may be used to disable wallpaper tinting outright. + final bool disableWallpaperTinting; + + /// The state of the sidebar's [NSVisualEffectView]. + /// + /// Possible values are: + /// + /// - [NSVisualEffectViewState.active]: The sidebar is always active. + /// - [NSVisualEffectViewState.inactive]: The sidebar is always inactive. + /// - [NSVisualEffectViewState.followsWindowActiveState]: The sidebar's state + /// follows the window's active state. + /// + /// Defaults to [NSVisualEffectViewState.followsWindowActiveState]. + final NSVisualEffectViewState sidebarState; + @override State createState() => _MacosWindowState(); } class _MacosWindowState extends State { - final _sidebarScrollController = ScrollController(); - final _endSidebarScrollController = ScrollController(); + var _sidebarScrollController = ScrollController(); + var _endSidebarScrollController = ScrollController(); double _sidebarWidth = 0.0; double _sidebarDragStartWidth = 0.0; double _sidebarDragStartPosition = 0.0; @@ -74,47 +106,71 @@ class _MacosWindowState extends State { _endSidebarWidth = (widget.endSidebar?.startWidth ?? widget.endSidebar?.minWidth) ?? _endSidebarWidth; - if (widget.sidebar?.builder != null) { - _sidebarScrollController.addListener(() => setState(() {})); - } - if (widget.endSidebar?.builder != null) { - _endSidebarScrollController.addListener(() => setState(() {})); - } + + widget.disableWallpaperTinting + ? GlobalWallpaperTintingSettings.disableWallpaperTinting() + : GlobalWallpaperTintingSettings.allowWallpaperTinting(); + + _addSidebarScrollControllerListenerIfNeeded(); + _addEndSidebarScrollControllerListenerIfNeeded(); } @override void didUpdateWidget(covariant MacosWindow old) { super.didUpdateWidget(old); - setState(() { - if (widget.sidebar == null) { - _sidebarWidth = 0.0; - } else if (widget.sidebar!.minWidth != old.sidebar!.minWidth || - widget.sidebar!.maxWidth != old.sidebar!.maxWidth) { - if (widget.sidebar!.minWidth > _sidebarWidth) { - _sidebarWidth = widget.sidebar!.minWidth; - } - if (widget.sidebar!.maxWidth! < _sidebarWidth) { - _sidebarWidth = widget.sidebar!.maxWidth!; - } + final sidebar = widget.sidebar; + if (sidebar == null) { + _sidebarWidth = 0.0; + } else if (sidebar.minWidth != old.sidebar!.minWidth || + sidebar.maxWidth != old.sidebar!.maxWidth) { + if (sidebar.minWidth > _sidebarWidth) { + _sidebarWidth = sidebar.minWidth; + } + if (sidebar.maxWidth! < _sidebarWidth) { + _sidebarWidth = sidebar.maxWidth!; + } + } + if (sidebar?.key != old.sidebar?.key) { + _sidebarScrollController.dispose(); + _sidebarScrollController = ScrollController(); + _addSidebarScrollControllerListenerIfNeeded(); + } + final endSidebar = widget.endSidebar; + if (endSidebar == null) { + _endSidebarWidth = 0.0; + } else if (endSidebar.minWidth != old.endSidebar!.minWidth || + endSidebar.maxWidth != old.endSidebar!.maxWidth) { + if (endSidebar.minWidth > _endSidebarWidth) { + _endSidebarWidth = endSidebar.minWidth; } - if (widget.endSidebar == null) { - _endSidebarWidth = 0.0; - } else if (widget.endSidebar!.minWidth != old.endSidebar!.minWidth || - widget.endSidebar!.maxWidth != old.endSidebar!.maxWidth) { - if (widget.endSidebar!.minWidth > _endSidebarWidth) { - _endSidebarWidth = widget.endSidebar!.minWidth; - } - if (widget.endSidebar!.maxWidth! < _endSidebarWidth) { - _endSidebarWidth = widget.endSidebar!.maxWidth!; - } + if (endSidebar.maxWidth! < _endSidebarWidth) { + _endSidebarWidth = endSidebar.maxWidth!; } - }); + } + if (endSidebar?.key != old.endSidebar?.key) { + _endSidebarScrollController.dispose(); + _endSidebarScrollController = ScrollController(); + _addEndSidebarScrollControllerListenerIfNeeded(); + } + } + + void _addSidebarScrollControllerListenerIfNeeded() { + if (widget.sidebar?.builder != null) { + _sidebarScrollController.addListener(() => setState(() {})); + } + } + + void _addEndSidebarScrollControllerListenerIfNeeded() { + if (widget.endSidebar?.builder != null) { + _endSidebarScrollController.addListener(() => setState(() {})); + } } @override void dispose() { _sidebarScrollController.dispose(); _endSidebarScrollController.dispose(); + super.dispose(); } @@ -122,13 +178,15 @@ class _MacosWindowState extends State { // ignore: code-metrics Widget build(BuildContext context) { assert(debugCheckHasMacosTheme(context)); - if (widget.sidebar?.startWidth != null) { - assert((widget.sidebar!.startWidth! >= widget.sidebar!.minWidth) && - (widget.sidebar!.startWidth! <= widget.sidebar!.maxWidth!)); + final sidebar = widget.sidebar; + final endSidebar = widget.endSidebar; + if (sidebar?.startWidth != null) { + assert((sidebar!.startWidth! >= sidebar.minWidth) && + (sidebar.startWidth! <= sidebar.maxWidth!)); } - if (widget.endSidebar?.startWidth != null) { - assert((widget.endSidebar!.startWidth! >= widget.endSidebar!.minWidth) && - (widget.endSidebar!.startWidth! <= widget.endSidebar!.maxWidth!)); + if (endSidebar?.startWidth != null) { + assert((endSidebar!.startWidth! >= endSidebar.minWidth) && + (endSidebar.startWidth! <= endSidebar.maxWidth!)); } final MacosThemeData theme = MacosTheme.of(context); late Color backgroundColor = widget.backgroundColor ?? theme.canvasColor; @@ -139,28 +197,19 @@ class _MacosWindowState extends State { final isMac = !kIsWeb && defaultTargetPlatform == TargetPlatform.macOS; // Respect the sidebar color override from parent if one is given - if (widget.sidebar?.decoration?.color != null) { - sidebarBackgroundColor = widget.sidebar!.decoration!.color!; - } else if (isMac && - MediaQuery.of(context).platformBrightness.isDark == - theme.brightness.isDark) { - // Only show blurry, transparent sidebar when platform brightness and app - // brightness are the same, otherwise it looks awful. Also only make the - // sidebar transparent on native Mac, or it will just be flat black or - // white. - sidebarBackgroundColor = Colors.transparent; + if (sidebar?.decoration?.color != null) { + sidebarBackgroundColor = sidebar!.decoration!.color!; } else { - sidebarBackgroundColor = theme.brightness.isDark - ? CupertinoColors.tertiarySystemBackground.darkColor - : CupertinoColors.systemGrey6.color; + sidebarBackgroundColor = MacosColors.transparent; } + // Set the application window's brightness on macOS + MacOSBrightnessOverrideHandler.ensureMatchingBrightness(theme.brightness); + // Respect the end sidebar color override from parent if one is given - if (widget.endSidebar?.decoration?.color != null) { - endSidebarBackgroundColor = widget.endSidebar!.decoration!.color!; - } else if (isMac && - MediaQuery.of(context).platformBrightness.isDark == - theme.brightness.isDark) { + if (endSidebar?.decoration?.color != null) { + endSidebarBackgroundColor = endSidebar!.decoration!.color!; + } else if (isMac) { endSidebarBackgroundColor = theme.canvasColor; } else { endSidebarBackgroundColor = theme.brightness.isDark @@ -175,14 +224,16 @@ class _MacosWindowState extends State { builder: (context, constraints) { final width = constraints.maxWidth; final height = constraints.maxHeight; - final isAtBreakpoint = width <= (widget.sidebar?.windowBreakpoint ?? 0); - final isAtEndBreakpoint = - width <= (widget.endSidebar?.windowBreakpoint ?? 0); - final canShowSidebar = _showSidebar && !isAtBreakpoint; - final canShowEndSidebar = _showEndSidebar && !isAtEndBreakpoint; + final isAtBreakpoint = width <= (sidebar?.windowBreakpoint ?? 0); + final isAtEndBreakpoint = width <= (endSidebar?.windowBreakpoint ?? 0); + final canShowSidebar = + _showSidebar && !isAtBreakpoint && sidebar != null; + final canShowEndSidebar = + _showEndSidebar && !isAtEndBreakpoint && endSidebar != null; final visibleSidebarWidth = canShowSidebar ? _sidebarWidth : 0.0; final visibleEndSidebarWidth = canShowEndSidebar ? _endSidebarWidth : 0.0; + final sidebarState = widget.sidebarState; final layout = Stack( children: [ @@ -197,8 +248,9 @@ class _MacosWindowState extends State { ), // Sidebar - if (widget.sidebar != null) + if (sidebar != null) AnimatedPositioned( + key: sidebar.key, curve: curve, duration: duration, height: height, @@ -208,41 +260,99 @@ class _MacosWindowState extends State { curve: Curves.easeInOut, color: sidebarBackgroundColor, constraints: BoxConstraints( - minWidth: widget.sidebar!.minWidth, - maxWidth: widget.sidebar!.maxWidth!, + minWidth: sidebar.minWidth, + maxWidth: sidebar.maxWidth!, minHeight: height, maxHeight: height, ).normalize(), - child: Column( - children: [ - if ((widget.sidebar?.topOffset ?? 0) > 0) - SizedBox(height: widget.sidebar?.topOffset), - if (_sidebarScrollController.hasClients && - _sidebarScrollController.offset > 0.0) - Divider(thickness: 1, height: 1, color: dividerColor), - if (widget.sidebar!.top != null && - constraints.maxHeight > 81) - Padding( - padding: const EdgeInsets.symmetric(horizontal: 8.0), - child: widget.sidebar!.top!, - ), - Expanded( - child: MacosScrollbar( - controller: _sidebarScrollController, - child: Padding( - padding: widget.sidebar?.padding ?? EdgeInsets.zero, - child: widget.sidebar! - .builder(context, _sidebarScrollController), + child: kIsWeb ? ColoredBox( + color: theme.canvasColor, + child: Column( + children: [ + // If an app is running on macOS, apply + // sidebar.topOffset as needed in order to avoid the + // traffic lights. Otherwise, position the sidebar + // by the top of the application's bounds based on + // the presence of sidebar.top. + if (!kIsWeb && sidebar.topOffset > 0) ...[ + SizedBox(height: sidebar.topOffset), + ] else if (sidebar.top != null) ...[ + const SizedBox(height: 12), + ] else + const SizedBox.shrink(), + if (_sidebarScrollController.hasClients && + _sidebarScrollController.offset > 0.0) + Divider(thickness: 1, height: 1, color: dividerColor), + if (sidebar.top != null && constraints.maxHeight > 81) + Padding( + padding: + const EdgeInsets.symmetric(horizontal: 8.0), + child: sidebar.top!, + ), + Expanded( + child: MacosScrollbar( + controller: _sidebarScrollController, + child: Padding( + padding: sidebar.padding, + child: sidebar.builder( + context, + _sidebarScrollController, + ), + ), ), ), - ), - if (widget.sidebar?.bottom != null && - constraints.maxHeight > 141) - Padding( - padding: const EdgeInsets.all(16.0), - child: widget.sidebar!.bottom!, + if (sidebar.bottom != null && + constraints.maxHeight > 141) + Padding( + padding: const EdgeInsets.all(16.0), + child: sidebar.bottom!, + ), + ], + ), + ) : TransparentMacOSSidebar( + state: sidebarState, + child: Column( + children: [ + // If an app is running on macOS, apply + // sidebar.topOffset as needed in order to avoid the + // traffic lights. Otherwise, position the sidebar + // by the top of the application's bounds based on + // the presence of sidebar.top. + if (!kIsWeb && sidebar.topOffset > 0) ...[ + SizedBox(height: sidebar.topOffset), + ] else if (sidebar.top != null) ...[ + const SizedBox(height: 12), + ] else + const SizedBox.shrink(), + if (_sidebarScrollController.hasClients && + _sidebarScrollController.offset > 0.0) + Divider(thickness: 1, height: 1, color: dividerColor), + if (sidebar.top != null && constraints.maxHeight > 81) + Padding( + padding: + const EdgeInsets.symmetric(horizontal: 8.0), + child: sidebar.top!, + ), + Expanded( + child: MacosScrollbar( + controller: _sidebarScrollController, + child: Padding( + padding: sidebar.padding, + child: sidebar.builder( + context, + _sidebarScrollController, + ), + ), + ), ), - ], + if (sidebar.bottom != null && + constraints.maxHeight > 141) + Padding( + padding: const EdgeInsets.all(16.0), + child: sidebar.bottom!, + ), + ], + ), ), ), ), @@ -275,7 +385,7 @@ class _MacosWindowState extends State { ), // Sidebar resizer - if (widget.sidebar?.isResizable ?? false) + if (sidebar?.isResizable ?? false) AnimatedPositioned( curve: curve, duration: duration, @@ -289,13 +399,12 @@ class _MacosWindowState extends State { _sidebarDragStartPosition = details.globalPosition.dx; }, onHorizontalDragUpdate: (details) { - final sidebar = widget.sidebar!; setState(() { var newWidth = _sidebarDragStartWidth + details.globalPosition.dx - _sidebarDragStartPosition; - if (sidebar.startWidth != null && + if (sidebar!.startWidth != null && sidebar.snapToStartBuffer != null && (newWidth - sidebar.startWidth!).abs() <= sidebar.snapToStartBuffer!) { @@ -340,8 +449,9 @@ class _MacosWindowState extends State { ), // End sidebar - if (widget.endSidebar != null) + if (endSidebar != null) AnimatedPositioned( + key: endSidebar.key, left: width - visibleEndSidebarWidth, curve: curve, duration: duration, @@ -352,46 +462,56 @@ class _MacosWindowState extends State { curve: Curves.easeInOut, color: endSidebarBackgroundColor, constraints: BoxConstraints( - minWidth: widget.endSidebar!.minWidth, - maxWidth: widget.endSidebar!.maxWidth!, + minWidth: endSidebar.minWidth, + maxWidth: endSidebar.maxWidth!, minHeight: height, maxHeight: height, ).normalize(), - child: Column( - children: [ - if ((widget.endSidebar?.topOffset ?? 0) > 0) - SizedBox(height: widget.endSidebar?.topOffset), - if (_endSidebarScrollController.hasClients && - _endSidebarScrollController.offset > 0.0) - Divider(thickness: 1, height: 1, color: dividerColor), - if (widget.endSidebar!.top != null) - Padding( - padding: const EdgeInsets.symmetric(horizontal: 8.0), - child: widget.endSidebar!.top!, - ), - Expanded( - child: MacosScrollbar( - controller: _endSidebarScrollController, - child: Padding( + child: WallpaperTintedArea( + backgroundColor: endSidebarBackgroundColor, + insertRepaintBoundary: true, + child: Column( + children: [ + if (endSidebar.topOffset > 0) + SizedBox(height: endSidebar.topOffset), + if (_endSidebarScrollController.hasClients && + _endSidebarScrollController.offset > 0.0) + Divider( + thickness: 1, + height: 1, + color: dividerColor, + ), + if (endSidebar.top != null) + Padding( padding: - widget.endSidebar?.padding ?? EdgeInsets.zero, - child: widget.endSidebar! - .builder(context, _endSidebarScrollController), + const EdgeInsets.symmetric(horizontal: 8.0), + child: endSidebar.top!, + ), + Expanded( + child: MacosScrollbar( + controller: _endSidebarScrollController, + child: Padding( + padding: endSidebar.padding, + child: endSidebar.builder( + context, + _endSidebarScrollController, + ), + ), ), ), - ), - if (widget.endSidebar?.bottom != null) - Padding( - padding: const EdgeInsets.all(16.0), - child: widget.endSidebar!.bottom!, - ), - ], + if (endSidebar.bottom != null) + Padding( + padding: const EdgeInsets.all(16.0), + child: endSidebar.bottom!, + ), + ], + ), ), ), ), // End sidebar resizer - if (widget.endSidebar?.isResizable ?? false) + if (endSidebar?.isResizable ?? false) AnimatedPositioned( curve: curve, duration: duration, @@ -405,13 +525,12 @@ class _MacosWindowState extends State { _endSidebarDragStartPosition = details.globalPosition.dx; }, onHorizontalDragUpdate: (details) { - final endSidebar = widget.endSidebar!; setState(() { var newWidth = _endSidebarDragStartWidth - details.globalPosition.dx + _endSidebarDragStartPosition; - if (endSidebar.startWidth != null && + if (endSidebar!.startWidth != null && endSidebar.snapToStartBuffer != null && (newWidth + endSidebar.startWidth!).abs() <= endSidebar.snapToStartBuffer!) { @@ -465,13 +584,17 @@ class _MacosWindowState extends State { setState(() => _sidebarSlideDuration = 300); setState(() => _showSidebar = !_showSidebar); await Future.delayed(Duration(milliseconds: _sidebarSlideDuration)); - setState(() => _sidebarSlideDuration = 0); + if (mounted) { + setState(() => _sidebarSlideDuration = 0); + } }, endSidebarToggler: () async { setState(() => _sidebarSlideDuration = 300); setState(() => _showEndSidebar = !_showEndSidebar); await Future.delayed(Duration(milliseconds: _sidebarSlideDuration)); - setState(() => _sidebarSlideDuration = 0); + if (mounted) { + setState(() => _sidebarSlideDuration = 0); + } }, child: layout, ); diff --git a/lib/src/macos_app.dart b/lib/src/macos_app.dart index c4068f30..459918fb 100644 --- a/lib/src/macos_app.dart +++ b/lib/src/macos_app.dart @@ -74,15 +74,17 @@ class MacosApp extends StatefulWidget { }) : routeInformationProvider = null, routeInformationParser = null, routerDelegate = null, - backButtonDispatcher = null; + backButtonDispatcher = null, + routerConfig = null; /// Creates a [MacosApp] that uses the [Router] instead of a [Navigator]. MacosApp.router({ super.key, this.routeInformationProvider, - required RouteInformationParser this.routeInformationParser, - required RouterDelegate this.routerDelegate, + this.routeInformationParser, + this.routerDelegate, this.backButtonDispatcher, + this.routerConfig, this.builder, this.title = '', this.onGenerateTitle, @@ -104,7 +106,8 @@ class MacosApp extends StatefulWidget { this.themeMode, this.theme, this.darkTheme, - }) : assert(supportedLocales.isNotEmpty), + }) : assert(routerDelegate != null || routerConfig != null), + assert(supportedLocales.isNotEmpty), navigatorObservers = null, navigatorKey = null, onGenerateRoute = null, @@ -157,6 +160,9 @@ class MacosApp extends StatefulWidget { /// {@macro flutter.widgets.widgetsApp.backButtonDispatcher} final BackButtonDispatcher? backButtonDispatcher; + /// {@macro flutter.widgets.widgetsApp.routerConfig} + final RouterConfig? routerConfig; + /// {@macro flutter.widgets.widgetsApp.builder} final TransitionBuilder? builder; @@ -300,7 +306,8 @@ class MacosApp extends StatefulWidget { } class _MacosAppState extends State { - bool get _usesRouter => widget.routerDelegate != null; + bool get _usesRouter => + widget.routerDelegate != null || widget.routerConfig != null; Widget _macosBuilder(BuildContext context, Widget? child) { final mode = widget.themeMode ?? ThemeMode.system; @@ -346,9 +353,10 @@ class _MacosAppState extends State { return c.CupertinoApp.router( key: GlobalObjectKey(this), routeInformationProvider: widget.routeInformationProvider, - routeInformationParser: widget.routeInformationParser!, - routerDelegate: widget.routerDelegate!, + routeInformationParser: widget.routeInformationParser, + routerDelegate: widget.routerDelegate, backButtonDispatcher: widget.backButtonDispatcher, + routerConfig: widget.routerConfig, builder: _macosBuilder, title: widget.title, onGenerateTitle: widget.onGenerateTitle, @@ -401,8 +409,7 @@ class _MacosAppState extends State { @override Widget build(BuildContext context) { // leaves room for assertions, etc - Widget result = _buildMacosApp(context); - return result; + return _buildMacosApp(context); } Iterable> get _localizationsDelegates sync* { diff --git a/lib/src/macos_window_utils_config.dart b/lib/src/macos_window_utils_config.dart new file mode 100644 index 00000000..32e52568 --- /dev/null +++ b/lib/src/macos_window_utils_config.dart @@ -0,0 +1,120 @@ +import 'package:flutter/widgets.dart'; +import 'package:macos_ui/macos_ui.dart'; + +/// A class for configuring macOS window properties. +/// +/// [toolbarStyle] is the style of the window toolbar. It should be +/// [NSWindowToolbarStyle.expanded] if the app will have a title bar and +/// [NSWindowToolbarStyle.unified] otherwise. +/// +/// Example: +/// ```dart +/// final config = MacosWindowUtilsConfig( +/// toolbarStyle: NSWindowToolbarStyle.expanded, +/// ); +/// await config.apply(); +/// ``` +class MacosWindowUtilsConfig { + /// Creates a [MacosWindowUtilsConfig]. + /// + /// The [toolbarStyle] is [NSWindowToolbarStyle.unified] by default. If the + /// app will have a title bar, use [NSWindowToolbarStyle.expanded] instead. + const MacosWindowUtilsConfig({ + this.toolbarStyle = NSWindowToolbarStyle.unified, + this.enableFullSizeContentView = true, + this.makeTitlebarTransparent = true, + this.hideTitle = true, + this.removeMenubarInFullScreenMode = true, + this.autoHideToolbarAndMenuBarInFullScreenMode = true, + }); + + /// The style of the window toolbar. + /// + /// Defaults to [NSWindowToolbarStyle.unified]. Use + /// [NSWindowToolbarStyle.expanded] instead if the app will have a title bar. + final NSWindowToolbarStyle toolbarStyle; + + /// Whether to enable the full-size content view. + final bool enableFullSizeContentView; + + /// Whether to make the title bar transparent. + final bool makeTitlebarTransparent; + + /// Whether to hide the title. + final bool hideTitle; + + /// Whether to remove the menubar in full-screen mode. + final bool removeMenubarInFullScreenMode; + + /// Whether to automatically hide the toolbar and menubar in full-screen mode. + final bool autoHideToolbarAndMenuBarInFullScreenMode; + + /// Applies the configuration to the macOS window. + /// + /// This method: + /// + /// - Initializes Flutter bindings + /// - Sets the window material to + /// [NSVisualEffectViewMaterial.windowBackground] + /// - Enables the full size content view if [enableFullSizeContentView] is + /// `true` + /// - Makes the title bar transparent if [makeTitlebarTransparent] is `true` + /// - Hides the title if [hideTitle] is `true` + /// - Adds a toolbar + /// - Sets the toolbar style to [toolbarStyle] + /// - Removes the menubar in full-screen mode if + /// [removeMenubarInFullScreenMode] is `true` + /// - Auto-hides the toolbar and menubar in full-screen mode if + /// [autoHideToolbarAndMenuBarInFullScreenMode] is `true` + Future apply() async { + WidgetsFlutterBinding.ensureInitialized(); + await WindowManipulator.initialize(enableWindowDelegate: true); + await WindowManipulator.setMaterial( + NSVisualEffectViewMaterial.windowBackground, + ); + if (enableFullSizeContentView) { + await WindowManipulator.enableFullSizeContentView(); + } + if (makeTitlebarTransparent) { + await WindowManipulator.makeTitlebarTransparent(); + } + if (hideTitle) { + await WindowManipulator.hideTitle(); + } + await WindowManipulator.addToolbar(); + + await WindowManipulator.setToolbarStyle( + toolbarStyle: toolbarStyle, + ); + + if (removeMenubarInFullScreenMode) { + final delegate = _FlutterWindowDelegate(); + WindowManipulator.addNSWindowDelegate(delegate); + } + + if (autoHideToolbarAndMenuBarInFullScreenMode) { + final options = NSAppPresentationOptions.from({ + NSAppPresentationOption.fullScreen, + NSAppPresentationOption.autoHideToolbar, + NSAppPresentationOption.autoHideMenuBar, + NSAppPresentationOption.autoHideDock, + }); + options.applyAsFullScreenPresentationOptions(); + } + } +} + +/// This delegate removes the toolbar in full-screen mode. +class _FlutterWindowDelegate extends NSWindowDelegate { + @override + void windowWillEnterFullScreen() { + WindowManipulator.removeToolbar(); + super.windowWillEnterFullScreen(); + } + + @override + void windowDidExitFullScreen() { + WindowManipulator.addToolbar(); + super.windowDidExitFullScreen(); + } +} diff --git a/lib/src/selectors/color_well.dart b/lib/src/selectors/color_well.dart index cff9cd9f..9f1c5685 100644 --- a/lib/src/selectors/color_well.dart +++ b/lib/src/selectors/color_well.dart @@ -106,7 +106,7 @@ class _MacosColorWellState extends State { Widget build(BuildContext context) { final theme = MacosTheme.of(context); final outerColor = theme.brightness.isDark - ? MacosColors.systemGrayColor.withOpacity(0.50) + ? MacosColors.systemGrayColor.withOpacity(0.5) : MacosColors.white; return GestureDetector( onTap: () async { diff --git a/lib/src/selectors/date_picker.dart b/lib/src/selectors/date_picker.dart index 4fec86a3..2b3cd9b0 100644 --- a/lib/src/selectors/date_picker.dart +++ b/lib/src/selectors/date_picker.dart @@ -44,6 +44,33 @@ class MacosDatePicker extends StatefulWidget { this.style = DatePickerStyle.combined, required this.onDateChanged, this.initialDate, + // Use this to get the weekday abbreviations instead of + // localizations.narrowWeekdays() in order to match Apple's spec + this.weekdayAbbreviations = const [ + 'Su', + 'Mo', + 'Tu', + 'We', + 'Th', + 'Fr', + 'Sa', + ], + this.monthAbbreviations = const [ + 'Jan', + 'Feb', + 'Mar', + 'Apr', + 'May', + 'Jun', + 'Jul', + 'Aug', + 'Sep', + 'Oct', + 'Nov', + 'Dec', + ], + this.dateFormat, + this.startWeekOnMonday, }); /// The [DatePickerStyle] to use. @@ -59,23 +86,38 @@ class MacosDatePicker extends StatefulWidget { /// Defaults to `DateTime.now()`. final DateTime? initialDate; + /// A list of 7 strings, one for each day of the week, starting with Sunday. + final List weekdayAbbreviations; + + /// A list of 12 strings, one for each month of the year, starting with January. + final List monthAbbreviations; + + /// Changes the way dates are displayed in the textual interface. + /// + /// The following tokens are supported (case-insensitive): + /// * `D`: day of the month (1-31) + /// * `DD`: day of the month (01-31) + /// * `M`: month of the year (1-12) + /// * `MM`: month of the year (01-12) + /// * `YYYY`: year (0000-9999) + /// * Any separator between tokens is preserved (e.g. `/`, `-`, `.`) + /// + /// Defaults to `M/D/YYYY`. + final String? dateFormat; + + /// Allows for changing the order of day headers in the graphical Date Picker + /// to Mo, Tu, We, Th, Fr, Sa, Su. + /// + /// This is useful for internationalization purposes, as many countries begin their weeks on Mondays. + /// + /// Defaults to `false`. + final bool? startWeekOnMonday; + @override State createState() => _MacosDatePickerState(); } class _MacosDatePickerState extends State { - // Use this to get the weekday abbreviations instead of - // localizations.narrowWeekdays() in order to match Apple's spec - static const List _narrowWeekdays = [ - 'Su', - 'Mo', - 'Tu', - 'We', - 'Th', - 'Fr', - 'Sa', - ]; - final _today = DateTime.now(); late final _initialDate = widget.initialDate ?? _today; @@ -153,13 +195,24 @@ class _MacosDatePickerState extends State { } // Creates the day headers - Su, Mo, Tu, We, Th, Fr, Sa + // or Mo, Tu, We, Th, Fr, Sa, Su depending on the value of [startWeekOnMonday] List _dayHeaders( TextStyle? headerStyle, MaterialLocalizations localizations, ) { final result = []; - for (int i = localizations.firstDayOfWeekIndex; true; i = (i + 1) % 7) { - final weekday = _narrowWeekdays[i]; + + // Hack due to invalid "firstDayOfWeekIndex" implementation in MaterialLocalizations + // issue: https://github.com/flutter/flutter/issues/122274 + // TODO: remove this workaround once the issue is fixed. + // Then, "firstDayOfWeekIndex" can be controlled by passing "localizationsDelegates" and "supportedLocales" to MacosApp + int firstDayOfWeekIndex = localizations.firstDayOfWeekIndex; + if (widget.startWeekOnMonday == true) { + firstDayOfWeekIndex = 1; + } + + for (int i = firstDayOfWeekIndex; result.length < 7; i = (i + 1) % 7) { + final weekday = widget.weekdayAbbreviations[i]; result.add( ExcludeSemantics( child: Center( @@ -170,11 +223,88 @@ class _MacosDatePickerState extends State { ), ), ); - if (i == (localizations.firstDayOfWeekIndex - 1) % 7) break; } return result; } + // Creates textual date presentation based on "dateFormat" property + List _textualDateElements() { + final separator = widget.dateFormat != null + ? widget.dateFormat!.toLowerCase().replaceAll(RegExp(r'[dmy]'), '')[0] + : '/'; + + final List dateElements = widget.dateFormat != null + ? widget.dateFormat!.toLowerCase().split(RegExp(r'[^dmy]')) + : ['m', 'd', 'y']; + + final List dateFields = []; + for (var dateElement in dateElements) { + if (dateElement.startsWith('d')) { + String value = dateElement == 'dd' && _selectedDay < 10 + // Add a leading zero + ? '0$_selectedDay' + : '$_selectedDay'; + + dateFields.add( + DatePickerFieldElement( + isSelected: _isDaySelected, + element: value, + onSelected: () { + setState(() { + _focusNode.requestFocus(); + _isDaySelected = !_isDaySelected; + _isMonthSelected = false; + _isYearSelected = false; + }); + }, + ), + ); + } else if (dateElement.startsWith('m')) { + String value = dateElement == 'mm' && _selectedMonth < 10 + // Add a leading zero + ? '0$_selectedMonth' + : '$_selectedMonth'; + + dateFields.add( + DatePickerFieldElement( + isSelected: _isMonthSelected, + element: value, + onSelected: () { + setState(() { + _focusNode.requestFocus(); + _isMonthSelected = !_isMonthSelected; + _isDaySelected = false; + _isYearSelected = false; + }); + }, + ), + ); + } else if (dateElement.startsWith('y')) { + dateFields.add( + DatePickerFieldElement( + isSelected: _isYearSelected, + element: '$_selectedYear', + onSelected: () { + setState(() { + _focusNode.requestFocus(); + _isYearSelected = !_isYearSelected; + _isDaySelected = false; + _isMonthSelected = false; + }); + }, + ), + ); + } + dateFields.add( + Text(separator), + ); + } + + dateFields.removeLast(); + + return dateFields; + } + Widget _buildTextualPicker(MacosDatePickerThemeData datePickerTheme) { return KeyboardShortcutRunner( onUpArrowKeypress: _incrementElement, @@ -195,46 +325,7 @@ class _MacosDatePickerState extends State { ), child: Row( mainAxisSize: MainAxisSize.min, - children: [ - DatePickerFieldElement( - isSelected: _isMonthSelected, - element: '$_selectedMonth', - onSelected: () { - setState(() { - _focusNode.requestFocus(); - _isMonthSelected = !_isMonthSelected; - _isDaySelected = false; - _isYearSelected = false; - }); - }, - ), - const Text('/'), - DatePickerFieldElement( - isSelected: _isDaySelected, - element: '$_selectedDay', - onSelected: () { - setState(() { - _focusNode.requestFocus(); - _isDaySelected = !_isDaySelected; - _isMonthSelected = false; - _isYearSelected = false; - }); - }, - ), - const Text('/'), - DatePickerFieldElement( - isSelected: _isYearSelected, - element: '$_selectedYear', - onSelected: () { - setState(() { - _focusNode.requestFocus(); - _isYearSelected = !_isYearSelected; - _isDaySelected = false; - _isMonthSelected = false; - }); - }, - ), - ], + children: _textualDateElements(), ), ), ), @@ -319,13 +410,14 @@ class _MacosDatePickerState extends State { child: Column( children: [ Padding( - padding: const EdgeInsets.fromLTRB(2.0, 2.0, 0.0, 4.0), + padding: + const EdgeInsets.only(left: 2.0, top: 2.0, bottom: 4.0), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Expanded( child: Text( - '${intToMonthAbbr(_selectedMonth)} $_selectedYear', + '${widget.monthAbbreviations[_selectedMonth - 1]} $_selectedYear', style: const TextStyle( fontSize: 13.0, fontWeight: FontWeight.w700, @@ -353,9 +445,13 @@ class _MacosDatePickerState extends State { setState(() { _selectedYear--; _selectedMonth = 12; + _selectedDay = 1; }); } else { - setState(() => _selectedMonth--); + setState(() { + _selectedMonth--; + _selectedDay = 1; + }); } widget.onDateChanged.call(_formatAsDateTime()); }, @@ -396,9 +492,13 @@ class _MacosDatePickerState extends State { setState(() { _selectedYear++; _selectedMonth = 1; + _selectedDay = 1; }); } else { - setState(() => _selectedMonth++); + setState(() { + _selectedMonth++; + _selectedDay = 1; + }); } widget.onDateChanged.call(_formatAsDateTime()); @@ -410,7 +510,7 @@ class _MacosDatePickerState extends State { ), ), Padding( - padding: const EdgeInsets.fromLTRB(6.0, 0.0, 5.0, 0.0), + padding: const EdgeInsets.only(left: 6.0, right: 5.0), child: Column( children: [ GridView.custom( @@ -465,9 +565,19 @@ class _MacosDatePickerState extends State { ), localizations, ); + + // Hack due to invalid "firstDayOfWeekIndex" implementation in MaterialLocalizations + // issue: https://github.com/flutter/flutter/issues/122274 + // TODO: remove this workaround once the issue is fixed. + // Then, DateUtils.getDaysInMonth will work as expected when proper "localizationsDelegates" and "supportedLocales" are provided to MacosApp + int fixedDayOffset = dayOffset; + if (widget.startWeekOnMonday == true) { + fixedDayOffset = dayOffset - 1; + } + // 1-based day of month, e.g. 1-31 for January, and 1-29 for February on // a leap year. - int day = -dayOffset; + int day = -fixedDayOffset; final dayItems = []; @@ -496,7 +606,7 @@ class _MacosDatePickerState extends State { ); decoration = BoxDecoration( color: datePickerTheme.monthViewCurrentDateColor, - borderRadius: BorderRadius.circular(3.0), + borderRadius: const BorderRadius.all(Radius.circular(3.0)), ); } else if (isToday) { dayText = Text( @@ -512,11 +622,12 @@ class _MacosDatePickerState extends State { ); decoration = BoxDecoration( color: datePickerTheme.monthViewSelectedDateColor, - borderRadius: BorderRadius.circular(3.0), + borderRadius: const BorderRadius.all(Radius.circular(3.0)), ); } Widget dayWidget = GestureDetector( + behavior: HitTestBehavior.opaque, onTap: () { setState(() { _isDaySelected = true; @@ -524,20 +635,18 @@ class _MacosDatePickerState extends State { }); widget.onDateChanged.call(_formatAsDateTime()); }, - child: Padding( + child: Container( + decoration: decoration, padding: const EdgeInsets.symmetric(vertical: 2.0), - child: Container( - decoration: decoration, - child: Align( - alignment: Alignment.centerRight, - child: Padding( - padding: const EdgeInsets.only(right: 2.0), - child: dayText ?? - Text( - localizations.formatDecimal(day), - style: dayStyle, - ), - ), + child: Align( + alignment: Alignment.centerRight, + child: Padding( + padding: const EdgeInsets.only(right: 2.0), + child: dayText ?? + Text( + localizations.formatDecimal(day), + style: dayStyle, + ), ), ), ), diff --git a/lib/src/theme/icon_theme.dart b/lib/src/theme/icon_theme.dart index 9e7377c8..f3b801f7 100644 --- a/lib/src/theme/icon_theme.dart +++ b/lib/src/theme/icon_theme.dart @@ -210,7 +210,7 @@ class MacosIconThemeData with Diagnosticable { @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); - properties.add(ColorProperty('color', color, defaultValue: null)); + properties.add(ColorProperty('MacosColor', color, defaultValue: null)); properties.add(DoubleProperty('opacity', opacity, defaultValue: null)); properties.add(DoubleProperty('size', size, defaultValue: null)); } diff --git a/lib/src/theme/macos_colors.dart b/lib/src/theme/macos_colors.dart index f5d92d80..73985d8f 100644 --- a/lib/src/theme/macos_colors.dart +++ b/lib/src/theme/macos_colors.dart @@ -85,6 +85,77 @@ class MacosColor extends Color { static int getAlphaFromOpacity(double opacity) { return (opacity.clamp(0.0, 1.0) * 255).round(); } + + /// Returns a new color that matches this color with the alpha channel + /// replaced with the given `opacity` (which ranges from 0.0 to 1.0). + /// + /// Out of range values will have unexpected effects. + @override + MacosColor withOpacity(double opacity) { + assert(opacity >= 0.0 && opacity <= 1.0); + return withAlpha((255.0 * opacity).round()); + } + + /// Returns a new color that matches this color with the alpha channel + /// replaced with `a` (which ranges from 0 to 255). + /// + /// Out of range values will have unexpected effects. + @override + MacosColor withAlpha(int a) { + return MacosColor.fromARGB(a, red, green, blue); + } + + /// Darkens a [MacosColor] by a [percent] amount (100 = black) without + /// changing the tint of the color. + static MacosColor darken(MacosColor c, [int percent = 10]) { + assert(1 <= percent && percent <= 100); + var f = 1 - percent / 100; + return MacosColor.fromARGB( + c.alpha, + (c.red * f).round(), + (c.green * f).round(), + (c.blue * f).round(), + ); + } + + /// Lightens a [MacosColor] by a [percent] amount (100 = white) without + /// changing the tint of the color + static MacosColor lighten(MacosColor c, [int percent = 10]) { + assert(1 <= percent && percent <= 100); + var p = percent / 100; + return MacosColor.fromARGB( + c.alpha, + c.red + ((255 - c.red) * p).round(), + c.green + ((255 - c.green) * p).round(), + c.blue + ((255 - c.blue) * p).round(), + ); + } + + @override + bool operator ==(Object other) { + if (identical(this, other)) { + return true; + } + if (other.runtimeType != runtimeType) { + return false; + } + return other is MacosColor && other.value == value; + } + + @override + int get hashCode => value.hashCode; + + @override + String toString() { + return 'MacosColor(0x${value.toRadixString(16).padLeft(8, '0')})'; + } +} + +extension ColorX on Color { + /// Returns a [MacosColor] with the same color values as this [Color]. + MacosColor toMacosColor() { + return MacosColor(value); + } } /// A collection of color values lifted from the macOS system color picker. diff --git a/lib/src/theme/macos_theme.dart b/lib/src/theme/macos_theme.dart index ac03ea2d..87405882 100644 --- a/lib/src/theme/macos_theme.dart +++ b/lib/src/theme/macos_theme.dart @@ -3,8 +3,8 @@ import 'package:macos_ui/macos_ui.dart'; import 'package:macos_ui/src/library.dart'; CupertinoDynamicColor _kScrollbarColor = CupertinoDynamicColor.withBrightness( - color: MacosColors.systemGrayColor.color.withOpacity(.8), - darkColor: MacosColors.systemGrayColor.darkColor.withOpacity(.8), + color: MacosColors.systemGrayColor.color.withOpacity(0.8), + darkColor: MacosColors.systemGrayColor.darkColor.withOpacity(0.8), ); /// Applies a macOS-style theme to descendant macOS widgets. @@ -213,19 +213,16 @@ class MacosThemeData with Diagnosticable { final Brightness _brightness = brightness ?? Brightness.light; final bool isDark = _brightness == Brightness.dark; primaryColor ??= MacosColors.controlAccentColor; + canvasColor ??= isDark - ? CupertinoColors.systemBackground.darkElevatedColor - : CupertinoColors.systemBackground; - typography ??= MacosTypography( - color: _brightness == Brightness.light - ? CupertinoColors.black - : CupertinoColors.white, - ); + ? const Color.fromRGBO(40, 40, 40, 1.0) + : const Color.fromRGBO(246, 246, 246, 1.0); + typography ??= + isDark ? MacosTypography.lightOpaque() : MacosTypography.darkOpaque(); pushButtonTheme ??= PushButtonThemeData( color: primaryColor, - secondaryColor: isDark - ? const Color.fromRGBO(56, 56, 56, 1.0) - : const Color.fromRGBO(218, 218, 223, 1.0), + secondaryColor: + isDark ? const Color.fromRGBO(110, 109, 112, 1.0) : MacosColors.white, disabledColor: isDark ? const Color.fromRGBO(255, 255, 255, 0.1) : const Color.fromRGBO(244, 245, 245, 1.0), diff --git a/lib/src/theme/overlay_filter.dart b/lib/src/theme/overlay_filter.dart index 2dd9b78e..13ad0ccb 100644 --- a/lib/src/theme/overlay_filter.dart +++ b/lib/src/theme/overlay_filter.dart @@ -1,6 +1,7 @@ import 'dart:ui'; import 'package:macos_ui/macos_ui.dart'; +import 'package:macos_ui/src/layout/wallpaper_tinting_settings/wallpaper_tinting_override.dart'; import 'package:macos_ui/src/library.dart'; /// {@template macosOverlayFilter} @@ -36,41 +37,43 @@ class MacosOverlayFilter extends StatelessWidget { Widget build(BuildContext context) { final brightness = MacosTheme.brightnessOf(context); - return Container( - decoration: BoxDecoration( - color: color ?? - (brightness.isDark - ? const Color.fromRGBO(30, 30, 30, 1) - : const Color.fromRGBO(242, 242, 247, 1)), - boxShadow: [ - BoxShadow( - color: brightness - .resolve( - CupertinoColors.systemGrey.color, - CupertinoColors.black, - ) - .withOpacity(0.25), - offset: const Offset(0, 4), - spreadRadius: 4.0, - blurRadius: 8.0, - ), - ], - border: Border.all( - color: brightness.resolve( - CupertinoColors.systemGrey3.color, - CupertinoColors.systemGrey3.darkColor, + return WallpaperTintingOverride( + child: Container( + decoration: BoxDecoration( + color: color ?? + (brightness.isDark + ? const Color.fromRGBO(30, 30, 30, 1) + : const Color.fromRGBO(242, 242, 247, 1)), + boxShadow: [ + BoxShadow( + color: brightness + .resolve( + CupertinoColors.systemGrey.color, + CupertinoColors.black, + ) + .withOpacity(0.25), + offset: const Offset(0, 4), + spreadRadius: 4.0, + blurRadius: 8.0, + ), + ], + border: Border.all( + color: brightness.resolve( + CupertinoColors.systemGrey3.color, + CupertinoColors.systemGrey3.darkColor, + ), ), + borderRadius: borderRadius, ), - borderRadius: borderRadius, - ), - child: ClipRRect( - borderRadius: borderRadius, - child: BackdropFilter( - filter: ImageFilter.blur( - sigmaX: 20.0, - sigmaY: 20.0, + child: ClipRRect( + borderRadius: borderRadius, + child: BackdropFilter( + filter: ImageFilter.blur( + sigmaX: 20.0, + sigmaY: 20.0, + ), + child: child, ), - child: child, ), ), ); diff --git a/lib/src/theme/tooltip_theme.dart b/lib/src/theme/tooltip_theme.dart index b0958ea7..1551c8bd 100644 --- a/lib/src/theme/tooltip_theme.dart +++ b/lib/src/theme/tooltip_theme.dart @@ -89,7 +89,7 @@ class MacosTooltipThemeData with Diagnosticable { brightness.isDark ? CupertinoColors.white : CupertinoColors.black, ), decoration: () { - final radius = BorderRadius.circular(2.0); + const radius = BorderRadius.all(Radius.circular(2.0)); final shadow = [ BoxShadow( color: brightness.isDark diff --git a/lib/src/theme/typography.dart b/lib/src/theme/typography.dart index 670d694c..7b2f72a7 100644 --- a/lib/src/theme/typography.dart +++ b/lib/src/theme/typography.dart @@ -1,5 +1,7 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/foundation.dart'; +import 'package:macos_ui/src/theme/macos_colors.dart'; +import 'package:macos_ui/src/theme/macos_theme.dart'; const _kDefaultFontFamily = '.AppleSystemUIFont'; @@ -16,8 +18,8 @@ const _kDefaultFontFamily = '.AppleSystemUIFont'; class MacosTypography with Diagnosticable { /// Creates a typography that uses the given values. /// - /// Rather than creating a new typography, consider using [MacosTypography.black] - /// or [MacosTypography.white]. + /// Rather than creating a new typography, consider using [MacosTypography.darkOpaque] + /// or [MacosTypography.lightOpaque]. /// /// If you do decide to create your own typography, consider using one of /// those predefined themes as a starting point for [copyWith]. @@ -65,18 +67,11 @@ class MacosTypography with Diagnosticable { ); headline ??= TextStyle( fontFamily: _kDefaultFontFamily, - fontWeight: FontWeight.w400, + fontWeight: FontWeight.w700, fontSize: 13, letterSpacing: -0.08, color: color, ); - subheadline ??= TextStyle( - fontFamily: _kDefaultFontFamily, - fontWeight: FontWeight.w400, - fontSize: 11, - letterSpacing: 0.06, - color: color, - ); body ??= TextStyle( fontFamily: _kDefaultFontFamily, fontWeight: FontWeight.w400, @@ -90,6 +85,13 @@ class MacosTypography with Diagnosticable { fontSize: 12, color: color, ); + subheadline ??= TextStyle( + fontFamily: _kDefaultFontFamily, + fontWeight: FontWeight.w400, + fontSize: 11, + letterSpacing: 0.06, + color: color, + ); footnote ??= TextStyle( fontFamily: _kDefaultFontFamily, fontWeight: FontWeight.w400, @@ -106,7 +108,7 @@ class MacosTypography with Diagnosticable { ); caption2 ??= TextStyle( fontFamily: _kDefaultFontFamily, - fontWeight: FontWeight.w400, + fontWeight: MacosFontWeight.w510, fontSize: 10, letterSpacing: 0.12, color: color, @@ -140,8 +142,10 @@ class MacosTypography with Diagnosticable { required this.caption2, }); - static MacosTypography black = MacosTypography(color: CupertinoColors.black); - static MacosTypography white = MacosTypography(color: CupertinoColors.white); + factory MacosTypography.darkOpaque() => + MacosTypography(color: MacosColors.labelColor.color); + factory MacosTypography.lightOpaque() => + MacosTypography(color: MacosColors.labelColor.darkColor); /// Style used for body text. final TextStyle body; @@ -228,10 +232,15 @@ class MacosTypography with Diagnosticable { ); } + static MacosTypography of(BuildContext context) { + final theme = MacosTheme.of(context); + return theme.typography; + } + @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); - final defaultStyle = MacosTypography.black; + final defaultStyle = MacosTypography.darkOpaque(); properties.add(DiagnosticsProperty( 'largeTitle', largeTitle, @@ -289,3 +298,150 @@ class MacosTypography with Diagnosticable { )); } } + +/// The thickness of the glyphs used to draw the text. +/// +/// Implements [FontWeight] in order to provide the following custom weight +/// values that Apple use in some of their text styles: +/// * [w510] +/// * [w590] +/// * [w860] +/// +/// Reference: +/// * [macOS Sonoma Figma Kit](https://www.figma.com/file/IX6ph2VWrJiRoMTI1Byz0K/Apple-Design-Resources---macOS-(Community)?node-id=0%3A1745&mode=dev) +class MacosFontWeight implements FontWeight { + const MacosFontWeight._(this.index, this.value); + + /// The encoded integer value of this font weight. + @override + final int index; + + /// The thickness value of this font weight. + @override + final int value; + + /// Thin, the least thick + static const MacosFontWeight w100 = MacosFontWeight._(0, 100); + + /// Extra-light + static const MacosFontWeight w200 = MacosFontWeight._(1, 200); + + /// Light + static const MacosFontWeight w300 = MacosFontWeight._(2, 300); + + /// Normal / regular / plain + static const MacosFontWeight w400 = MacosFontWeight._(3, 400); + + /// Medium + static const MacosFontWeight w500 = MacosFontWeight._(4, 500); + + /// An Apple-specific font weight. + /// + /// When [MacosTypography.caption1] needs to be bolded, use this value. + static const MacosFontWeight w510 = MacosFontWeight._(5, 510); + + /// An Apple-specific font weight. + /// + /// When [MacosTypography.body], [MacosTypography.callout], + /// [MacosTypography.subheadline], [MacosTypography.footnote], or + /// [MacosTypography.caption2] need to be bolded, use this value. + static const MacosFontWeight w590 = MacosFontWeight._(6, 590); + + /// Semi-bold + static const MacosFontWeight w600 = MacosFontWeight._(7, 600); + + /// Bold + static const MacosFontWeight w700 = MacosFontWeight._(8, 700); + + /// Extra-bold + static const MacosFontWeight w800 = MacosFontWeight._(9, 800); + + /// An Apple-specific font weight. + /// + /// When [MacosTypography.title3] needs to be bolded, use this value. + static const MacosFontWeight w860 = MacosFontWeight._(10, 860); + + /// Black, the most thick + static const MacosFontWeight w900 = MacosFontWeight._(11, 900); + + /// The default font weight. + static const MacosFontWeight normal = w400; + + /// A commonly used font weight that is heavier than normal. + static const MacosFontWeight bold = w700; + + /// A list of all the font weights. + static const List values = [ + w100, + w200, + w300, + w400, + w500, + w510, + w590, + w600, + w700, + w800, + w860, + w900, + ]; + + /// Linearly interpolates between two font weights. + /// + /// Rather than using fractional weights, the interpolation rounds to the + /// nearest weight. + /// + /// If both `a` and `b` are null, then this method will return null. Otherwise, + /// any null values for `a` or `b` are interpreted as equivalent to [normal] + /// (also known as [w400]). + /// + /// The `t` argument represents position on the timeline, with 0.0 meaning + /// that the interpolation has not started, returning `a` (or something + /// equivalent to `a`), 1.0 meaning that the interpolation has finished, + /// returning `b` (or something equivalent to `b`), and values in between + /// meaning that the interpolation is at the relevant point on the timeline + /// between `a` and `b`. The interpolation can be extrapolated beyond 0.0 and + /// 1.0, so negative values and values greater than 1.0 are valid (and can + /// easily be generated by curves such as [Curves.elasticInOut]). The result + /// is clamped to the range [w100]–[w900]. + /// + /// Values for `t` are usually obtained from an [Animation], such as + /// an [AnimationController]. + static MacosFontWeight? lerp( + MacosFontWeight? a, + MacosFontWeight? b, + double t, + ) { + if (a == null && b == null) { + return null; + } + return values[_lerpInt((a ?? normal).index, (b ?? normal).index, t) + .round() + .clamp(0, 8)]; + } + + @override + String toString() { + return const { + 0: 'MacosFontWeight.w100', + 1: 'MacosFontWeight.w200', + 2: 'MacosFontWeight.w300', + 3: 'MacosFontWeight.w400', + 4: 'MacosFontWeight.w500', + 5: 'MacosFontWeight.w510', + 6: 'MacosFontWeight.w590', + 7: 'MacosFontWeight.w600', + 8: 'MacosFontWeight.w700', + 9: 'MacosFontWeight.w800', + 10: 'MacosFontWeight.w860', + 11: 'MacosFontWeight.w900', + }[index]!; + } +} + +/// Linearly interpolate between two integers. +/// +/// Same as [lerpDouble] but specialized for non-null `int` type. +double _lerpInt(int a, int b, double t) { + return a + (b - a) * t; +} diff --git a/lib/src/utils.dart b/lib/src/utils.dart index 08f2debc..1f2d8a1e 100644 --- a/lib/src/utils.dart +++ b/lib/src/utils.dart @@ -1,3 +1,6 @@ +import 'dart:io'; + +import 'package:flutter/foundation.dart'; import 'package:macos_ui/macos_ui.dart'; import 'package:macos_ui/src/library.dart'; @@ -57,39 +60,29 @@ Color iconLuminance(Color backgroundColor, bool isDark) { } } -String intToMonthAbbr(int month) { - switch (month) { - case 1: - return 'Jan'; - case 2: - return 'Feb'; - case 3: - return 'Mar'; - case 4: - return 'Apr'; - case 5: - return 'May'; - case 6: - return 'Jun'; - case 7: - return 'Jul'; - case 8: - return 'Aug'; - case 9: - return 'Sep'; - case 10: - return 'Oct'; - case 11: - return 'Nov'; - case 12: - return 'Dec'; - default: - throw Exception('Unsupported value'); - } -} - class Unsupported { const Unsupported(this.message); final String message; } + +/// A class that ensures that the application's macOS window's brightness +/// matches the given brightness. +class MacOSBrightnessOverrideHandler { + static Brightness? _lastBrightness; + + /// Ensures that the application's macOS window's brightness matches + /// [currentBrightness]. + /// + /// For performance reasons, the brightness setting will only be overridden if + /// [currentBrightness] differs from the value it had when this method was + /// previously called. Therefore, it is safe to call this method frequently. + static void ensureMatchingBrightness(Brightness currentBrightness) { + if (kIsWeb) return; + if (!Platform.isMacOS) return; + if (currentBrightness == _lastBrightness) return; + + WindowManipulator.overrideMacOSBrightness(dark: currentBrightness.isDark); + _lastBrightness = currentBrightness; + } +} diff --git a/pubspec.lock b/pubspec.lock index 6cfa9f14..f80cd1d5 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -5,50 +5,34 @@ packages: dependency: transitive description: name: _fe_analyzer_shared - sha256: "0c80aeab9bc807ab10022cd3b2f4cf2ecdf231949dc1ddd9442406a003f19201" + sha256: "8880b4cfe7b5b17d57c052a5a3a8cc1d4f546261c7cc8fbd717bd53f48db0568" url: "https://pub.dev" source: hosted - version: "52.0.0" + version: "59.0.0" analyzer: dependency: transitive description: name: analyzer - sha256: cd8ee83568a77f3ae6b913a36093a1c9b1264e7cb7f834d9ddd2311dade9c1f4 + sha256: a89627f49b0e70e068130a36571409726b04dab12da7e5625941d2c8ec278b96 url: "https://pub.dev" source: hosted - version: "5.4.0" - analyzer_plugin: - dependency: transitive - description: - name: analyzer_plugin - sha256: c1d5f167683de03d5ab6c3b53fc9aeefc5d59476e7810ba7bbddff50c6f4392d - url: "https://pub.dev" - source: hosted - version: "0.11.2" - ansicolor: - dependency: transitive - description: - name: ansicolor - sha256: "607f8fa9786f392043f169898923e6c59b4518242b68b8862eb8a8b7d9c30b4a" - url: "https://pub.dev" - source: hosted - version: "2.0.1" + version: "5.11.1" args: dependency: transitive description: name: args - sha256: "4cab82a83ffef80b262ddedf47a0a8e56ee6fbf7fe21e6e768b02792034dd440" + sha256: c372bb384f273f0c2a8aaaa226dad84dc27c8519a691b888725dec59518ad53a url: "https://pub.dev" source: hosted - version: "2.4.0" + version: "2.4.1" async: dependency: transitive description: name: async - sha256: bfe67ef28df125b7dddcea62755991f807aa39a2492a23e1550161692950bbe0 + sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" url: "https://pub.dev" source: hosted - version: "2.10.0" + version: "2.11.0" boolean_selector: dependency: transitive description: @@ -61,10 +45,10 @@ packages: dependency: transitive description: name: characters - sha256: e6a326c8af69605aec75ed6c187d06b349707a27fbff8222ca9cc2cff167975c + sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" url: "https://pub.dev" source: hosted - version: "1.2.1" + version: "1.3.0" clock: dependency: transitive description: @@ -77,10 +61,10 @@ packages: dependency: transitive description: name: collection - sha256: cfc915e6923fe5ce6e153b0723c753045de46de1b4d63771530504004a45fae0 + sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c" url: "https://pub.dev" source: hosted - version: "1.17.0" + version: "1.17.1" convert: dependency: transitive description: @@ -101,42 +85,10 @@ packages: dependency: transitive description: name: crypto - sha256: aa274aa7774f8964e4f4f38cc994db7b6158dd36e9187aaceaddc994b35c6c67 - url: "https://pub.dev" - source: hosted - version: "3.0.2" - csslib: - dependency: transitive - description: - name: csslib - sha256: b36c7f7e24c0bdf1bf9a3da461c837d1de64b9f8beb190c9011d8c72a3dfd745 - url: "https://pub.dev" - source: hosted - version: "0.17.2" - dart_code_metrics: - dependency: "direct dev" - description: - name: dart_code_metrics - sha256: "026e28da197a03caeccccc0b174ec98ef03da3c81c4543314d7add121aab4375" + sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab url: "https://pub.dev" source: hosted - version: "5.6.0" - dart_code_metrics_presets: - dependency: transitive - description: - name: dart_code_metrics_presets - sha256: "9c51724f836aebc4465228954cb5757e5a99737af26a452b5dec0a2d5d0b4d66" - url: "https://pub.dev" - source: hosted - version: "1.2.0" - dart_style: - dependency: transitive - description: - name: dart_style - sha256: "7a03456c3490394c8e7665890333e91ae8a49be43542b616e414449ac358acd4" - url: "https://pub.dev" - source: hosted - version: "2.2.4" + version: "3.0.3" fake_async: dependency: transitive description: @@ -162,10 +114,10 @@ packages: dependency: "direct dev" description: name: flutter_lints - sha256: aeb0b80a8b3709709c9cc496cdc027c5b3216796bc0af0ce1007eaf24464fd4c + sha256: "2118df84ef0c3ca93f96123a616ae8540879991b8b57af2f81b76a7ada49b2a4" url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "2.0.2" flutter_test: dependency: "direct dev" description: flutter @@ -187,22 +139,6 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.1" - html: - dependency: transitive - description: - name: html - sha256: d9793e10dbe0e6c364f4c59bf3e01fb33a9b2a674bc7a1081693dba0614b6269 - url: "https://pub.dev" - source: hosted - version: "0.15.1" - http: - dependency: transitive - description: - name: http - sha256: "6aa2946395183537c8b880962d935877325d6a09a2867c3970c05c0fed6ac482" - url: "https://pub.dev" - source: hosted - version: "0.13.5" http_multi_server: dependency: transitive description: @@ -231,26 +167,18 @@ packages: dependency: transitive description: name: js - sha256: "5528c2f391ededb7775ec1daa69e65a2d61276f7552de2b5f7b8d34ee9fd4ab7" + sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 url: "https://pub.dev" source: hosted - version: "0.6.5" - json_annotation: - dependency: transitive - description: - name: json_annotation - sha256: c33da08e136c3df0190bd5bbe51ae1df4a7d96e7954d1d7249fea2968a72d317 - url: "https://pub.dev" - source: hosted - version: "4.8.0" + version: "0.6.7" lints: dependency: transitive description: name: lints - sha256: "5e4a9cd06d447758280a8ac2405101e0e2094d2a1dbdd3756aec3fe7775ba593" + sha256: "6b0206b0bf4f04961fc5438198ccb3a885685cd67d4d4a32cc20ad7f8adbe015" url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "2.1.0" logging: dependency: transitive description: @@ -259,14 +187,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.1" + macos_window_utils: + dependency: "direct main" + description: + name: macos_window_utils + sha256: b78a210aa70ca7ccad6e7b7b810fb4689c507f4a46e299214900b2a1eb70ea23 + url: "https://pub.dev" + source: hosted + version: "1.1.3" matcher: dependency: transitive description: name: matcher - sha256: "16db949ceee371e9b99d22f88fa3a73c4e59fd0afed0bd25fc336eb76c198b72" + sha256: "6501fbd55da300384b768785b83e5ce66991266cec21af89ab9ae7f5ce1c4cbb" url: "https://pub.dev" source: hosted - version: "0.12.13" + version: "0.12.15" material_color_utilities: dependency: transitive description: @@ -279,10 +215,10 @@ packages: dependency: transitive description: name: meta - sha256: "6c268b42ed578a53088d834796959e4a1814b5e9e164f147f580a386e5decf42" + sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3" url: "https://pub.dev" source: hosted - version: "1.8.0" + version: "1.9.1" mime: dependency: transitive description: @@ -303,10 +239,10 @@ packages: dependency: transitive description: name: node_preamble - sha256: "8ebdbaa3b96d5285d068f80772390d27c21e1fa10fb2df6627b1b9415043608d" + sha256: "6e7eac89047ab8a8d26cf16127b5ed26de65209847630400f9aefd7cd5c730db" url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "2.0.2" package_config: dependency: transitive description: @@ -319,26 +255,10 @@ packages: dependency: transitive description: name: path - sha256: db9d4f58c908a4ba5953fcee2ae317c94889433e5024c27ce74a37f94267945b + sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" url: "https://pub.dev" source: hosted - version: "1.8.2" - petitparser: - dependency: transitive - description: - name: petitparser - sha256: "49392a45ced973e8d94a85fdb21293fbb40ba805fc49f2965101ae748a3683b4" - url: "https://pub.dev" - source: hosted - version: "5.1.0" - platform: - dependency: transitive - description: - name: platform - sha256: "4a451831508d7d6ca779f7ac6e212b4023dd5a7d08a27a63da33756410e32b76" - url: "https://pub.dev" - source: hosted - version: "3.1.0" + version: "1.8.3" pool: dependency: transitive description: @@ -347,62 +267,46 @@ packages: url: "https://pub.dev" source: hosted version: "1.5.1" - process: - dependency: transitive - description: - name: process - sha256: "53fd8db9cec1d37b0574e12f07520d582019cb6c44abf5479a01505099a34a09" - url: "https://pub.dev" - source: hosted - version: "4.2.4" pub_semver: dependency: transitive description: name: pub_semver - sha256: "307de764d305289ff24ad257ad5c5793ce56d04947599ad68b3baa124105fc17" + sha256: "40d3ab1bbd474c4c2328c91e3a7df8c6dd629b79ece4c4bd04bee496a224fb0c" url: "https://pub.dev" source: hosted - version: "2.1.3" - pub_updater: - dependency: transitive - description: - name: pub_updater - sha256: "42890302ab2672adf567dc2b20e55b4ecc29d7e19c63b6b98143ab68dd717d3a" - url: "https://pub.dev" - source: hosted - version: "0.2.4" + version: "2.1.4" shelf: dependency: transitive description: name: shelf - sha256: c24a96135a2ccd62c64b69315a14adc5c3419df63b4d7c05832a346fdb73682c + sha256: ad29c505aee705f41a4d8963641f91ac4cee3c8fad5947e033390a7bd8180fa4 url: "https://pub.dev" source: hosted - version: "1.4.0" + version: "1.4.1" shelf_packages_handler: dependency: transitive description: name: shelf_packages_handler - sha256: aef74dc9195746a384843102142ab65b6a4735bb3beea791e63527b88cc83306 + sha256: "89f967eca29607c933ba9571d838be31d67f53f6e4ee15147d5dc2934fee1b1e" url: "https://pub.dev" source: hosted - version: "3.0.1" + version: "3.0.2" shelf_static: dependency: transitive description: name: shelf_static - sha256: e792b76b96a36d4a41b819da593aff4bdd413576b3ba6150df5d8d9996d2e74c + sha256: a41d3f53c4adf0f57480578c1d61d90342cd617de7fc8077b1304643c2d85c1e url: "https://pub.dev" source: hosted - version: "1.1.1" + version: "1.1.2" shelf_web_socket: dependency: transitive description: name: shelf_web_socket - sha256: a988c0e8d8ffbdb8a28aa7ec8e449c260f3deb808781fe1284d22c5bba7156e8 + sha256: "9ca081be41c60190ebcb4766b2486a7d50261db7bd0f5d9615f2d653637a84c1" url: "https://pub.dev" source: hosted - version: "1.0.3" + version: "1.0.4" sky_engine: dependency: transitive description: flutter @@ -468,34 +372,34 @@ packages: dependency: transitive description: name: test - sha256: a5fcd2d25eeadbb6589e80198a47d6a464ba3e2049da473943b8af9797900c2d + sha256: "3dac9aecf2c3991d09b9cdde4f98ded7b30804a88a0d7e4e7e1678e78d6b97f4" url: "https://pub.dev" source: hosted - version: "1.22.0" + version: "1.24.1" test_api: dependency: transitive description: name: test_api - sha256: ad540f65f92caa91bf21dfc8ffb8c589d6e4dc0c2267818b4cc2792857706206 + sha256: eb6ac1540b26de412b3403a163d919ba86f6a973fe6cc50ae3541b80092fdcfb url: "https://pub.dev" source: hosted - version: "0.4.16" + version: "0.5.1" test_core: dependency: transitive description: name: test_core - sha256: "0ef9755ec6d746951ba0aabe62f874b707690b5ede0fecc818b138fcc9b14888" + sha256: "5138dbffb77b2289ecb12b81c11ba46036590b72a64a7a90d6ffb880f1a29e93" url: "https://pub.dev" source: hosted - version: "0.4.20" + version: "0.5.1" typed_data: dependency: transitive description: name: typed_data - sha256: "26f87ade979c47a150c9eaab93ccd2bebe70a27dc0b4b29517f2904f04eb11a5" + sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c url: "https://pub.dev" source: hosted - version: "1.3.1" + version: "1.3.2" vector_math: dependency: transitive description: @@ -508,26 +412,26 @@ packages: dependency: transitive description: name: vm_service - sha256: e7fb6c2282f7631712b69c19d1bff82f3767eea33a2321c14fa59ad67ea391c7 + sha256: f3743ca475e0c9ef71df4ba15eb2d7684eecd5c8ba20a462462e4e8b561b2e11 url: "https://pub.dev" source: hosted - version: "9.4.0" + version: "11.6.0" watcher: dependency: transitive description: name: watcher - sha256: "6a7f46926b01ce81bfc339da6a7f20afbe7733eff9846f6d6a5466aa4c6667c0" + sha256: "3d2ad6751b3c16cf07c7fca317a1413b3f26530319181b37e3b9039b84fc01d8" url: "https://pub.dev" source: hosted - version: "1.0.2" + version: "1.1.0" web_socket_channel: dependency: transitive description: name: web_socket_channel - sha256: ca49c0bc209c687b887f30527fb6a9d80040b072cc2990f34b9bec3e7663101b + sha256: d88238e5eac9a42bb43ca4e721edba3c08c6354d4a53063afaa568516217621b url: "https://pub.dev" source: hosted - version: "2.3.0" + version: "2.4.0" webkit_inspection_protocol: dependency: transitive description: @@ -536,22 +440,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.2.0" - xml: - dependency: transitive - description: - name: xml - sha256: "979ee37d622dec6365e2efa4d906c37470995871fe9ae080d967e192d88286b5" - url: "https://pub.dev" - source: hosted - version: "6.2.2" yaml: dependency: transitive description: name: yaml - sha256: "23812a9b125b48d4007117254bca50abb6c712352927eece9e155207b1db2370" + sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5" url: "https://pub.dev" source: hosted - version: "3.1.1" + version: "3.1.2" sdks: - dart: ">=2.18.0 <3.0.0" - flutter: ">=1.20.0" + dart: ">=3.0.0 <4.0.0" + flutter: ">=3.10.0" diff --git a/pubspec.yaml b/pubspec.yaml index 4d660503..13865b31 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,22 +1,22 @@ name: macos_ui description: Flutter widgets and themes implementing the current macOS design language. -version: 1.12.2 +version: 2.0.0 homepage: "https://macosui.dev" repository: "https://github.com/GroovinChip/macos_ui" environment: - sdk: ">=2.17.0 <3.0.0" - flutter: ">=1.20.0" + sdk: ">=3.0.0 <4.0.0" + flutter: ">=3.10.0" dependencies: flutter: sdk: flutter + macos_window_utils: ^1.1.3 dev_dependencies: flutter_test: sdk: flutter - dart_code_metrics: ^5.6.0 - flutter_lints: ^2.0.1 + flutter_lints: ^2.0.2 mocktail: ^0.3.0 flutter: diff --git a/test/buttons/back_button_test.dart b/test/buttons/back_button_test.dart index 90e85f65..cd709ee1 100644 --- a/test/buttons/back_button_test.dart +++ b/test/buttons/back_button_test.dart @@ -44,6 +44,7 @@ void main() { await tester.pumpWidget( MacosApp( home: MacosWindow( + disableWallpaperTinting: true, child: MacosScaffold( toolBar: ToolBar( leading: MacosBackButton( diff --git a/test/buttons/checkbox_test.dart b/test/buttons/checkbox_test.dart index 09881007..7ba24553 100644 --- a/test/buttons/checkbox_test.dart +++ b/test/buttons/checkbox_test.dart @@ -62,7 +62,7 @@ void main() { [ 'state: "unchecked"', 'enabled', - 'size: 16.0', + 'size: 14.0', 'activeColor: null', 'disabledColor: quaternaryLabel(*color = Color(0x2d3c3c43)*, darkColor = Color(0x28ebebf5), highContrastColor = Color(0x423c3c43), darkHighContrastColor = Color(0x3debebf5), resolved by: UNRESOLVED)', 'offBorderColor: tertiaryLabel(*color = Color(0x4c3c3c43)*, darkColor = Color(0x4cebebf5), highContrastColor = Color(0x603c3c43), darkHighContrastColor = Color(0x60ebebf5), resolved by: UNRESOLVED)', diff --git a/test/buttons/help_button_test.dart b/test/buttons/help_button_test.dart index 2f11f430..a18b7cf9 100644 --- a/test/buttons/help_button_test.dart +++ b/test/buttons/help_button_test.dart @@ -52,6 +52,7 @@ void main() { helpButtonTheme: darkHelpButtonThemeData, ), home: MacosWindow( + disableWallpaperTinting: true, child: MacosScaffold( children: [ ContentArea( diff --git a/test/buttons/icon_button_test.dart b/test/buttons/icon_button_test.dart index 057f6a3b..8e52e106 100644 --- a/test/buttons/icon_button_test.dart +++ b/test/buttons/icon_button_test.dart @@ -45,6 +45,7 @@ void main() { await tester.pumpWidget( MacosApp( home: MacosWindow( + disableWallpaperTinting: true, child: MacosScaffold( children: [ ContentArea( diff --git a/test/buttons/pulldown_button_test.dart b/test/buttons/pulldown_button_test.dart index e22a355e..668914d0 100644 --- a/test/buttons/pulldown_button_test.dart +++ b/test/buttons/pulldown_button_test.dart @@ -75,7 +75,7 @@ void main() { MacosPulldownMenuItem( title: const Text('one'), onTap: () { - menuItemTapCounters[0] += 1; + menuItemTapCounters.first += 1; }, ), MacosPulldownMenuItem( diff --git a/test/buttons/push_button_test.dart b/test/buttons/push_button_test.dart index 96dc571e..8bb294a0 100644 --- a/test/buttons/push_button_test.dart +++ b/test/buttons/push_button_test.dart @@ -27,7 +27,7 @@ void main() { ContentArea( builder: (context, _) { return PushButton( - buttonSize: ButtonSize.small, + controlSize: ControlSize.regular, onPressed: mockOnPressedFunction.handler, child: const Text('Push me'), ); @@ -55,12 +55,13 @@ void main() { pushButtonTheme: darkPushButtonThemeData, ), home: MacosWindow( + disableWallpaperTinting: true, child: MacosScaffold( children: [ ContentArea( builder: (context, _) { return PushButton( - buttonSize: ButtonSize.small, + controlSize: ControlSize.regular, key: pushButtonKey, onPressed: mockOnTapCancelFunction.handler, child: const Text('Push me'), @@ -83,7 +84,7 @@ void main() { testWidgets('debugFillProperties', (tester) async { final builder = DiagnosticPropertiesBuilder(); const PushButton( - buttonSize: ButtonSize.small, + controlSize: ControlSize.regular, child: Text('Test'), ).debugFillProperties(builder); @@ -95,7 +96,7 @@ void main() { expect( description, [ - 'buttonSize: small', + 'controlSize: regular', 'color: null', 'disabledColor: null', 'pressedOpacity: 0.4', @@ -103,7 +104,7 @@ void main() { 'semanticLabel: null', 'borderRadius: BorderRadius.circular(4.0)', 'disabled', - 'isSecondary: null', + 'secondary: null', ], ); }); diff --git a/test/buttons/radio_button_test.dart b/test/buttons/radio_button_test.dart index d561b643..7d878d1c 100644 --- a/test/buttons/radio_button_test.dart +++ b/test/buttons/radio_button_test.dart @@ -17,6 +17,7 @@ void main() { await tester.pumpWidget( MacosApp( home: MacosWindow( + disableWallpaperTinting: true, child: MacosScaffold( children: [ ContentArea( diff --git a/test/buttons/switch_test.dart b/test/buttons/switch_test.dart index 642f0f11..a02bc40b 100644 --- a/test/buttons/switch_test.dart +++ b/test/buttons/switch_test.dart @@ -52,10 +52,12 @@ void main() { description, [ 'unchecked', + 'size: regular', 'dragStartBehavior: start', 'disabled', 'activeColor: null', 'trackColor: null', + 'knobColor: null', 'semanticLabel: null', ], ); diff --git a/test/indicators/relevance_indicator_test.dart b/test/indicators/relevance_indicator_test.dart deleted file mode 100644 index dbdea9ba..00000000 --- a/test/indicators/relevance_indicator_test.dart +++ /dev/null @@ -1,31 +0,0 @@ -import 'package:flutter/foundation.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:macos_ui/macos_ui.dart'; - -void main() { - testWidgets('debugFillProperties', (tester) async { - final builder = DiagnosticPropertiesBuilder(); - const RelevanceIndicator( - value: 50, - amount: 100, - ).debugFillProperties(builder); - - final description = builder.properties - .where((node) => !node.isFiltered(DiagnosticLevel.info)) - .map((node) => node.toString()) - .toList(); - - expect( - description, - [ - 'value: 50', - 'amount: 100', - 'barHeight: 20.0', - 'barWidth: 0.8', - 'selectedColor: label(*color = Color(0xff000000)*, darkColor = Color(0xffffffff), resolved by: UNRESOLVED)', - 'unselectedColor: secondaryLabel(*color = Color(0x993c3c43)*, darkColor = Color(0x99ebebf5), highContrastColor = Color(0xad3c3c43), darkHighContrastColor = Color(0xadebebf5), resolved by: UNRESOLVED)', - 'semanticLabel: null', - ], - ); - }); -} diff --git a/test/indicators/scrollbar_test.dart b/test/indicators/scrollbar_test.dart index f5ffe2c2..1173e096 100644 --- a/test/indicators/scrollbar_test.dart +++ b/test/indicators/scrollbar_test.dart @@ -16,8 +16,8 @@ void main() { testWidgets( 'Scrollbar changes position when scrolled with the mouse wheel', (tester) async { - final Size screenSize = tester.binding.window.physicalSize / - tester.binding.window.devicePixelRatio; + final Size screenSize = + tester.view.physicalSize / tester.view.devicePixelRatio; await tester.pumpWidget( Directionality( diff --git a/test/indicators/slider_test.dart b/test/indicators/slider_test.dart index 23bb0b35..99f963ff 100644 --- a/test/indicators/slider_test.dart +++ b/test/indicators/slider_test.dart @@ -4,8 +4,6 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:macos_ui/macos_ui.dart'; void main() { - final TestWidgetsFlutterBinding binding = - TestWidgetsFlutterBinding.ensureInitialized(); testWidgets('debugFillProperties', (tester) async { final builder = DiagnosticPropertiesBuilder(); MacosSlider( @@ -36,8 +34,8 @@ void main() { }); testWidgets('Continuous slider can move when tapped', (tester) async { - tester.binding.window.physicalSizeTestValue = const Size(100, 50); - binding.window.devicePixelRatioTestValue = 1.0; + tester.view.physicalSize = const Size(100, 50); + tester.view.devicePixelRatio = 1.0; final value = ValueNotifier(0.25); await tester.pumpWidget( @@ -65,12 +63,12 @@ void main() { await tester.pumpAndSettle(); expect(value.value, 0.0); - addTearDown(tester.binding.window.clearPhysicalSizeTestValue); + addTearDown(tester.view.resetPhysicalSize); }); testWidgets('Discrete slider snaps to correct values', (widgetTester) async { - widgetTester.binding.window.physicalSizeTestValue = const Size(100, 50); - binding.window.devicePixelRatioTestValue = 1.0; + widgetTester.view.physicalSize = const Size(100, 50); + widgetTester.view.devicePixelRatio = 1.0; final value = ValueNotifier(0.25); await widgetTester.pumpWidget( @@ -109,6 +107,6 @@ void main() { expect(value.value, 0.5); - addTearDown(widgetTester.binding.window.clearPhysicalSizeTestValue); + addTearDown(widgetTester.view.resetPhysicalSize); }); } diff --git a/test/layout/macos_list_tile_test.dart b/test/layout/macos_list_tile_test.dart index e019a007..b8b65f7f 100644 --- a/test/layout/macos_list_tile_test.dart +++ b/test/layout/macos_list_tile_test.dart @@ -20,6 +20,7 @@ void main() { pushButtonTheme: const PushButtonThemeData(), ), home: MacosWindow( + disableWallpaperTinting: true, child: MacosScaffold( children: [ ContentArea( diff --git a/test/layout/resizeable_pane_test.dart b/test/layout/resizeable_pane_test.dart index 86638a6a..f3ac9f83 100644 --- a/test/layout/resizeable_pane_test.dart +++ b/test/layout/resizeable_pane_test.dart @@ -6,6 +6,10 @@ void main() { const matrix = ResizableSide.values; group('ResizablePane', () { + const double maxSize = 300; + const double minSize = 100; + const double startSize = 200; + for (var side in matrix) { bool verticallyResizable = side == ResizableSide.top; @@ -14,10 +18,6 @@ void main() { ? 'top' : (side == ResizableSide.left ? 'left' : 'right'), () { - const double maxSize = 300; - const double minSize = 100; - const double startSize = 200; - final resizablePane = ResizablePane( builder: (context, scrollController) => const Text('Hello there'), minSize: minSize, @@ -29,6 +29,231 @@ void main() { final view = side == ResizableSide.top ? MacosApp( home: MacosWindow( + disableWallpaperTinting: true, + child: MacosScaffold( + children: [ + ContentArea( + builder: (context, scrollController) { + return Column( + children: [ + const Flexible( + fit: FlexFit.loose, + child: Center( + child: Text('Hello there'), + ), + ), + resizablePane, + ], + ); + }, + ), + ], + ), + ), + ) + : MacosApp( + home: MacosWindow( + disableWallpaperTinting: true, + child: MacosScaffold( + children: [ + resizablePane, + ContentArea( + builder: (context, scrollController) { + return const Text('Hello there'); + }, + ), + ], + ), + ), + ); + + final resizablePaneFinder = find.byWidget(resizablePane); + final dragFinder = find.descendant( + of: resizablePaneFinder, + matching: find.byType(GestureDetector), + ); + + // No need to check if the resizable side is top because directionModifier + // would take -1 if it is the case + final directionModifier = side == ResizableSide.right ? 1 : -1; + final double safeDelta = 50.0 * directionModifier; + final double overflowDelta = 500.0 * directionModifier; + + testWidgets( + 'Default ResizablePane Constructor comes with an internal MacosScrollBar', + (WidgetTester tester) async { + await tester.pumpWidget(view); + expect(find.byType(MacosScrollbar), findsOneWidget); + }, + ); + + testWidgets('initial size equals startSize', (tester) async { + await tester.pumpWidget(view); + + var resizablePaneRenderObject = + tester.renderObject(resizablePaneFinder); + var initialSize = verticallyResizable + ? resizablePaneRenderObject.size.height + : resizablePaneRenderObject.size.width; + + expect(initialSize, startSize); + }); + + testWidgets('dragging wider works $side', (tester) async { + await tester.pumpWidget(view); + + await tester.drag( + dragFinder, + verticallyResizable ? Offset(0, safeDelta) : Offset(safeDelta, 0), + ); + await tester.pump(); + + var resizablePaneRenderObject = + tester.renderObject(resizablePaneFinder); + expect( + verticallyResizable + ? resizablePaneRenderObject.size.height + : resizablePaneRenderObject.size.width, + startSize + safeDelta * directionModifier, + ); + }); + + testWidgets('dragging wider respects maxSize', (tester) async { + await tester.pumpWidget(view); + + await tester.drag( + dragFinder, + verticallyResizable + ? Offset(0, overflowDelta) + : Offset(overflowDelta, 0), + ); + await tester.pump(); + + var resizablePaneRenderObject = + tester.renderObject(resizablePaneFinder); + var currentSize = verticallyResizable + ? resizablePaneRenderObject.size.height + : resizablePaneRenderObject.size.width; + expect(currentSize, maxSize); + }); + + testWidgets( + 'drag events past maxSize have no effect $side', + (tester) async { + await tester.pumpWidget(view); + + final dragStartLocation = tester.getCenter(dragFinder); + final drag = await tester.startGesture(dragStartLocation); + await drag.moveBy( + verticallyResizable + ? Offset(0, overflowDelta) + : Offset(overflowDelta, 0), + ); + await drag.moveBy( + verticallyResizable + ? Offset(0, -10.0 * directionModifier) + : Offset(-10.0 * directionModifier, 0), + ); + await drag.up(); + await tester.pump(); + + var resizablePaneRenderObject = + tester.renderObject(resizablePaneFinder); + var currentSize = verticallyResizable + ? resizablePaneRenderObject.size.height + : resizablePaneRenderObject.size.width; + expect(currentSize, maxSize); + }, + ); + + testWidgets('dragging narrower works', (tester) async { + await tester.pumpWidget(view); + + await tester.drag( + dragFinder, + verticallyResizable + ? Offset(0, -safeDelta) + : Offset(-safeDelta, 0), + ); + await tester.pump(); + + var resizablePaneRenderObject = + tester.renderObject(resizablePaneFinder); + var currentSize = verticallyResizable + ? resizablePaneRenderObject.size.height + : resizablePaneRenderObject.size.width; + expect( + currentSize, + startSize - safeDelta * directionModifier, + ); + }); + + testWidgets('dragging narrower respects minSize', (tester) async { + await tester.pumpWidget(view); + + await tester.drag( + dragFinder, + verticallyResizable + ? Offset(0, -overflowDelta) + : Offset(-overflowDelta, 0), + ); + await tester.pump(); + + var resizablePaneRenderObject = + tester.renderObject(resizablePaneFinder); + var currentSize = verticallyResizable + ? resizablePaneRenderObject.size.height + : resizablePaneRenderObject.size.width; + expect(currentSize, minSize); + }); + + testWidgets( + 'drag events past minSize have no effect', + (tester) async { + await tester.pumpWidget(view); + + final dragStartLocation = tester.getCenter(dragFinder); + final drag = await tester.startGesture(dragStartLocation); + await drag.moveBy( + verticallyResizable + ? Offset(0, -overflowDelta) + : Offset(-overflowDelta, 0), + ); + await drag.moveBy( + verticallyResizable + ? Offset(0, 10.0 * directionModifier) + : Offset(10.0 * directionModifier, 0), + ); + await drag.up(); + await tester.pump(); + + var resizablePaneRenderObject = + tester.renderObject(resizablePaneFinder); + var currentSize = verticallyResizable + ? resizablePaneRenderObject.size.height + : resizablePaneRenderObject.size.width; + expect(currentSize, minSize); + }, + ); + }, + ); + group( + side == ResizableSide.top + ? 'top' + : (side == ResizableSide.left ? 'left' : 'right'), + () { + final resizablePane = ResizablePane.noScrollBar( + minSize: minSize, + startSize: startSize, + maxSize: maxSize, + resizableSide: side, + child: const Text('Hello there'), + ); + + final view = side == ResizableSide.top + ? MacosApp( + home: MacosWindow( + disableWallpaperTinting: true, child: MacosScaffold( children: [ ContentArea( @@ -52,6 +277,7 @@ void main() { ) : MacosApp( home: MacosWindow( + disableWallpaperTinting: true, child: MacosScaffold( children: [ resizablePane, @@ -77,6 +303,14 @@ void main() { final double safeDelta = 50.0 * directionModifier; final double overflowDelta = 500.0 * directionModifier; + testWidgets( + 'ResizablePane.noScrollBar Constructor does not come with an internal MacosScrollBar', + (WidgetTester tester) async { + await tester.pumpWidget(view); + expect(find.byType(MacosScrollbar), findsNothing); + }, + ); + testWidgets('initial size equals startSize', (tester) async { await tester.pumpWidget(view); diff --git a/test/layout/sliver_toolbar_test.dart b/test/layout/sliver_toolbar_test.dart index bcfeb9d4..0d693771 100644 --- a/test/layout/sliver_toolbar_test.dart +++ b/test/layout/sliver_toolbar_test.dart @@ -81,6 +81,8 @@ void main() { tester.getBottomLeft(find.byKey(leadingKey)), const Offset(8.0, 47.0), ); + + await tester.pump(Duration.zero); }, ); @@ -97,6 +99,8 @@ void main() { expect(tester.getTopLeft(navToolbar).dy, 4.0); expect(tester.getSize(toolbar).height, 52.0); expect(tester.getSize(navToolbar).height, 43.0); + + await tester.pump(Duration.zero); }, ); @@ -116,6 +120,8 @@ void main() { expect(tester.getTopLeft(toolbar).dy, 0.0); expect(tester.getTopLeft(navToolbar).dy, 4.0); + + await tester.pump(Duration.zero); }, ); @@ -137,6 +143,8 @@ void main() { expect(toolbar, findsNothing); expect(navToolbar, findsNothing); + + await tester.pump(Duration.zero); }, ); @@ -177,6 +185,8 @@ void main() { expect(tester.getTopLeft(toolbar).dy, 0.0); expect(navToolbar, findsOneWidget); expect(tester.getTopLeft(navToolbar).dy, 4.0); + + await tester.pump(Duration.zero); }, ); } diff --git a/test/layout/window_test.dart b/test/layout/window_test.dart index 262b5ded..92f1a80a 100644 --- a/test/layout/window_test.dart +++ b/test/layout/window_test.dart @@ -30,6 +30,7 @@ void main() { viewBuilder(Sidebar sidebar) { return MacosApp( home: MacosWindow( + disableWallpaperTinting: true, sidebar: sidebar, child: const MacosScaffold( children: [], @@ -70,6 +71,8 @@ void main() { await tester.pumpWidget(view); expectSidebarOpen(tester, width: startWidth); + + await tester.pump(Duration.zero); }); test('dragClosedBuffer defaults to half minWidth', () { @@ -84,6 +87,8 @@ void main() { await tester.pump(); expectSidebarOpen(tester, width: startWidth + safeDelta); + + await tester.pump(Duration.zero); }); testWidgets('dragging wider respects maxWidth', (tester) async { @@ -93,6 +98,8 @@ void main() { await tester.pump(); expectSidebarOpen(tester, width: maxWidth); + + await tester.pump(Duration.zero); }); testWidgets('drag events past maxWidth have no effect', (tester) async { @@ -106,6 +113,8 @@ void main() { await tester.pump(); expectSidebarOpen(tester, width: maxWidth); + + await tester.pump(Duration.zero); }); testWidgets('dragging narrower works', (tester) async { @@ -115,6 +124,8 @@ void main() { await tester.pump(); expectSidebarOpen(tester, width: startWidth - safeDelta); + + await tester.pump(Duration.zero); }); group('when dragClosed is true', () { @@ -135,6 +146,8 @@ void main() { await tester.pump(); expectSidebarOpen(tester, width: minWidth); + + await tester.pump(Duration.zero); }, ); testWidgets( @@ -146,6 +159,8 @@ void main() { await tester.pump(); expectSidebarClosed(tester); + + await tester.pump(Duration.zero); }, ); @@ -172,6 +187,8 @@ void main() { await tester.pump(); expectSidebarOpen(tester, width: startWidth - safeDelta); + + await tester.pump(Duration.zero); }, ); @@ -186,6 +203,8 @@ void main() { await tester.pump(); expectSidebarClosed(tester); + + await tester.pump(Duration.zero); }); }); @@ -199,6 +218,8 @@ void main() { await tester.pump(); expectSidebarOpen(tester, width: minWidth); + + await tester.pump(Duration.zero); }, ); @@ -213,6 +234,8 @@ void main() { await tester.pump(); expectSidebarOpen(tester, width: minWidth); + + await tester.pump(Duration.zero); }); }); @@ -243,6 +266,8 @@ void main() { tester, width: startWidth + snapToStartBuffer * 2, ); + + await tester.pump(Duration.zero); }, ); @@ -273,6 +298,8 @@ void main() { ); await tester.pump(); expectSidebarOpen(tester, width: startWidth); + + await tester.pump(Duration.zero); }, ); }); diff --git a/test/selectors/date_picker_test.dart b/test/selectors/date_picker_test.dart index f0c5afbc..f0996546 100644 --- a/test/selectors/date_picker_test.dart +++ b/test/selectors/date_picker_test.dart @@ -12,6 +12,7 @@ void main() { await tester.pumpWidget( MacosApp( home: MacosWindow( + disableWallpaperTinting: true, child: MacosScaffold( children: [ ContentArea( @@ -50,6 +51,7 @@ void main() { await tester.pumpWidget( MacosApp( home: MacosWindow( + disableWallpaperTinting: true, child: MacosScaffold( children: [ ContentArea( @@ -80,6 +82,61 @@ void main() { }, ); + testWidgets( + 'Textual MacosDatePicker renders the date with respect to "dateFormat" property', + (tester) async { + renderWidget(String dateFormat) => MacosApp( + home: MacosWindow( + disableWallpaperTinting: true, + child: MacosScaffold( + children: [ + ContentArea( + builder: (context, _) { + return Center( + child: MacosDatePicker( + initialDate: DateTime.parse('2023-04-01'), + onDateChanged: (date) {}, + dateFormat: dateFormat, + style: DatePickerStyle.textual, + ), + ); + }, + ), + ], + ), + ), + ); + + getNthTextFromWidget(int index) => + (find.byType(Text).at(index).evaluate().first.widget as Text).data + as String; + + await tester.pumpWidget(renderWidget('dd.mm.yyyy')); + String firstDateElement = getNthTextFromWidget(0); + expect(firstDateElement, '01'); + String secondDateElement = getNthTextFromWidget(1); + expect(secondDateElement, '.'); + String thirdDateElement = getNthTextFromWidget(2); + expect(thirdDateElement, '04'); + String fourthDateElement = getNthTextFromWidget(3); + expect(fourthDateElement, '.'); + String fifthDateElement = getNthTextFromWidget(4); + expect(fifthDateElement, '2023'); + + await tester.pumpWidget(renderWidget('yyyy-m-d')); + firstDateElement = getNthTextFromWidget(0); + expect(firstDateElement, '2023'); + secondDateElement = getNthTextFromWidget(1); + expect(secondDateElement, '-'); + thirdDateElement = getNthTextFromWidget(2); + expect(thirdDateElement, '4'); + fourthDateElement = getNthTextFromWidget(3); + expect(fourthDateElement, '-'); + fifthDateElement = getNthTextFromWidget(4); + expect(fifthDateElement, '1'); + }, + ); + testWidgets( 'Can select the date field element and change the value', (tester) async { @@ -305,5 +362,159 @@ void main() { } }, ); + + testWidgets( + 'Graphical MacosDatePicker renders abbreviations based on "weekdayAbbreviations" and "monthAbbreviations" properties', + (tester) async { + await tester.pumpWidget( + MacosApp( + home: MacosWindow( + child: MacosScaffold( + children: [ + ContentArea( + builder: (context, _) { + return Center( + child: MacosDatePicker( + initialDate: DateTime.parse('2023-04-01'), + onDateChanged: (date) {}, + weekdayAbbreviations: const [ + 'Nd', + 'Po', + 'Wt', + 'Śr', + 'Cz', + 'Pt', + 'So', + ], + monthAbbreviations: const [ + 'Sty', + 'Lut', + 'Mar', + 'Kwi', + 'Maj', + 'Cze', + 'Lip', + 'Sie', + 'Wrz', + 'Paź', + 'Lis', + 'Gru', + ], + ), + ); + }, + ), + ], + ), + ), + ), + ); + + await tester.pumpAndSettle(); + + expect(find.text('Kwi 2023'), findsOneWidget); + expect(find.text('Nd'), findsOneWidget); + expect(find.text('Po'), findsOneWidget); + expect(find.text('Wt'), findsOneWidget); + expect(find.text('Śr'), findsOneWidget); + expect(find.text('Cz'), findsOneWidget); + expect(find.text('Pt'), findsOneWidget); + expect(find.text('So'), findsOneWidget); + }, + ); }); + + testWidgets( + 'Graphical MacosDatePicker with "startWeekOnMonday" set to true shows Monday as the first day of the week', + (tester) async { + await tester.pumpWidget( + MacosApp( + home: MacosWindow( + child: MacosScaffold( + children: [ + ContentArea( + builder: (context, _) { + return Center( + child: MacosDatePicker( + startWeekOnMonday: true, + initialDate: DateTime.parse('2023-04-01'), + onDateChanged: (date) {}, + ), + ); + }, + ), + ], + ), + ), + ), + ); + + final dayHeadersRow = find.byType(GridView).first; + final dayHeaders = find.descendant( + of: dayHeadersRow, + matching: find.byType(Text), + ); + final firstWeekday = dayHeaders.first; + final firstWeekdayText = + (firstWeekday.evaluate().first.widget as Text).data; + await tester.pumpAndSettle(); + + expect(firstWeekdayText, 'Mo'); + + final calendarGrid = find.byType(GridView).last; + final dayOffsetWidgets = find.descendant( + of: calendarGrid, + matching: find.byType(SizedBox), + ); + final dayOffset = dayOffsetWidgets.evaluate().length; + + expect(dayOffset, 5); + }, + ); + + // Regression test due to invalid "firstDayOfWeekIndex" implementation in MaterialLocalizations + // issue: https://github.com/flutter/flutter/issues/122274 + // TODO: remove this once the issue is fixed and test starts failing + testWidgets( + 'Graphical MacosDatePicker still needs "startWeekOnMonday" to show Monday as the first day of the week, even when the locale is set to something other than "en_US"', + (tester) async { + await tester.pumpWidget( + MacosApp( + supportedLocales: const [ + Locale('en', 'PL'), + ], + home: MacosWindow( + child: MacosScaffold( + children: [ + ContentArea( + builder: (context, _) { + return Center( + child: MacosDatePicker( + startWeekOnMonday: true, + initialDate: DateTime.parse('2023-04-01'), + onDateChanged: (date) {}, + ), + ); + }, + ), + ], + ), + ), + ), + ); + + final dayHeadersRow = find.byType(GridView).first; + final dayHeaders = find.descendant( + of: dayHeadersRow, + matching: find.byType(Text), + ); + final firstWeekday = dayHeaders.first; + final firstWeekdayText = + (firstWeekday.evaluate().first.widget as Text).data; + await tester.pumpAndSettle(); + + // The result will be 'Tu' if the fix is no longer needed and can be removed + expect(firstWeekdayText, 'Mo'); + }, + ); } diff --git a/test/theme/help_button_theme_test.dart b/test/theme/help_button_theme_test.dart index 702a85ed..1e127f80 100644 --- a/test/theme/help_button_theme_test.dart +++ b/test/theme/help_button_theme_test.dart @@ -45,8 +45,8 @@ void main() { expect( description, [ - 'color: Color(0xff0433ff)', - 'disabledColor: Color(0xff8e8e93)', + 'color: MacosColor(0xff0433ff)', + 'disabledColor: MacosColor(0xff8e8e93)', ], ); }); @@ -56,6 +56,7 @@ void main() { await tester.pumpWidget( MacosApp( home: MacosWindow( + disableWallpaperTinting: true, child: MacosScaffold( children: [ ContentArea( diff --git a/test/theme/icon_button_theme_test.dart b/test/theme/icon_button_theme_test.dart index af7a40e6..7d42593e 100644 --- a/test/theme/icon_button_theme_test.dart +++ b/test/theme/icon_button_theme_test.dart @@ -63,6 +63,7 @@ void main() { await tester.pumpWidget( MacosApp( home: MacosWindow( + disableWallpaperTinting: true, child: MacosScaffold( children: [ ContentArea( diff --git a/test/theme/icon_theme_test.dart b/test/theme/icon_theme_test.dart index 8e98f80a..79076b18 100644 --- a/test/theme/icon_theme_test.dart +++ b/test/theme/icon_theme_test.dart @@ -51,7 +51,7 @@ void main() { expect( description, [ - 'color: Color(0xffffffff)', + 'MacosColor: MacosColor(0xffffffff)', 'opacity: 0.0', 'size: 20.0', ], @@ -63,6 +63,7 @@ void main() { await tester.pumpWidget( MacosApp( home: MacosWindow( + disableWallpaperTinting: true, child: MacosScaffold( children: [ ContentArea( diff --git a/test/theme/popup_button_theme_test.dart b/test/theme/popup_button_theme_test.dart index daa039de..7cbe6b7a 100644 --- a/test/theme/popup_button_theme_test.dart +++ b/test/theme/popup_button_theme_test.dart @@ -54,8 +54,8 @@ void main() { expect( description, [ - 'highlightColor: Color(0xff8e8e93)', - 'backgroundColor: Color(0xff0433ff)', + 'highlightColor: MacosColor(0xff8e8e93)', + 'backgroundColor: MacosColor(0xff0433ff)', 'popupColor: Color(0x19000000)', ], ); @@ -67,6 +67,7 @@ void main() { await tester.pumpWidget( MacosApp( home: MacosWindow( + disableWallpaperTinting: true, child: MacosScaffold( children: [ ContentArea( diff --git a/test/theme/pulldown_button_theme_test.dart b/test/theme/pulldown_button_theme_test.dart index 373ac0c4..87fc9666 100644 --- a/test/theme/pulldown_button_theme_test.dart +++ b/test/theme/pulldown_button_theme_test.dart @@ -53,10 +53,10 @@ void main() { expect( description, [ - 'highlightColor: Color(0xff8e8e93)', - 'backgroundColor: Color(0xff0433ff)', + 'highlightColor: MacosColor(0xff8e8e93)', + 'backgroundColor: MacosColor(0xff0433ff)', 'pulldownColor: Color(0x19000000)', - 'iconColor: Color(0xff00f900)', + 'iconColor: MacosColor(0xff00f900)', ], ); }); @@ -66,6 +66,7 @@ void main() { await tester.pumpWidget( MacosApp( home: MacosWindow( + disableWallpaperTinting: true, child: MacosScaffold( children: [ ContentArea( diff --git a/test/theme/push_button_theme_test.dart b/test/theme/push_button_theme_test.dart index 5bcc4ef1..2ff90243 100644 --- a/test/theme/push_button_theme_test.dart +++ b/test/theme/push_button_theme_test.dart @@ -1,7 +1,7 @@ import 'package:flutter/foundation.dart'; -import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:macos_ui/macos_ui.dart'; +import 'package:macos_ui/src/library.dart'; void main() { group('PushButton theme tests', () { @@ -46,8 +46,8 @@ void main() { expect( description, [ - 'color: Color(0xff0433ff)', - 'disabledColor: Color(0xff8e8e93)', + 'color: MacosColor(0xff0433ff)', + 'disabledColor: MacosColor(0xff8e8e93)', 'secondaryColor: Color(0x19000000)', ], ); @@ -58,13 +58,14 @@ void main() { await tester.pumpWidget( MacosApp( home: MacosWindow( + disableWallpaperTinting: true, child: MacosScaffold( children: [ ContentArea( builder: (context, _) { capturedContext = context; return const PushButton( - buttonSize: ButtonSize.small, + controlSize: ControlSize.regular, child: Text('Push me'), ); }, @@ -77,8 +78,8 @@ void main() { final theme = PushButtonTheme.of(capturedContext); expect(theme.color, const Color(0xff007aff)); - expect(theme.disabledColor, const Color(0xfff4f5f5)); - expect(theme.secondaryColor, const Color(0xffdadadf)); + expect(theme.disabledColor, const Color.fromRGBO(244, 245, 245, 1.0)); + expect(theme.secondaryColor, MacosColors.white); }); }); } diff --git a/test/theme/search_field_theme_test.dart b/test/theme/search_field_theme_test.dart index da838f37..6f0851b9 100644 --- a/test/theme/search_field_theme_test.dart +++ b/test/theme/search_field_theme_test.dart @@ -62,6 +62,7 @@ void main() { await tester.pumpWidget( MacosApp( home: MacosWindow( + disableWallpaperTinting: true, child: MacosScaffold( children: [ ContentArea(