From 4a47f617cb686ad16932548ebcbf116263b32eb5 Mon Sep 17 00:00:00 2001 From: GroovinChip Date: Mon, 6 Feb 2023 14:48:27 -0500 Subject: [PATCH 001/151] chore: account for latest Flutter:master --- example/lib/pages/fields_page.dart | 12 +++++----- example/lib/pages/toolbar_page.dart | 6 ++--- example/pubspec.lock | 26 +++++++++++----------- lib/src/fields/text_field.dart | 4 ++-- lib/src/layout/scaffold.dart | 2 +- pubspec.lock | 34 ++++++++++++++--------------- 6 files changed, 42 insertions(+), 42 deletions(-) diff --git a/example/lib/pages/fields_page.dart b/example/lib/pages/fields_page.dart index e7865f98..291437cf 100644 --- a/example/lib/pages/fields_page.dart +++ b/example/lib/pages/fields_page.dart @@ -352,8 +352,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 +365,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 +378,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/toolbar_page.dart b/example/lib/pages/toolbar_page.dart index 4e77fc86..f6188ca3 100644 --- a/example/lib/pages/toolbar_page.dart +++ b/example/lib/pages/toolbar_page.dart @@ -157,11 +157,11 @@ class _ToolbarPageState extends State { ), children: [ ContentArea(builder: (context) { - return SingleChildScrollView( - padding: const EdgeInsets.all(30), + return const SingleChildScrollView( + padding: EdgeInsets.all(30), child: Center( child: Column( - children: const [ + children: [ Text( "The toolbar appears below the title bar of the macOS app or integrates with it.", textAlign: TextAlign.center, diff --git a/example/pubspec.lock b/example/pubspec.lock index 45cafbfa..f6155c18 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -37,10 +37,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" cupertino_icons: dependency: "direct main" description: @@ -79,10 +79,10 @@ packages: 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: @@ -102,10 +102,10 @@ packages: dependency: transitive description: name: matcher - sha256: "16db949ceee371e9b99d22f88fa3a73c4e59fd0afed0bd25fc336eb76c198b72" + sha256: c94db23593b89766cda57aab9ac311e3616cf87c6fa4e9749df032f66f30dcb8 url: "https://pub.dev" source: hosted - version: "0.12.13" + version: "0.12.14" material_color_utilities: dependency: transitive description: @@ -118,10 +118,10 @@ packages: dependency: transitive description: name: meta - sha256: "6c268b42ed578a53088d834796959e4a1814b5e9e164f147f580a386e5decf42" + sha256: "12307e7f0605ce3da64cf0db90e5fcab0869f3ca03f76be6bb2991ce0a55e82b" url: "https://pub.dev" source: hosted - version: "1.8.0" + version: "1.9.0" nested: dependency: transitive description: @@ -134,10 +134,10 @@ packages: dependency: transitive description: name: path - sha256: db9d4f58c908a4ba5953fcee2ae317c94889433e5024c27ce74a37f94267945b + sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" url: "https://pub.dev" source: hosted - version: "1.8.2" + version: "1.8.3" provider: dependency: "direct main" description: @@ -195,10 +195,10 @@ packages: dependency: transitive description: name: test_api - sha256: ad540f65f92caa91bf21dfc8ffb8c589d6e4dc0c2267818b4cc2792857706206 + sha256: "6182294da5abf431177fccc1ee02401f6df30f766bc6130a0852c6b6d7ee6b2d" url: "https://pub.dev" source: hosted - version: "0.4.16" + version: "0.4.18" vector_math: dependency: transitive description: @@ -208,5 +208,5 @@ packages: source: hosted version: "2.1.4" sdks: - dart: ">=2.18.0 <3.0.0" + dart: ">=2.19.0 <4.0.0" flutter: ">=1.20.0" diff --git a/lib/src/fields/text_field.dart b/lib/src/fields/text_field.dart index 99f390cc..720786dc 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, @@ -127,7 +127,7 @@ class _TextFieldSelectionGestureDetectorBuilder } @override - void onDragSelectionEnd(DragEndDetails details) { + void onDragSelectionEnd(TapDragEndDetails details) { _state._requestKeyboard(); } } diff --git a/lib/src/layout/scaffold.dart b/lib/src/layout/scaffold.dart index 712ab1a7..16aa71e1 100644 --- a/lib/src/layout/scaffold.dart +++ b/lib/src/layout/scaffold.dart @@ -115,7 +115,7 @@ class _MacosScaffoldState extends State { } class _ScaffoldBody extends MultiChildRenderObjectWidget { - _ScaffoldBody({ + const _ScaffoldBody({ super.children, }); diff --git a/pubspec.lock b/pubspec.lock index eb16ba9a..9a886f15 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -77,10 +77,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: @@ -223,10 +223,10 @@ packages: dependency: transitive description: name: js - sha256: "5528c2f391ededb7775ec1daa69e65a2d61276f7552de2b5f7b8d34ee9fd4ab7" + sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 url: "https://pub.dev" source: hosted - version: "0.6.5" + version: "0.6.7" json_annotation: dependency: transitive description: @@ -255,10 +255,10 @@ packages: dependency: transitive description: name: matcher - sha256: "16db949ceee371e9b99d22f88fa3a73c4e59fd0afed0bd25fc336eb76c198b72" + sha256: c94db23593b89766cda57aab9ac311e3616cf87c6fa4e9749df032f66f30dcb8 url: "https://pub.dev" source: hosted - version: "0.12.13" + version: "0.12.14" material_color_utilities: dependency: transitive description: @@ -271,10 +271,10 @@ packages: dependency: transitive description: name: meta - sha256: "6c268b42ed578a53088d834796959e4a1814b5e9e164f147f580a386e5decf42" + sha256: "12307e7f0605ce3da64cf0db90e5fcab0869f3ca03f76be6bb2991ce0a55e82b" url: "https://pub.dev" source: hosted - version: "1.8.0" + version: "1.9.0" mime: dependency: transitive description: @@ -311,10 +311,10 @@ packages: dependency: transitive description: name: path - sha256: db9d4f58c908a4ba5953fcee2ae317c94889433e5024c27ce74a37f94267945b + sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" url: "https://pub.dev" source: hosted - version: "1.8.2" + version: "1.8.3" petitparser: dependency: transitive description: @@ -460,26 +460,26 @@ packages: dependency: transitive description: name: test - sha256: a5fcd2d25eeadbb6589e80198a47d6a464ba3e2049da473943b8af9797900c2d + sha256: b54d427664c00f2013ffb87797a698883c46aee9288e027a50b46eaee7486fa2 url: "https://pub.dev" source: hosted - version: "1.22.0" + version: "1.22.2" test_api: dependency: transitive description: name: test_api - sha256: ad540f65f92caa91bf21dfc8ffb8c589d6e4dc0c2267818b4cc2792857706206 + sha256: "6182294da5abf431177fccc1ee02401f6df30f766bc6130a0852c6b6d7ee6b2d" url: "https://pub.dev" source: hosted - version: "0.4.16" + version: "0.4.18" test_core: dependency: transitive description: name: test_core - sha256: "0ef9755ec6d746951ba0aabe62f874b707690b5ede0fecc818b138fcc9b14888" + sha256: "95ecc12692d0dd59080ab2d38d9cf32c7e9844caba23ff6cd285690398ee8ef4" url: "https://pub.dev" source: hosted - version: "0.4.20" + version: "0.4.22" typed_data: dependency: transitive description: @@ -545,5 +545,5 @@ packages: source: hosted version: "3.1.1" sdks: - dart: ">=2.18.0 <3.0.0" + dart: ">=2.19.0 <4.0.0" flutter: ">=1.20.0" From 411d00d56f17f68beaefb0fc0e8c817b343c4a03 Mon Sep 17 00:00:00 2001 From: GroovinChip Date: Mon, 6 Feb 2023 14:48:50 -0500 Subject: [PATCH 002/151] fix(example): incorrect variable usage --- example/lib/pages/indicators_page.dart | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/example/lib/pages/indicators_page.dart b/example/lib/pages/indicators_page.dart index 58a6d391..7c118b96 100644 --- a/example/lib/pages/indicators_page.dart +++ b/example/lib/pages/indicators_page.dart @@ -39,14 +39,14 @@ class _IndicatorsPageState extends State { child: Column( children: [ CapacityIndicator( - value: sliderValue, - onChanged: (v) => setState(() => sliderValue = v), + value: capacitorValue, + onChanged: (v) => setState(() => capacitorValue = v), discrete: true, ), const SizedBox(height: 20), CapacityIndicator( - value: sliderValue, - onChanged: (v) => setState(() => sliderValue = v), + value: capacitorValue, + onChanged: (v) => setState(() => capacitorValue = v), ), const SizedBox(height: 20), MacosSlider( From 2d5a7e09b9d77ecaf74786564259fc4f7a1a95fa Mon Sep 17 00:00:00 2001 From: Reuben Turner Date: Tue, 21 Feb 2023 10:26:34 -0500 Subject: [PATCH 003/151] Version 1.11.1 (#366) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 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> --- .github/PULL_REQUEST_TEMPLATE.md | 4 +- .github/workflows/flutter_analysis.yml | 18 +- .github/workflows/pana_analysis.yml | 1 + .gitignore | 2 +- CHANGELOG.md | 10 + CONTRIBUTING.md | 8 +- README.md | 66 +- example/.gitignore | 1 + example/lib/pages/buttons_page.dart | 726 +++++++++++---------- example/lib/pages/fields_page.dart | 4 +- example/lib/pages/indicators_page.dart | 2 +- example/lib/pages/selectors_page.dart | 1 + example/lib/pages/tabview_page.dart | 2 +- example/lib/pages/toolbar_page.dart | 6 +- example/pubspec.lock | 2 +- lib/src/fields/search_field.dart | 66 +- lib/src/layout/resizable_pane.dart | 232 ++++--- lib/src/layout/toolbar/toolbar.dart | 20 +- pr_prelaunch_tasks.sh | 39 -- publish_tasks.sh | 16 - pubspec.yaml | 2 +- test/buttons/checkbox_test.dart | 2 +- test/buttons/help_button_test.dart | 2 +- test/buttons/icon_button_test.dart | 2 +- test/buttons/popup_button_test.dart | 2 +- test/buttons/pulldown_button_test.dart | 2 +- test/buttons/push_button_test.dart | 2 +- test/buttons/radio_button_test.dart | 2 +- test/buttons/segmented_control_test.dart | 2 +- test/buttons/switch_test.dart | 2 +- test/fields/search_field_test.dart | 12 +- test/fields/text_field_test.dart | 2 +- test/layout/macos_list_tile_test.dart | 2 +- test/layout/resizeable_pane_test.dart | 314 +++++---- test/layout/tab_view_test.dart | 2 +- test/selectors/date_picker_test.dart | 4 +- test/theme/help_button_theme_test.dart | 2 +- test/theme/icon_button_theme_test.dart | 2 +- test/theme/icon_theme_test.dart | 2 +- test/theme/popup_button_theme_test.dart | 2 +- test/theme/pulldown_button_theme_test.dart | 2 +- test/theme/push_button_theme_test.dart | 2 +- test/theme/search_field_theme_test.dart | 2 +- 43 files changed, 907 insertions(+), 689 deletions(-) delete mode 100644 pr_prelaunch_tasks.sh delete mode 100644 publish_tasks.sh diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index af69bfd4..bb71af66 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -7,9 +7,7 @@ ## Pre-launch Checklist -- [ ] I have run `dartfmt` on all changed files - [ ] I have incremented the package version as appropriate and updated `CHANGELOG.md` with my changes - [ ] I have added/updated relevant documentation - [ ] I have run "optimize/organize imports" on all changed files -- [ ] I have addressed all analyzer warnings as best I could - \ No newline at end of file +- [ ] I have addressed all analyzer warnings as best I could \ No newline at end of file diff --git a/.github/workflows/flutter_analysis.yml b/.github/workflows/flutter_analysis.yml index 29e896de..0187a10d 100644 --- a/.github/workflows/flutter_analysis.yml +++ b/.github/workflows/flutter_analysis.yml @@ -10,13 +10,27 @@ jobs: - name: Install Flutter uses: subosito/flutter-action@v2 with: - channel: stable + channel: master - name: Install dependencies run: flutter pub get + - uses: actions/checkout@v3 + - name: Set up git + run: | + git config user.name "GitHub Actions Bot" + git config user.email "<>" + - name: Format code - run: dart format --set-exit-if-changed . + run: | + dart format . + if [ $? -eq 1 ]; then + git add . + git commit -m "chore: formatting corrections" + git push + echo "Code has been formatted and changes have been committed and pushed." + fi + echo "All code is properly formatted!" - name: Analyze code run: flutter analyze --fatal-infos . diff --git a/.github/workflows/pana_analysis.yml b/.github/workflows/pana_analysis.yml index a32d6cb3..9f90d17e 100644 --- a/.github/workflows/pana_analysis.yml +++ b/.github/workflows/pana_analysis.yml @@ -4,6 +4,7 @@ on: [pull_request, workflow_dispatch] jobs: package-analysis: runs-on: ubuntu-latest + if: github.base_ref != 'customer_testing' steps: - uses: actions/checkout@v3 diff --git a/.gitignore b/.gitignore index 70efb322..fcfa202c 100644 --- a/.gitignore +++ b/.gitignore @@ -15,7 +15,7 @@ *.iws .idea/ -.vscode/launch.json +.vscode # The .vscode folder contains launch configuration and tasks you configure in # VS Code which you may wish to be included in version control, so this line diff --git a/CHANGELOG.md b/CHANGELOG.md index 994154c1..15c38001 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,13 @@ +## [1.11.1] +* Fixed an issue where the `MacosSearchField` would not perform an action when an item was selected. + +## [1.11.0] +* 🚨 Breaking Changes 🚨 +* `ResizablePane` can now be vertically resized + * `ResizablePane.startWidth` has been changed to `ResizablePane.startSize` + * `ResizablePane.minWidth` has been changed to `ResizablePane.minSize` + * `ResizablePane.maxWidth` has been changed to `ResizablePane.maxSize` + ## [1.10.0] 🚨 Breaking Changes 🚨 * `MacosScrollbar` has been completely overhauled and now resembles the native macOS scrollbar in appearance and diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7b6b77d0..bed82a0e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -18,16 +18,12 @@ This repository uses [conventional commits](https://www.conventionalcommits.org/ As mentioned above, all pull requests should target `dev`. #### Pre-launch script -Before opening your pull request, run the `pr_prelaunch_tasks.sh` script to ensure that your changes meet the -following requirements: +Before opening your pull request, please ensure that the following +following requirements are met: * All code is properly formatted * There are no Dart analysis warnings * All tests pass -If the format step of the script results in changes, the script will make those change, commit them, and prompt you to push the commit. - -If the `dart fix` step results in changes, the script will make those changes, commit them, and prompt you to push the commit. - Pull requests should **always** be merged via GitHub and not via command-line. ### Versioning diff --git a/README.md b/README.md index 663d9d65..1a1a1451 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,10 @@ Guides, codelabs, and other documentation can be found at https://macosui.dev -## 🚨 Usage notes +## 🚨 Usage notes +### Flutter channel +`macos_ui` is developed against Flutter's `stable` channel. To ensure a smooth development experience with `macos_ui`, you should build your application on Flutter's `stable` channel. + ### Platform Compatibility pub.dev shows that `macos_ui` only supports macOS. This is because `macos_ui` calls some native code, and therefore @@ -50,6 +53,7 @@ should avoid allowing your application window to be resized below the height of - [Layout](#layout) - [MacosWindow](#macoswindow) + - [Sidebar](#sidebar) - [MacosScaffold](#macosscaffold) - [Modern Window Look](#modern-window-look) - [ToolBar](#toolbar) @@ -148,6 +152,56 @@ your `MacosScaffold` in a `Builder` widget in order for this to work properly. +## Sidebar +A sidebar enables app navigation and provides quick access to top-level collections of content in your app. + +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: + +```dart +int pageIndex = 0; + +... + +MacosWindow( + sidebar: Sidebar( + minWidth: 200, + builder: (context, scrollController) { + return SidebarItems( + currentIndex: pageIndex, + scrollController: scrollController, + itemSize: SidebarItemSize.large, + onChanged: (i) { + setState(() => pageIndex = i); + }, + items: const [ + SidebarItem( + label: Text('Page One'), + ), + SidebarItem( + label: Text('Page Two'), + ), + ], + ); + }, + ), + endSidebar: Sidebar( + startWidth: 200, + minWidth: 200, + maxWidth: 300, + shownByDefault: false, + builder: (context, _) { + return const Center( + child: Text('End Sidebar'), + ); + }, + ), +), +``` + ## MacosScaffold The `MacosScaffold` is what you might call a "page". @@ -355,7 +409,7 @@ CustomToolbarItem( ## MacosListTile -A widget that aims to approximate the [ListTile] widget found in +A widget that aims to approximate the [`ListTile`](https://api.flutter.dev/flutter/material/ListTile-class.html) widget found in Flutter's material library. ![MacosListTile](https://imgur.com/pQB99M2.png) @@ -862,10 +916,10 @@ You can set `discrete` to `true` to make it a discrete capacity indicator. A slider is a control that lets people select a value from a continuous or discrete range of values by moving the slider thumb. - Continuous | Discrete | -|--------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| ![Continuous Slider Example](https://i.imgur.com/dc4YjoX.png) | ![Discrete Slider Example](https://i.imgur.com/KckOTUf.png) | -| A horizontal slider where any value continuous value between a min and max can be selected | A horizontal slider where only discrete values between a min and max can be selected. Tick marks are often displayed to provide context. | + | Continuous | Discrete | + | ------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------- | + | ![Continuous Slider Example](https://i.imgur.com/dc4YjoX.png) | ![Discrete Slider Example](https://i.imgur.com/KckOTUf.png) | + | A horizontal slider where any value continuous value between a min and max can be selected | A horizontal slider where only discrete values between a min and max can be selected. Tick marks are often displayed to provide context. | Here's an example of how to create an interactive continuous slider: diff --git a/example/.gitignore b/example/.gitignore index 8b52fde8..4e8c3b2b 100644 --- a/example/.gitignore +++ b/example/.gitignore @@ -47,3 +47,4 @@ app.*.map.json /android/app/release /windows/ +linux/ diff --git a/example/lib/pages/buttons_page.dart b/example/lib/pages/buttons_page.dart index dafc5ea2..6533484b 100644 --- a/example/lib/pages/buttons_page.dart +++ b/example/lib/pages/buttons_page.dart @@ -60,8 +60,8 @@ class _ButtonsPageState extends State { ), children: [ ResizablePane( - minWidth: 180, - startWidth: 200, + minSize: 180, + startSize: 200, windowBreakpoint: 700, resizableSide: ResizableSide.right, builder: (_, __) { @@ -72,366 +72,394 @@ class _ButtonsPageState extends State { ), ContentArea( builder: (context, scrollController) { - return 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, + 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'), + ), + ], ), - 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('MacosDisclosureButton'), + const SizedBox(height: 8), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + MacosDisclosureButton( + isPressed: isDisclosureButtonPressed, + onPressed: () { + debugPrint('click'); + setState(() { + isDisclosureButtonPressed = + !isDisclosureButtonPressed; + }); + }), + ], ), - shape: BoxShape.circle, - //onPressed: () {}, - ), - const SizedBox(width: 8), - MacosIconButton( - icon: const MacosIcon( - CupertinoIcons.minus_square, + 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: () {}, + ), + ], ), - backgroundColor: Colors.transparent, - onPressed: () {}, - ), - ], - ), - const SizedBox(height: 20), - const Text('PushButton'), - const SizedBox(height: 8), - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - PushButton( - buttonSize: ButtonSize.large, - child: const Text('Large'), - onPressed: () { - MacosWindowScope.of(context).toggleSidebar(); - }, - ), - const SizedBox(width: 20), - PushButton( - buttonSize: ButtonSize.small, - child: const Text('Small'), - onPressed: () { - Navigator.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(height: 20), + const Text('PushButton'), + const SizedBox(height: 8), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + PushButton( + buttonSize: ButtonSize.large, + child: const Text('Large'), + onPressed: () { + MacosWindowScope.of(context).toggleSidebar(); + }, + ), + const SizedBox(width: 20), + PushButton( + buttonSize: ButtonSize.small, + child: const Text('Small'), + onPressed: () { + Navigator.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(); + }, + ), + ); }, ), - ); - }, - ), - ResizablePane( - minWidth: 180, - startWidth: 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: 20), - PushButton( - buttonSize: ButtonSize.large, - isSecondary: true, - child: const Text('Secondary'), - onPressed: () { - MacosWindowScope.of(context).toggleSidebar(); - }, - ), - ], - ), - const SizedBox(height: 20), - const Text('MacosSwitch'), - const SizedBox(height: 8), - MacosSwitch( - value: switchValue, - onChanged: (value) { - setState(() => switchValue = value); - }, - ), - const SizedBox(height: 20), - const Text('MacosPulldownButton'), - const SizedBox(height: 8), - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - MacosPulldownButton( - title: "PDF", - items: [ - MacosPulldownMenuItem( - title: const Text('Open in Preview'), - onTap: () => debugPrint("Opening in preview..."), - ), - MacosPulldownMenuItem( - title: const Text('Save as PDF...'), - onTap: () => debugPrint("Saving as PDF..."), - ), - MacosPulldownMenuItem( - enabled: false, - title: const Text('Save as Postscript'), - onTap: () => debugPrint("Saving as Postscript..."), - ), - const MacosPulldownMenuDivider(), - MacosPulldownMenuItem( - enabled: false, - title: const Text('Save to iCloud Drive'), - onTap: () => debugPrint("Saving to iCloud..."), - ), - MacosPulldownMenuItem( - enabled: false, - title: const Text('Save to Web Receipts'), - onTap: () => - debugPrint("Saving to Web Receipts..."), - ), - MacosPulldownMenuItem( - title: const Text('Send in Mail...'), - onTap: () => debugPrint("Sending via Mail..."), - ), - const MacosPulldownMenuDivider(), - MacosPulldownMenuItem( - title: const Text('Edit Menu...'), - onTap: () => debugPrint("Editing menu..."), - ), - ], - ), - const SizedBox(width: 20), - const MacosPulldownButton( - title: "PDF", - disabledTitle: "Disabled", - items: [], - ), - ], - ), - const SizedBox(height: 8), - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - MacosPulldownButton( - icon: CupertinoIcons.ellipsis_circle, - items: [ - MacosPulldownMenuItem( - title: const Text('New Folder'), - onTap: () => debugPrint("Creating new folder..."), - ), - MacosPulldownMenuItem( - title: const Text('Open'), - onTap: () => debugPrint("Opening..."), - ), - MacosPulldownMenuItem( - title: const Text('Open with...'), - onTap: () => debugPrint("Opening with..."), - ), - MacosPulldownMenuItem( - title: const Text('Import from iPhone...'), - onTap: () => debugPrint("Importing..."), - ), - const MacosPulldownMenuDivider(), - MacosPulldownMenuItem( - enabled: false, - title: const Text('Remove'), - onTap: () => debugPrint("Deleting..."), - ), - MacosPulldownMenuItem( - title: const Text('Move to Bin'), - onTap: () => debugPrint("Moving to Bin..."), - ), - const MacosPulldownMenuDivider(), - MacosPulldownMenuItem( - title: const Text('Tags...'), - onTap: () => debugPrint("Tags..."), - ), - ], - ), - const SizedBox(width: 20), - const MacosPulldownButton( - icon: CupertinoIcons.square_grid_3x2, - items: [], - ), - ], - ), - const SizedBox(height: 20), - const Text('MacosPopupButton'), - const SizedBox(height: 8), - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - MacosPopupButton( - value: popupValue, - onChanged: (String? newValue) { - setState(() => popupValue = newValue!); - }, - items: ['One', 'Two', 'Three', 'Four'] - .map>((String value) { - return MacosPopupMenuItem( - value: value, - child: Text(value), - ); - }).toList(), - ), - const SizedBox(width: 20), - MacosPopupButton( - disabledHint: const Text("Disabled"), - onChanged: null, - items: null, - ), - ], - ), - const SizedBox(height: 20), - MacosPopupButton( - value: languagePopupValue, - onChanged: (String? newValue) { - setState(() => languagePopupValue = newValue!); - }, - items: languages - .map>((String value) { - return MacosPopupMenuItem( - value: value, - child: Text(value), - ); - }).toList(), - ), - const SizedBox(height: 20), - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Text('System Theme'), - const SizedBox(width: 8), - MacosRadioButton( - groupValue: context.watch().mode, - value: ThemeMode.system, - onChanged: (value) { - context.read().mode = value!; - }, - ), - ], - ), - const SizedBox(height: 8), - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Text('Light Theme'), - const SizedBox(width: 24), - MacosRadioButton( - groupValue: context.watch().mode, - value: ThemeMode.light, - onChanged: (value) { - context.read().mode = value!; - }, - ), - ], - ), - const SizedBox(height: 8), - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Text('Dark Theme'), - const SizedBox(width: 26), - MacosRadioButton( - groupValue: context.watch().mode, - value: ThemeMode.dark, - onChanged: (value) { - context.read().mode = value!; - }, - ), - ], - ), - const SizedBox(height: 20), - const Text('MacosSegmentedControl'), - const SizedBox(height: 8), - MacosSegmentedControl( - controller: _tabController, - tabs: [ - MacosTab( - label: 'Tab 1', - active: _tabController.index == 0, - ), - MacosTab( - label: 'Tab 2', - active: _tabController.index == 1, - ), - MacosTab( - label: 'Tab 3', - active: _tabController.index == 2, - ), - ], + const SizedBox(width: 20), + PushButton( + buttonSize: ButtonSize.large, + isSecondary: true, + child: const Text('Secondary'), + onPressed: () { + MacosWindowScope.of(context).toggleSidebar(); + }, + ), + ], + ), + const SizedBox(height: 20), + const Text('MacosSwitch'), + const SizedBox(height: 8), + MacosSwitch( + value: switchValue, + onChanged: (value) { + setState(() => switchValue = value); + }, + ), + const SizedBox(height: 20), + const Text('MacosPulldownButton'), + const SizedBox(height: 8), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + MacosPulldownButton( + title: "PDF", + items: [ + MacosPulldownMenuItem( + title: const Text('Open in Preview'), + onTap: () => + debugPrint("Opening in preview..."), + ), + MacosPulldownMenuItem( + title: const Text('Save as PDF...'), + onTap: () => debugPrint("Saving as PDF..."), + ), + MacosPulldownMenuItem( + enabled: false, + title: const Text('Save as Postscript'), + onTap: () => + debugPrint("Saving as Postscript..."), + ), + const MacosPulldownMenuDivider(), + MacosPulldownMenuItem( + enabled: false, + title: const Text('Save to iCloud Drive'), + onTap: () => + debugPrint("Saving to iCloud..."), + ), + MacosPulldownMenuItem( + enabled: false, + title: const Text('Save to Web Receipts'), + onTap: () => + debugPrint("Saving to Web Receipts..."), + ), + MacosPulldownMenuItem( + title: const Text('Send in Mail...'), + onTap: () => + debugPrint("Sending via Mail..."), + ), + const MacosPulldownMenuDivider(), + MacosPulldownMenuItem( + title: const Text('Edit Menu...'), + onTap: () => debugPrint("Editing menu..."), + ), + ], + ), + const SizedBox(width: 20), + const MacosPulldownButton( + title: "PDF", + disabledTitle: "Disabled", + items: [], + ), + ], + ), + const SizedBox(height: 8), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + MacosPulldownButton( + icon: CupertinoIcons.ellipsis_circle, + items: [ + MacosPulldownMenuItem( + title: const Text('New Folder'), + onTap: () => + debugPrint("Creating new folder..."), + ), + MacosPulldownMenuItem( + title: const Text('Open'), + onTap: () => debugPrint("Opening..."), + ), + MacosPulldownMenuItem( + title: const Text('Open with...'), + onTap: () => debugPrint("Opening with..."), + ), + MacosPulldownMenuItem( + title: const Text('Import from iPhone...'), + onTap: () => debugPrint("Importing..."), + ), + const MacosPulldownMenuDivider(), + MacosPulldownMenuItem( + enabled: false, + title: const Text('Remove'), + onTap: () => debugPrint("Deleting..."), + ), + MacosPulldownMenuItem( + title: const Text('Move to Bin'), + onTap: () => debugPrint("Moving to Bin..."), + ), + const MacosPulldownMenuDivider(), + MacosPulldownMenuItem( + title: const Text('Tags...'), + onTap: () => debugPrint("Tags..."), + ), + ], + ), + const SizedBox(width: 20), + const MacosPulldownButton( + icon: CupertinoIcons.square_grid_3x2, + items: [], + ), + ], + ), + const SizedBox(height: 20), + const Text('MacosPopupButton'), + const SizedBox(height: 8), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + MacosPopupButton( + value: popupValue, + onChanged: (String? newValue) { + setState(() => popupValue = newValue!); + }, + items: [ + 'One', + 'Two', + 'Three', + 'Four' + ].map>((String value) { + return MacosPopupMenuItem( + value: value, + child: Text(value), + ); + }).toList(), + ), + const SizedBox(width: 20), + MacosPopupButton( + disabledHint: const Text("Disabled"), + onChanged: null, + items: null, + ), + ], + ), + const SizedBox(height: 20), + MacosPopupButton( + value: languagePopupValue, + onChanged: (String? newValue) { + setState(() => languagePopupValue = newValue!); + }, + items: languages + .map>((String value) { + return MacosPopupMenuItem( + value: value, + child: Text(value), + ); + }).toList(), + ), + const SizedBox(height: 20), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Text('System Theme'), + const SizedBox(width: 8), + MacosRadioButton( + groupValue: context.watch().mode, + value: ThemeMode.system, + onChanged: (value) { + context.read().mode = value!; + }, + ), + ], + ), + const SizedBox(height: 8), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Text('Light Theme'), + const SizedBox(width: 24), + MacosRadioButton( + groupValue: context.watch().mode, + value: ThemeMode.light, + onChanged: (value) { + context.read().mode = value!; + }, + ), + ], + ), + const SizedBox(height: 8), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Text('Dark Theme'), + const SizedBox(width: 26), + MacosRadioButton( + groupValue: context.watch().mode, + value: ThemeMode.dark, + onChanged: (value) { + context.read().mode = value!; + }, + ), + ], + ), + const SizedBox(height: 20), + const Text('MacosSegmentedControl'), + const SizedBox(height: 8), + MacosSegmentedControl( + controller: _tabController, + tabs: [ + MacosTab( + label: 'Tab 1', + active: _tabController.index == 0, + ), + MacosTab( + label: 'Tab 2', + active: _tabController.index == 1, + ), + MacosTab( + label: 'Tab 3', + active: _tabController.index == 2, + ), + ], + ), + ], + ), ), - ], - ), + ), + ResizablePane( + minSize: 50, + startSize: 200, + //windowBreakpoint: 600, + builder: (_, __) { + return const Center( + child: Text('Resizable Pane'), + ); + }, + resizableSide: ResizableSide.top, + ) + ], ); }, ), ResizablePane( - minWidth: 180, - startWidth: 200, + minSize: 180, + startSize: 200, windowBreakpoint: 800, resizableSide: ResizableSide.left, builder: (_, __) { diff --git a/example/lib/pages/fields_page.dart b/example/lib/pages/fields_page.dart index c80ad815..c63e9f09 100644 --- a/example/lib/pages/fields_page.dart +++ b/example/lib/pages/fields_page.dart @@ -126,8 +126,8 @@ class _FieldsPageState extends State { }, ), ResizablePane( - minWidth: 180, - startWidth: 200, + minSize: 180, + startSize: 200, windowBreakpoint: 800, resizableSide: ResizableSide.left, builder: (_, __) { diff --git a/example/lib/pages/indicators_page.dart b/example/lib/pages/indicators_page.dart index 5daeaaf9..1dd4caf5 100644 --- a/example/lib/pages/indicators_page.dart +++ b/example/lib/pages/indicators_page.dart @@ -40,7 +40,7 @@ class _IndicatorsPageState extends State { children: [ CapacityIndicator( value: capacitorValue, - onChanged: (v) => setState(() => sliderValue = v), + onChanged: (v) => setState(() => capacitorValue = v), splits: 20, discrete: true, ), diff --git a/example/lib/pages/selectors_page.dart b/example/lib/pages/selectors_page.dart index e44a1936..8256a33e 100644 --- a/example/lib/pages/selectors_page.dart +++ b/example/lib/pages/selectors_page.dart @@ -30,6 +30,7 @@ class _SelectorsPageState extends State { ContentArea( builder: (context, scrollController) { return SingleChildScrollView( + controller: scrollController, padding: const EdgeInsets.all(20), child: Column( children: [ diff --git a/example/lib/pages/tabview_page.dart b/example/lib/pages/tabview_page.dart index 786b11b0..64d4d8ff 100644 --- a/example/lib/pages/tabview_page.dart +++ b/example/lib/pages/tabview_page.dart @@ -22,7 +22,7 @@ class _TabViewPageState extends State { ), children: [ ContentArea( - builder: (context, scrollController) { + builder: (context, _) { return Padding( padding: const EdgeInsets.all(24.0), child: MacosTabView( diff --git a/example/lib/pages/toolbar_page.dart b/example/lib/pages/toolbar_page.dart index af01f229..7492869b 100644 --- a/example/lib/pages/toolbar_page.dart +++ b/example/lib/pages/toolbar_page.dart @@ -158,11 +158,11 @@ class _ToolbarPageState extends State { children: [ ContentArea( builder: (context, scrollController) { - return SingleChildScrollView( - padding: const EdgeInsets.all(30), + return const SingleChildScrollView( + padding: EdgeInsets.all(30), child: Center( child: Column( - children: const [ + children: [ Text( "The toolbar appears below the title bar of the macOS app or integrates with it.", textAlign: TextAlign.center, diff --git a/example/pubspec.lock b/example/pubspec.lock index f6155c18..9d37f274 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -97,7 +97,7 @@ packages: path: ".." relative: true source: path - version: "1.10.0" + version: "1.11.1" matcher: dependency: transitive description: diff --git a/lib/src/fields/search_field.dart b/lib/src/fields/search_field.dart index d7eddb63..2064ef9a 100644 --- a/lib/src/fields/search_field.dart +++ b/lib/src/fields/search_field.dart @@ -326,39 +326,39 @@ class _MacosSearchFieldState extends State> { } height += _kResultsOverlayMargin; - return MacosOverlayFilter( - borderRadius: _kBorderRadius, - color: MacosSearchFieldTheme.of(context).resultsBackgroundColor, - child: SizedBox( - height: height, - child: ListView.builder( - reverse: showOverlayAbove, - padding: const EdgeInsets.all(6.0), - itemCount: snapshot.data!.length, - itemBuilder: (context, index) { - var selectedItem = snapshot.data![index]!; - return _SearchResultItemButton( - resultHeight: widget.resultHeight, - onPressed: () { - searchController!.text = selectedItem.searchKey; - searchController!.selection = TextSelection.fromPosition( - TextPosition( - offset: searchController!.text.length, - ), - ); - selectedItem.onSelected?.call(); - // Hide the results - suggestionStream.sink.add(null); - if (widget.onResultSelected != null) { - widget.onResultSelected!(selectedItem); - } - }, - child: selectedItem.child ?? - Text( - selectedItem.searchKey, - ), - ); - }, + return TextFieldTapRegion( + child: MacosOverlayFilter( + borderRadius: _kBorderRadius, + color: MacosSearchFieldTheme.of(context).resultsBackgroundColor, + child: SizedBox( + height: height, + child: ListView.builder( + reverse: showOverlayAbove, + padding: const EdgeInsets.all(6.0), + itemCount: snapshot.data!.length, + itemBuilder: (context, index) { + var selectedItem = snapshot.data![index]!; + return _SearchResultItemButton( + resultHeight: widget.resultHeight, + onPressed: () { + searchController!.text = selectedItem.searchKey; + searchController!.selection = + TextSelection.fromPosition( + TextPosition( + offset: searchController!.text.length, + ), + ); + selectedItem.onSelected?.call(); + // Hide the results + suggestionStream.sink.add(null); + if (widget.onResultSelected != null) { + widget.onResultSelected!(selectedItem); + } + }, + child: selectedItem.child ?? Text(selectedItem.searchKey), + ); + }, + ), ), ), ); diff --git a/lib/src/layout/resizable_pane.dart b/lib/src/layout/resizable_pane.dart index c71ff8c2..68ccfbed 100644 --- a/lib/src/layout/resizable_pane.dart +++ b/lib/src/layout/resizable_pane.dart @@ -15,16 +15,19 @@ enum ResizableSide { /// The right side of the [ResizablePane]. right, + + /// The top side of the [ResizablePane]. + top, } /// {@template resizablePane} -/// A widget that can be resized horizontally. +/// A widget that can be resized horizontally or vertically. /// -/// The [builder], [minWidth] and [resizableSide] can not be null. -/// The [maxWidth] and the [windowBreakpoint] default to `500.00`. +/// The [builder], [minSize] and [resizableSide] can not be null. +/// The [maxSize] and the [windowBreakpoint] default to `500.00`. /// [isResizable] defaults to `true`. /// -/// The [startWidth] is the initial width. +/// The [startSize] is the initial width or height depending on the orientation of the pane. /// {@endtemplate} class ResizablePane extends StatefulWidget { /// {@macro resizablePane} @@ -32,19 +35,19 @@ class ResizablePane extends StatefulWidget { super.key, required this.builder, this.decoration, - this.maxWidth = 500.0, - required this.minWidth, + this.maxSize = 500.0, + required this.minSize, this.isResizable = true, required this.resizableSide, this.windowBreakpoint, - required this.startWidth, + required this.startSize, }) : assert( - maxWidth >= minWidth, - 'minWidth should not be more than maxWidth.', + maxSize >= minSize, + 'minSize should not be more than maxSize.', ), assert( - (startWidth >= minWidth) && (startWidth <= maxWidth), - 'startWidth must not be less than minWidth or more than maxWidth', + (startSize >= minSize) && (startSize <= maxSize), + 'startSize must not be less than minSize or more than maxWidth', ); /// The builder that creates a child to display in this widget, which will @@ -61,19 +64,34 @@ class ResizablePane extends StatefulWidget { /// resizable side of this widget. final bool isResizable; - /// Specifies the maximum width that this [ResizablePane] can have. + /// Specifies the maximum width or height that this [ResizablePane] can have + /// according to its orientation. + /// + /// The orientation is horizontal if the [resizableSide] is + /// [ResizableSide.left] or [ResizableSide.right] and vertical if the + /// [resizableSide] is [ResizableSide.top]). /// - /// The value can be null and defaults to `500.0`. - final double maxWidth; + /// If this value is null, it defaults to `500.0`. + final double maxSize; - /// Specifies the minimum width that this [ResizablePane] can have. - final double minWidth; + /// Specifies the minimum width of height that this [ResizablePane] can have + /// according to its orientation. + /// + /// The orientation is horizontal if the [resizableSide] is + /// [ResizableSide.left] or [ResizableSide.right] and vertical if the + /// [resizableSide] is [ResizableSide.top]. + final double minSize; - /// Specifies the width that this [ResizablePane] first starts width. + /// Specifies the width or height that this [ResizablePane] first starts with + /// according to its orientation. + /// + /// The orientation is horizontal if the [resizableSide] is + /// [ResizableSide.left] or [ResizableSide.right] and vertical if the + /// [resizableSide] is [ResizableSide.top]). /// - /// The [startWidth] should not be more than the [maxWidth] or - /// less than the [minWidth]. - final double startWidth; + /// The [startSize] should not be more than the [maxSize] or + /// less than the [minSize]. + final double startSize; /// Indicates the draggable side of the [ResizablePane] for resizing final ResizableSide resizableSide; @@ -86,21 +104,26 @@ class ResizablePane extends StatefulWidget { } class _ResizablePaneState extends State { - SystemMouseCursor _cursor = SystemMouseCursors.resizeColumn; + late SystemMouseCursor _cursor; final _scrollController = ScrollController(); - late double _width; - late double _dragStartWidth; + late double _size; + late double _dragStartSize; late double _dragStartPosition; Color get _dividerColor => MacosTheme.of(context).dividerColor; bool get _resizeOnRight => widget.resizableSide == ResizableSide.right; + bool get _resizeOnTop => widget.resizableSide == ResizableSide.top; + BoxDecoration get _decoration { final borderSide = BorderSide(color: _dividerColor); final right = Border(right: borderSide); final left = Border(left: borderSide); - return BoxDecoration(border: _resizeOnRight ? right : left).copyWith( + final top = Border(top: borderSide); + return BoxDecoration( + border: _resizeOnTop ? top : (_resizeOnRight ? right : left), + ).copyWith( color: widget.decoration?.color, border: widget.decoration?.border, borderRadius: widget.decoration?.borderRadius, @@ -112,51 +135,99 @@ class _ResizablePaneState extends State { ); } + BoxConstraints get _boxConstraint { + if (_resizeOnTop) { + return BoxConstraints( + maxHeight: widget.maxSize, + minHeight: widget.minSize, + ).normalize(); + } + return BoxConstraints( + maxWidth: widget.maxSize, + minWidth: widget.minSize, + ).normalize(); + } + Widget get _resizeArea { - return GestureDetector( - behavior: HitTestBehavior.opaque, - child: MouseRegion( - cursor: _cursor, - child: const SizedBox(width: 5), - ), - onHorizontalDragStart: (details) { - _dragStartWidth = _width; - _dragStartPosition = details.globalPosition.dx; - }, - onHorizontalDragUpdate: (details) { - setState(() { - final newWidth = _resizeOnRight - ? _dragStartWidth - - (_dragStartPosition - details.globalPosition.dx) - : _dragStartWidth + - (_dragStartPosition - details.globalPosition.dx); - _width = math.max( - widget.minWidth, - math.min( - widget.maxWidth, - newWidth, + return _resizeOnTop + ? GestureDetector( + behavior: HitTestBehavior.opaque, + child: MouseRegion( + cursor: _cursor, + child: const SizedBox(width: 5), ), + onVerticalDragStart: (details) { + _dragStartSize = _size; + _dragStartPosition = details.globalPosition.dy; + }, + onVerticalDragUpdate: (details) { + setState(() { + final newHeight = _dragStartSize + + (_dragStartPosition - details.globalPosition.dy); + _size = math.max( + widget.minSize, + math.min( + widget.maxSize, + newHeight, + ), + ); + if (_size == widget.minSize) { + _cursor = SystemMouseCursors.resizeUp; + } else if (_size == widget.maxSize) { + _cursor = SystemMouseCursors.resizeDown; + } else { + _cursor = SystemMouseCursors.resizeRow; + } + }); + }, + ) + : GestureDetector( + behavior: HitTestBehavior.opaque, + child: MouseRegion( + cursor: _cursor, + child: const SizedBox(width: 5), + ), + onHorizontalDragStart: (details) { + _dragStartSize = _size; + _dragStartPosition = details.globalPosition.dx; + }, + onHorizontalDragUpdate: (details) { + setState(() { + final newWidth = _resizeOnRight + ? _dragStartSize - + (_dragStartPosition - details.globalPosition.dx) + : _dragStartSize + + (_dragStartPosition - details.globalPosition.dx); + _size = math.max( + widget.minSize, + math.min( + widget.maxSize, + newWidth, + ), + ); + if (_size == widget.minSize) { + _cursor = _resizeOnRight + ? SystemMouseCursors.resizeRight + : SystemMouseCursors.resizeLeft; + } else if (_size == widget.maxSize) { + _cursor = _resizeOnRight + ? SystemMouseCursors.resizeLeft + : SystemMouseCursors.resizeRight; + } else { + _cursor = SystemMouseCursors.resizeColumn; + } + }); + }, ); - if (_width == widget.minWidth) { - _cursor = _resizeOnRight - ? SystemMouseCursors.resizeRight - : SystemMouseCursors.resizeLeft; - } else if (_width == widget.maxWidth) { - _cursor = _resizeOnRight - ? SystemMouseCursors.resizeLeft - : SystemMouseCursors.resizeRight; - } else { - _cursor = SystemMouseCursors.resizeColumn; - } - }); - }, - ); } @override void initState() { super.initState(); - _width = widget.startWidth; + _cursor = _resizeOnTop + ? SystemMouseCursors.resizeRow + : SystemMouseCursors.resizeColumn; + _size = widget.startSize; _scrollController.addListener(() => setState(() {})); } @@ -164,12 +235,12 @@ class _ResizablePaneState extends State { void didUpdateWidget(covariant ResizablePane oldWidget) { super.didUpdateWidget(oldWidget); if (oldWidget.windowBreakpoint != widget.windowBreakpoint || - oldWidget.minWidth != widget.minWidth || - oldWidget.maxWidth != widget.maxWidth || + oldWidget.minSize != widget.minSize || + oldWidget.maxSize != widget.maxSize || oldWidget.resizableSide != widget.resizableSide) { setState(() { - if (widget.minWidth > _width) _width = widget.minWidth; - if (widget.maxWidth < _width) _width = widget.maxWidth; + if (widget.minSize > _size) _size = widget.minSize; + if (widget.maxSize < _size) _size = widget.maxSize; }); } } @@ -186,19 +257,23 @@ class _ResizablePaneState extends State { final maxHeight = media.size.height; final maxWidth = media.size.width; - if (widget.windowBreakpoint != null && - maxWidth <= widget.windowBreakpoint!) { - return const SizedBox.shrink(); + if (_resizeOnTop) { + if (widget.windowBreakpoint != null && + maxHeight <= widget.windowBreakpoint!) { + return const SizedBox.shrink(); + } + } else { + if (widget.windowBreakpoint != null && + maxWidth <= widget.windowBreakpoint!) { + return const SizedBox.shrink(); + } } return Container( - width: _width, - height: maxHeight, + width: _resizeOnTop ? maxWidth : _size, + height: _resizeOnTop ? _size : maxHeight, decoration: _decoration, - constraints: BoxConstraints( - maxWidth: widget.maxWidth, - minWidth: widget.minWidth, - ).normalize(), + constraints: _boxConstraint, child: Stack( children: [ SafeArea( @@ -209,7 +284,7 @@ class _ResizablePaneState extends State { child: widget.builder(context, _scrollController), ), ), - if (widget.isResizable && !_resizeOnRight) + if (widget.isResizable && !_resizeOnRight && !_resizeOnTop) Positioned( left: 0, width: 5, @@ -223,6 +298,13 @@ class _ResizablePaneState extends State { height: maxHeight, child: _resizeArea, ), + if (widget.isResizable && _resizeOnTop) + Positioned( + top: 0, + width: maxWidth, + height: 5, + child: _resizeArea, + ), ], ), ); diff --git a/lib/src/layout/toolbar/toolbar.dart b/lib/src/layout/toolbar/toolbar.dart index 01a3c12d..294a24a6 100644 --- a/lib/src/layout/toolbar/toolbar.dart +++ b/lib/src/layout/toolbar/toolbar.dart @@ -89,15 +89,17 @@ class ToolBar extends StatefulWidget { /// Typically the [leading] widget is a [MacosIcon] or a [MacosIconButton]. final Widget? leading; - /// Controls whether we should try to imply the leading widget if null. + /// Controls whether the toolbar should try to imply if the [leading] widget + /// is null. /// - /// If `true` and [leading] is null, automatically try to deduce what the leading - /// widget should be. If `false` and [leading] is null, leading space is given to [title]. - /// If leading widget is not null, this parameter has no effect. + /// If `true` and [leading] are null, the toolbar will automatically try to + /// deduce what the leading widget should be. If `false` and [leading] is + /// null, leading space is given to [title]. If the [leading] widget is not + /// null, this parameter has no effect. final bool automaticallyImplyLeading; - /// A list of [ToolbarItem] widgets to display in a row after the [title] widget, - /// as the toolbar actions. + /// A list of [ToolbarItem] widgets to display in a row after the [title] + /// widget, as the toolbar actions. /// /// Toolbar items include [ToolBarIconButton], [ToolBarPulldownButton], /// [ToolBarSpacer], and [CustomToolbarItem] widgets. @@ -108,14 +110,14 @@ class ToolBar extends StatefulWidget { /// at the right edge of the toolbar. final List? actions; - /// Whether the title should be centered. + /// Whether the [title] should be centered. final bool centerTitle; /// The color of the divider below the toolbar. /// - /// Defaults to MacosTheme.of(context).dividerColor. + /// Defaults to `MacosTheme.of(context).dividerColor`. /// - /// Set it to MacosColors.transparent to remove. + /// Set this to `MacosColors.transparent` to remove. final Color? dividerColor; @override diff --git a/pr_prelaunch_tasks.sh b/pr_prelaunch_tasks.sh deleted file mode 100644 index b7d2ff83..00000000 --- a/pr_prelaunch_tasks.sh +++ /dev/null @@ -1,39 +0,0 @@ -dart format --set-exit-if-changed . -if [ $? -eq 1 ]; then - dart format lib - git add . - git commit -m "chore: run flutter format ." - echo "push changes? [y/n]" - read -r pushResponse - if [ "$pushResponse" = "y" ]; then - git push origin - fi -fi -echo "Run dart fix --dry-run? [y/n]" -read -r dryRunResponse -if [ "$dryRunResponse" = "y" ]; then - dart fix --dry-run -fi -echo "Run dart fix --apply? [y/n]" -read -r applyResponse -if [ "$applyResponse" = "y" ]; then - dart fix --apply - if [ -z "$(git status --porcelain)" ]; then - echo "No changes to commit" - else - git add . - git commit -m "chore: run dart fix --apply" - echo "push changes? [y/n]" - read -r pushResponse - if [ "$pushResponse" = "y" ]; then - git push origin - fi - fi -fi -echo "Run tests? [y/n]" -read -r testResponse -if [ "$testResponse" = "y" ]; then - flutter test -else - exit 0 -fi \ No newline at end of file diff --git a/publish_tasks.sh b/publish_tasks.sh deleted file mode 100644 index a6aa33e1..00000000 --- a/publish_tasks.sh +++ /dev/null @@ -1,16 +0,0 @@ -# MAINTAINER ONLY SCRIPT. DO NOT RUN THIS SCRIPT UNLESS YOU ARE THE MAINTAINER. -pana --no-warning -echo "Are you ready to dry-run publish macos_ui? [y/n]" -read -r dryRunResponse -if [ "$dryRunResponse" = "y" ]; then - flutter pub publish --dry-run -else - exit 0 -fi -echo "Are you ready to publish macos_ui to pub.dev? [y/n]" -read -r publishResponse -if [ "$publishResponse" = "y" ]; then - flutter pub publish -else - exit 0 -fi \ No newline at end of file diff --git a/pubspec.yaml b/pubspec.yaml index 7b6b263b..d1760775 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.10.0 +version: 1.11.1 homepage: "https://macosui.dev" repository: "https://github.com/GroovinChip/macos_ui" diff --git a/test/buttons/checkbox_test.dart b/test/buttons/checkbox_test.dart index 9ba9c1a5..09881007 100644 --- a/test/buttons/checkbox_test.dart +++ b/test/buttons/checkbox_test.dart @@ -12,7 +12,7 @@ void main() { child: MacosScaffold( children: [ ContentArea( - builder: (context, scrollController) { + builder: (context, _) { return StatefulBuilder( builder: (context, setState) { return MacosCheckbox( diff --git a/test/buttons/help_button_test.dart b/test/buttons/help_button_test.dart index 6124e5c0..2f11f430 100644 --- a/test/buttons/help_button_test.dart +++ b/test/buttons/help_button_test.dart @@ -24,7 +24,7 @@ void main() { child: MacosScaffold( children: [ ContentArea( - builder: (context, scrollController) { + builder: (context, _) { return HelpButton( onPressed: mockOnPressedFunction.handler, ); diff --git a/test/buttons/icon_button_test.dart b/test/buttons/icon_button_test.dart index a4c1e543..057f6a3b 100644 --- a/test/buttons/icon_button_test.dart +++ b/test/buttons/icon_button_test.dart @@ -19,7 +19,7 @@ void main() { child: MacosScaffold( children: [ ContentArea( - builder: (context, scrollController) { + builder: (context, _) { return MacosIconButton( icon: const Icon(CupertinoIcons.add), onPressed: mockOnPressedFunction.handler, diff --git a/test/buttons/popup_button_test.dart b/test/buttons/popup_button_test.dart index d685d3da..704501ef 100644 --- a/test/buttons/popup_button_test.dart +++ b/test/buttons/popup_button_test.dart @@ -17,7 +17,7 @@ void main() { child: MacosScaffold( children: [ ContentArea( - builder: (context, scrollController) { + builder: (context, _) { return StatefulBuilder( builder: (context, setState) { return MacosPopupButton( diff --git a/test/buttons/pulldown_button_test.dart b/test/buttons/pulldown_button_test.dart index aa42cc83..e22a355e 100644 --- a/test/buttons/pulldown_button_test.dart +++ b/test/buttons/pulldown_button_test.dart @@ -22,7 +22,7 @@ void main() { child: MacosScaffold( children: [ ContentArea( - builder: (context, scrollController) { + builder: (context, _) { return Center( child: MacosPulldownButton( title: "test", diff --git a/test/buttons/push_button_test.dart b/test/buttons/push_button_test.dart index 99fe95ae..96dc571e 100644 --- a/test/buttons/push_button_test.dart +++ b/test/buttons/push_button_test.dart @@ -25,7 +25,7 @@ void main() { child: MacosScaffold( children: [ ContentArea( - builder: (context, scrollController) { + builder: (context, _) { return PushButton( buttonSize: ButtonSize.small, onPressed: mockOnPressedFunction.handler, diff --git a/test/buttons/radio_button_test.dart b/test/buttons/radio_button_test.dart index cb51f3b3..d561b643 100644 --- a/test/buttons/radio_button_test.dart +++ b/test/buttons/radio_button_test.dart @@ -20,7 +20,7 @@ void main() { child: MacosScaffold( children: [ ContentArea( - builder: (context, scrollController) { + builder: (context, _) { return Center( child: MacosRadioButton( value: TestOptions.first, diff --git a/test/buttons/segmented_control_test.dart b/test/buttons/segmented_control_test.dart index 1ccf4a74..62d19796 100644 --- a/test/buttons/segmented_control_test.dart +++ b/test/buttons/segmented_control_test.dart @@ -14,7 +14,7 @@ void main() { child: MacosScaffold( children: [ ContentArea( - builder: (context, scrollController) { + builder: (context, _) { return Center( child: MacosSegmentedControl( controller: controller, diff --git a/test/buttons/switch_test.dart b/test/buttons/switch_test.dart index 75c3e863..642f0f11 100644 --- a/test/buttons/switch_test.dart +++ b/test/buttons/switch_test.dart @@ -12,7 +12,7 @@ void main() { child: MacosScaffold( children: [ ContentArea( - builder: (context, scrollController) { + builder: (context, _) { return Center( child: MacosSwitch( value: selected, diff --git a/test/fields/search_field_test.dart b/test/fields/search_field_test.dart index a6f1a750..4fdbc863 100644 --- a/test/fields/search_field_test.dart +++ b/test/fields/search_field_test.dart @@ -29,10 +29,11 @@ void main() { child: MacosScaffold( children: [ ContentArea( - builder: (context, scrollController) { + builder: (context, _) { return Center( child: SizedBox( width: 300.0, + height: 500.0, child: MacosSearchField( results: kOptions.map((e) => SearchResultItem(e)).toList(), @@ -64,6 +65,15 @@ void main() { ListView list = find.byType(ListView).evaluate().first.widget as ListView; // 'chameleon' and 'elephant' are displayed. expect(list.semanticChildCount, 2); + + await tester.ensureVisible(find.text('elephant')); + await tester.pump(); + + await tester.tap(find.text('elephant')); + await tester.pump(); + + expect(controller.text, 'elephant'); + expect(find.byType(ListView), findsNothing); }, ); } diff --git a/test/fields/text_field_test.dart b/test/fields/text_field_test.dart index d83ce27b..71b12568 100644 --- a/test/fields/text_field_test.dart +++ b/test/fields/text_field_test.dart @@ -12,7 +12,7 @@ void main() { child: MacosScaffold( children: [ ContentArea( - builder: (context, scrollController) { + builder: (context, _) { return Center( child: MacosTextField( controller: controller, diff --git a/test/layout/macos_list_tile_test.dart b/test/layout/macos_list_tile_test.dart index 6f86b952..e019a007 100644 --- a/test/layout/macos_list_tile_test.dart +++ b/test/layout/macos_list_tile_test.dart @@ -23,7 +23,7 @@ void main() { child: MacosScaffold( children: [ ContentArea( - builder: (context, scrollController) { + builder: (context, _) { return MacosListTile( title: const Text('List Tile'), onClick: mockOnPressedFunction.handler, diff --git a/test/layout/resizeable_pane_test.dart b/test/layout/resizeable_pane_test.dart index bae5ac4d..86638a6a 100644 --- a/test/layout/resizeable_pane_test.dart +++ b/test/layout/resizeable_pane_test.dart @@ -7,150 +7,226 @@ void main() { group('ResizablePane', () { for (var side in matrix) { - group(side == ResizableSide.left ? "left" : "right", () { - const double maxWidth = 300; - const double minWidth = 100; - const double startWidth = 200; - - final resizablePane = ResizablePane( - builder: (context, scrollController) => const Text('Hello there'), - minWidth: minWidth, - startWidth: startWidth, - maxWidth: maxWidth, - resizableSide: side, - ); - - final view = MacosApp( - home: MacosWindow( - 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), - ); - - final directionModifier = side == ResizableSide.right ? 1 : -1; - final double safeDelta = 50.0 * directionModifier; - final double overflowDelta = 500.0 * directionModifier; - - testWidgets('initial width equals startWidth', (tester) async { - await tester.pumpWidget(view); - - var resizablePaneRenderObject = - tester.renderObject(resizablePaneFinder); - expect(resizablePaneRenderObject.size.width, startWidth); - }); - - testWidgets('dragging wider works', (tester) async { - await tester.pumpWidget(view); - - await tester.drag( - dragFinder, - Offset(safeDelta, 0), + bool verticallyResizable = side == ResizableSide.top; + + group( + side == ResizableSide.top + ? '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, + startSize: startSize, + maxSize: maxSize, + resizableSide: side, ); - await tester.pump(); - var resizablePaneRenderObject = - tester.renderObject(resizablePaneFinder); - expect( - resizablePaneRenderObject.size.width, - startWidth + safeDelta * directionModifier, + final view = side == ResizableSide.top + ? MacosApp( + home: MacosWindow( + 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( + 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), ); - }); - testWidgets('dragging wider respects maxWidth', (tester) async { - await tester.pumpWidget(view); + // 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; - await tester.drag( - dragFinder, - Offset(overflowDelta, 0), - ); - await tester.pump(); + 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; - var resizablePaneRenderObject = - tester.renderObject(resizablePaneFinder); - expect(resizablePaneRenderObject.size.width, maxWidth); - }); + expect(initialSize, startSize); + }); - testWidgets( - 'drag events past maxWidth have no effect', - (tester) async { + testWidgets('dragging wider works $side', (tester) async { await tester.pumpWidget(view); - final dragStartLocation = tester.getCenter(dragFinder); - final drag = await tester.startGesture(dragStartLocation); - await drag.moveBy(Offset(overflowDelta, 0)); - await drag.moveBy(Offset(-10.0 * directionModifier, 0)); - await drag.up(); + await tester.drag( + dragFinder, + verticallyResizable ? Offset(0, safeDelta) : Offset(safeDelta, 0), + ); await tester.pump(); var resizablePaneRenderObject = tester.renderObject(resizablePaneFinder); - expect(resizablePaneRenderObject.size.width, maxWidth); - }, - ); - - testWidgets('dragging narrower works', (tester) async { - await tester.pumpWidget(view); + 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, - Offset(-safeDelta, 0), - ); - await tester.pump(); + await tester.drag( + dragFinder, + verticallyResizable + ? Offset(0, overflowDelta) + : Offset(overflowDelta, 0), + ); + await tester.pump(); - var resizablePaneRenderObject = - tester.renderObject(resizablePaneFinder); - expect( - resizablePaneRenderObject.size.width, - startWidth - safeDelta * directionModifier, + 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 respects minWidth', (tester) async { - await tester.pumpWidget(view); - await tester.drag( - dragFinder, - Offset(-overflowDelta, 0), - ); - await tester.pump(); + testWidgets('dragging narrower works', (tester) async { + await tester.pumpWidget(view); - var resizablePaneRenderObject = - tester.renderObject(resizablePaneFinder); - expect(resizablePaneRenderObject.size.width, minWidth); - }); + await tester.drag( + dragFinder, + verticallyResizable + ? Offset(0, -safeDelta) + : Offset(-safeDelta, 0), + ); + await tester.pump(); - testWidgets( - 'drag events past minWidth have no effect', - (tester) async { + 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); - final dragStartLocation = tester.getCenter(dragFinder); - final drag = await tester.startGesture(dragStartLocation); - await drag.moveBy(Offset(-overflowDelta, 0)); - await drag.moveBy(Offset(10.0 * directionModifier, 0)); - await drag.up(); + await tester.drag( + dragFinder, + verticallyResizable + ? Offset(0, -overflowDelta) + : Offset(-overflowDelta, 0), + ); await tester.pump(); var resizablePaneRenderObject = tester.renderObject(resizablePaneFinder); - expect(resizablePaneRenderObject.size.width, minWidth); - }, - ); - }); + 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); + }, + ); + }, + ); } }); } diff --git a/test/layout/tab_view_test.dart b/test/layout/tab_view_test.dart index 3673f4d1..2fb09c07 100644 --- a/test/layout/tab_view_test.dart +++ b/test/layout/tab_view_test.dart @@ -14,7 +14,7 @@ void main() { child: MacosScaffold( children: [ ContentArea( - builder: (context, scrollController) { + builder: (context, _) { return Padding( padding: const EdgeInsets.all(24.0), child: MacosTabView( diff --git a/test/selectors/date_picker_test.dart b/test/selectors/date_picker_test.dart index e6d36763..f0c5afbc 100644 --- a/test/selectors/date_picker_test.dart +++ b/test/selectors/date_picker_test.dart @@ -15,7 +15,7 @@ void main() { child: MacosScaffold( children: [ ContentArea( - builder: (context, scrollController) { + builder: (context, _) { return Center( child: MacosDatePicker( onDateChanged: (date) {}, @@ -262,7 +262,7 @@ void main() { child: MacosScaffold( children: [ ContentArea( - builder: (context, scrollController) { + builder: (context, _) { return Center( child: MacosDatePicker( onDateChanged: (date) { diff --git a/test/theme/help_button_theme_test.dart b/test/theme/help_button_theme_test.dart index cd93ac74..702a85ed 100644 --- a/test/theme/help_button_theme_test.dart +++ b/test/theme/help_button_theme_test.dart @@ -59,7 +59,7 @@ void main() { child: MacosScaffold( children: [ ContentArea( - builder: (context, scrollController) { + builder: (context, _) { capturedContext = context; return const HelpButton(); }, diff --git a/test/theme/icon_button_theme_test.dart b/test/theme/icon_button_theme_test.dart index 92832505..af7a40e6 100644 --- a/test/theme/icon_button_theme_test.dart +++ b/test/theme/icon_button_theme_test.dart @@ -66,7 +66,7 @@ void main() { child: MacosScaffold( children: [ ContentArea( - builder: (context, scrollController) { + builder: (context, _) { capturedContext = context; return MacosIconButton( icon: const Icon(CupertinoIcons.add), diff --git a/test/theme/icon_theme_test.dart b/test/theme/icon_theme_test.dart index 23182817..8e98f80a 100644 --- a/test/theme/icon_theme_test.dart +++ b/test/theme/icon_theme_test.dart @@ -66,7 +66,7 @@ void main() { child: MacosScaffold( children: [ ContentArea( - builder: (context, scrollController) { + builder: (context, _) { capturedContext = context; return const MacosIcon( CupertinoIcons.add, diff --git a/test/theme/popup_button_theme_test.dart b/test/theme/popup_button_theme_test.dart index 710f2f49..daa039de 100644 --- a/test/theme/popup_button_theme_test.dart +++ b/test/theme/popup_button_theme_test.dart @@ -70,7 +70,7 @@ void main() { child: MacosScaffold( children: [ ContentArea( - builder: (context, scrollController) { + builder: (context, _) { capturedContext = context; return MacosPopupButton( value: popupValue, diff --git a/test/theme/pulldown_button_theme_test.dart b/test/theme/pulldown_button_theme_test.dart index 44264874..373ac0c4 100644 --- a/test/theme/pulldown_button_theme_test.dart +++ b/test/theme/pulldown_button_theme_test.dart @@ -69,7 +69,7 @@ void main() { child: MacosScaffold( children: [ ContentArea( - builder: (context, scrollController) { + builder: (context, _) { capturedContext = context; return const Center( child: MacosPulldownButton( diff --git a/test/theme/push_button_theme_test.dart b/test/theme/push_button_theme_test.dart index 8ced6d85..5bcc4ef1 100644 --- a/test/theme/push_button_theme_test.dart +++ b/test/theme/push_button_theme_test.dart @@ -61,7 +61,7 @@ void main() { child: MacosScaffold( children: [ ContentArea( - builder: (context, scrollController) { + builder: (context, _) { capturedContext = context; return const PushButton( buttonSize: ButtonSize.small, diff --git a/test/theme/search_field_theme_test.dart b/test/theme/search_field_theme_test.dart index 866f83cc..da838f37 100644 --- a/test/theme/search_field_theme_test.dart +++ b/test/theme/search_field_theme_test.dart @@ -65,7 +65,7 @@ void main() { child: MacosScaffold( children: [ ContentArea( - builder: (context, scrollController) { + builder: (context, _) { capturedContext = context; return const Center( child: MacosSearchField(), From 079b51fa313c520c0325e2f486b48f2df335c92f Mon Sep 17 00:00:00 2001 From: Minas Giannekas Date: Fri, 3 Mar 2023 16:54:03 +0100 Subject: [PATCH 004/151] 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 --- 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 fc6a26bb2c1ecfee71dc17a2dff7d3512e431412 Mon Sep 17 00:00:00 2001 From: Jonas Schlauch <72231111+jtdLab@users.noreply.github.com> Date: Mon, 20 Mar 2023 16:10:40 +0100 Subject: [PATCH 005/151] 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 --- CHANGELOG.md | 3 +++ example/pubspec.lock | 2 +- lib/src/macos_app.dart | 22 +++++++++++++++------- pubspec.yaml | 2 +- 4 files changed, 20 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 50742fa2..c79798a3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +## [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/example/pubspec.lock b/example/pubspec.lock index cf8e59b8..546efaa5 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -97,7 +97,7 @@ packages: path: ".." relative: true source: path - version: "1.12.2" + version: "1.12.3" matcher: dependency: transitive description: diff --git a/lib/src/macos_app.dart b/lib/src/macos_app.dart index c4068f30..a430ce1b 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, diff --git a/pubspec.yaml b/pubspec.yaml index 4d660503..7e1d73a1 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.2 +version: 1.12.3 homepage: "https://macosui.dev" repository: "https://github.com/GroovinChip/macos_ui" From fcf6ea1e72fa090cfc3a0bcfd836d81751c7ccec Mon Sep 17 00:00:00 2001 From: Reuben Turner Date: Tue, 21 Mar 2023 10:41:57 -0400 Subject: [PATCH 006/151] 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 --- .github/workflows/flutter_analysis.yml | 4 -- .github/workflows/test.yaml | 19 +++++++ analysis_options.yaml | 12 ++++- lib/src/buttons/back_button.dart | 2 +- lib/src/buttons/checkbox.dart | 4 +- lib/src/buttons/disclosure_button.dart | 2 +- lib/src/buttons/icon_button.dart | 2 +- lib/src/buttons/popup_button.dart | 7 ++- lib/src/buttons/pulldown_button.dart | 6 +-- lib/src/buttons/segmented_control.dart | 4 +- .../buttons/toolbar/toolbar_icon_button.dart | 2 +- lib/src/fields/text_field.dart | 3 +- lib/src/indicators/progress_indicators.dart | 4 +- lib/src/indicators/slider.dart | 17 ++++--- lib/src/layout/resizable_pane.dart | 6 +-- lib/src/layout/scrollbar.dart | 2 +- lib/src/layout/sidebar/sidebar_items.dart | 9 ++-- lib/src/layout/toolbar/sliver_toolbar.dart | 3 +- lib/src/layout/toolbar/toolbar_divider.dart | 10 +--- lib/src/layout/toolbar/toolbar_popup.dart | 5 +- lib/src/layout/window.dart | 50 ++++++++++--------- lib/src/macos_app.dart | 3 +- lib/src/selectors/color_well.dart | 2 +- lib/src/selectors/date_picker.dart | 33 ++++++------ lib/src/theme/macos_theme.dart | 4 +- lib/src/theme/tooltip_theme.dart | 2 +- lib/src/theme/typography.dart | 6 ++- test/buttons/pulldown_button_test.dart | 2 +- 28 files changed, 122 insertions(+), 103 deletions(-) create mode 100644 .github/workflows/test.yaml 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/analysis_options.yaml b/analysis_options.yaml index d4a906dd..74448811 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -14,13 +14,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/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..1ad46d62 100644 --- a/lib/src/buttons/checkbox.dart +++ b/lib/src/buttons/checkbox.dart @@ -106,7 +106,7 @@ 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, @@ -118,7 +118,7 @@ class MacosCheckbox extends StatelessWidget { context, ), ), - borderRadius: BorderRadius.circular(4.0), + borderRadius: const BorderRadius.all(Radius.circular(4.0)), ), child: Icon( isDisabled || value == false 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/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..51b783f4 100644 --- a/lib/src/buttons/pulldown_button.dart +++ b/lib/src/buttons/pulldown_button.dart @@ -808,7 +808,7 @@ class _MacosPulldownButtonState extends State void _handleTap() { final TextDirection? textDirection = Directionality.maybeOf(context); const EdgeInsetsGeometry menuMargin = - EdgeInsetsDirectional.only(start: 4.0, end: 4.0); + EdgeInsets.symmetric(horizontal: 4.0); final List<_MenuItem> menuItems = <_MenuItem>[ for (int index = 0; index < widget.items!.length; index += 1) @@ -904,7 +904,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 +913,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/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/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/fields/text_field.dart b/lib/src/fields/text_field.dart index 9387b029..f3cf0888 100644 --- a/lib/src/fields/text_field.dart +++ b/lib/src/fields/text_field.dart @@ -358,7 +358,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, @@ -1157,6 +1157,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/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..b3f6b338 100644 --- a/lib/src/layout/resizable_pane.dart +++ b/lib/src/layout/resizable_pane.dart @@ -238,10 +238,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; } } 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..a5a00dbf 100644 --- a/lib/src/layout/toolbar/sliver_toolbar.dart +++ b/lib/src/layout/toolbar/sliver_toolbar.dart @@ -275,7 +275,7 @@ class _SliverToolBarDelegate extends SliverPersistentHeaderDelegate { ); } - final Widget toolBar = FlexibleSpaceBar.createSettings( + return FlexibleSpaceBar.createSettings( minExtent: minExtent, maxExtent: maxExtent, currentExtent: math.max(minExtent, maxExtent - shrinkOffset), @@ -295,7 +295,6 @@ class _SliverToolBarDelegate extends SliverPersistentHeaderDelegate { height: height, ), ); - return toolBar; } @override 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/window.dart b/lib/src/layout/window.dart index 9ce38eb0..f20979b9 100644 --- a/lib/src/layout/window.dart +++ b/lib/src/layout/window.dart @@ -85,30 +85,28 @@ class _MacosWindowState extends State { @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!; - } + 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.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 (widget.sidebar!.maxWidth! < _sidebarWidth) { + _sidebarWidth = widget.sidebar!.maxWidth!; } - }); + } + 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!; + } + } } @override @@ -465,13 +463,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 a430ce1b..459918fb 100644 --- a/lib/src/macos_app.dart +++ b/lib/src/macos_app.dart @@ -409,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/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..f764394f 100644 --- a/lib/src/selectors/date_picker.dart +++ b/lib/src/selectors/date_picker.dart @@ -319,7 +319,8 @@ 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: [ @@ -410,7 +411,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( @@ -496,7 +497,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,7 +513,7 @@ class _MacosDatePickerState extends State { ); decoration = BoxDecoration( color: datePickerTheme.monthViewSelectedDateColor, - borderRadius: BorderRadius.circular(3.0), + borderRadius: const BorderRadius.all(Radius.circular(3.0)), ); } @@ -524,20 +525,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/macos_theme.dart b/lib/src/theme/macos_theme.dart index ac03ea2d..c8ea1f55 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. 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..d3efbeaf 100644 --- a/lib/src/theme/typography.dart +++ b/lib/src/theme/typography.dart @@ -140,8 +140,10 @@ class MacosTypography with Diagnosticable { required this.caption2, }); - static MacosTypography black = MacosTypography(color: CupertinoColors.black); - static MacosTypography white = MacosTypography(color: CupertinoColors.white); + static final MacosTypography black = + MacosTypography(color: CupertinoColors.black); + static final MacosTypography white = + MacosTypography(color: CupertinoColors.white); /// Style used for body text. final TextStyle body; 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( From aa82a08d1c573c439a8a1c8266df55ca7ee6d656 Mon Sep 17 00:00:00 2001 From: Erick Ghaumez Date: Sun, 26 Mar 2023 11:24:52 +0200 Subject: [PATCH 007/151] Fix the online gallery link --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b0925894..a88690b9 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 From a52b616f14b502de236c3ed0fb622e6abfb52b1c Mon Sep 17 00:00:00 2001 From: Elias Yishak <42216813+eliasyishak@users.noreply.github.com> Date: Fri, 31 Mar 2023 11:38:03 -0400 Subject: [PATCH 008/151] Fix for invalid dates (#402) --- CHANGELOG.md | 3 +++ lib/src/selectors/date_picker.dart | 12 ++++++++++-- pubspec.yaml | 2 +- 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c79798a3..55ffc59e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +## [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)) diff --git a/lib/src/selectors/date_picker.dart b/lib/src/selectors/date_picker.dart index f764394f..348fec85 100644 --- a/lib/src/selectors/date_picker.dart +++ b/lib/src/selectors/date_picker.dart @@ -354,9 +354,13 @@ class _MacosDatePickerState extends State { setState(() { _selectedYear--; _selectedMonth = 12; + _selectedDay = 1; }); } else { - setState(() => _selectedMonth--); + setState(() { + _selectedMonth--; + _selectedDay = 1; + }); } widget.onDateChanged.call(_formatAsDateTime()); }, @@ -397,9 +401,13 @@ class _MacosDatePickerState extends State { setState(() { _selectedYear++; _selectedMonth = 1; + _selectedDay = 1; }); } else { - setState(() => _selectedMonth++); + setState(() { + _selectedMonth++; + _selectedDay = 1; + }); } widget.onDateChanged.call(_formatAsDateTime()); diff --git a/pubspec.yaml b/pubspec.yaml index 7e1d73a1..40ee143e 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.3 +version: 1.12.4 homepage: "https://macosui.dev" repository: "https://github.com/GroovinChip/macos_ui" From 7de5419bfca8e91886fec1f12e66f05cd0e52098 Mon Sep 17 00:00:00 2001 From: GroovinChip Date: Thu, 6 Apr 2023 11:02:44 -0400 Subject: [PATCH 009/151] Revert "Merge branch 'customer_testing' into dev" This reverts commit ebf1d069238a468ba1f22d053ee0781239cf8db2, reversing changes made to a52b616f14b502de236c3ed0fb622e6abfb52b1c. --- .github/workflows/flutter_analysis.yml | 2 +- example/lib/pages/toolbar_page.dart | 2 +- example/pubspec.lock | 26 ++++++++++---------- lib/src/fields/text_field.dart | 4 +-- lib/src/layout/scaffold.dart | 2 +- pubspec.lock | 34 +++++++++++++------------- 6 files changed, 35 insertions(+), 35 deletions(-) diff --git a/.github/workflows/flutter_analysis.yml b/.github/workflows/flutter_analysis.yml index 4557a34d..fba04aa4 100644 --- a/.github/workflows/flutter_analysis.yml +++ b/.github/workflows/flutter_analysis.yml @@ -10,7 +10,7 @@ jobs: - name: Install Flutter uses: subosito/flutter-action@v2 with: - channel: master + channel: stable - name: Install dependencies run: flutter pub get diff --git a/example/lib/pages/toolbar_page.dart b/example/lib/pages/toolbar_page.dart index b026a364..6e1754ce 100644 --- a/example/lib/pages/toolbar_page.dart +++ b/example/lib/pages/toolbar_page.dart @@ -162,7 +162,7 @@ class _ToolbarPageState extends State { padding: const EdgeInsets.all(30), child: Center( child: Column( - children: [ + children: const [ 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/pubspec.lock b/example/pubspec.lock index eaf08376..546efaa5 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -37,10 +37,10 @@ packages: dependency: transitive description: name: collection - sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c" + sha256: cfc915e6923fe5ce6e153b0723c753045de46de1b4d63771530504004a45fae0 url: "https://pub.dev" source: hosted - version: "1.17.1" + version: "1.17.0" cupertino_icons: dependency: "direct main" description: @@ -79,10 +79,10 @@ packages: dependency: transitive description: name: js - sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 + sha256: "5528c2f391ededb7775ec1daa69e65a2d61276f7552de2b5f7b8d34ee9fd4ab7" url: "https://pub.dev" source: hosted - version: "0.6.7" + version: "0.6.5" lints: dependency: transitive description: @@ -102,10 +102,10 @@ packages: dependency: transitive description: name: matcher - sha256: c94db23593b89766cda57aab9ac311e3616cf87c6fa4e9749df032f66f30dcb8 + sha256: "16db949ceee371e9b99d22f88fa3a73c4e59fd0afed0bd25fc336eb76c198b72" url: "https://pub.dev" source: hosted - version: "0.12.14" + version: "0.12.13" material_color_utilities: dependency: transitive description: @@ -118,10 +118,10 @@ packages: dependency: transitive description: name: meta - sha256: "12307e7f0605ce3da64cf0db90e5fcab0869f3ca03f76be6bb2991ce0a55e82b" + sha256: "6c268b42ed578a53088d834796959e4a1814b5e9e164f147f580a386e5decf42" url: "https://pub.dev" source: hosted - version: "1.9.0" + version: "1.8.0" nested: dependency: transitive description: @@ -134,10 +134,10 @@ packages: dependency: transitive description: name: path - sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" + sha256: db9d4f58c908a4ba5953fcee2ae317c94889433e5024c27ce74a37f94267945b url: "https://pub.dev" source: hosted - version: "1.8.3" + version: "1.8.2" provider: dependency: "direct main" description: @@ -195,10 +195,10 @@ packages: dependency: transitive description: name: test_api - sha256: "6182294da5abf431177fccc1ee02401f6df30f766bc6130a0852c6b6d7ee6b2d" + sha256: ad540f65f92caa91bf21dfc8ffb8c589d6e4dc0c2267818b4cc2792857706206 url: "https://pub.dev" source: hosted - version: "0.4.18" + version: "0.4.16" vector_math: dependency: transitive description: @@ -208,5 +208,5 @@ packages: source: hosted version: "2.1.4" sdks: - dart: ">=2.19.0 <4.0.0" + dart: ">=2.18.0 <3.0.0" flutter: ">=1.20.0" diff --git a/lib/src/fields/text_field.dart b/lib/src/fields/text_field.dart index b1f1e1bb..f3cf0888 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(TapDragUpDetails details) { + void onSingleTapUp(TapUpDetails 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, @@ -127,7 +127,7 @@ class _TextFieldSelectionGestureDetectorBuilder } @override - void onDragSelectionEnd(TapDragEndDetails details) { + void onDragSelectionEnd(DragEndDetails details) { _state._requestKeyboard(); } } diff --git a/lib/src/layout/scaffold.dart b/lib/src/layout/scaffold.dart index 16aa71e1..712ab1a7 100644 --- a/lib/src/layout/scaffold.dart +++ b/lib/src/layout/scaffold.dart @@ -115,7 +115,7 @@ class _MacosScaffoldState extends State { } class _ScaffoldBody extends MultiChildRenderObjectWidget { - const _ScaffoldBody({ + _ScaffoldBody({ super.children, }); diff --git a/pubspec.lock b/pubspec.lock index 7a731dae..6cfa9f14 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -77,10 +77,10 @@ packages: dependency: transitive description: name: collection - sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c" + sha256: cfc915e6923fe5ce6e153b0723c753045de46de1b4d63771530504004a45fae0 url: "https://pub.dev" source: hosted - version: "1.17.1" + version: "1.17.0" convert: dependency: transitive description: @@ -231,10 +231,10 @@ packages: dependency: transitive description: name: js - sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 + sha256: "5528c2f391ededb7775ec1daa69e65a2d61276f7552de2b5f7b8d34ee9fd4ab7" url: "https://pub.dev" source: hosted - version: "0.6.7" + version: "0.6.5" json_annotation: dependency: transitive description: @@ -263,10 +263,10 @@ packages: dependency: transitive description: name: matcher - sha256: c94db23593b89766cda57aab9ac311e3616cf87c6fa4e9749df032f66f30dcb8 + sha256: "16db949ceee371e9b99d22f88fa3a73c4e59fd0afed0bd25fc336eb76c198b72" url: "https://pub.dev" source: hosted - version: "0.12.14" + version: "0.12.13" material_color_utilities: dependency: transitive description: @@ -279,10 +279,10 @@ packages: dependency: transitive description: name: meta - sha256: "12307e7f0605ce3da64cf0db90e5fcab0869f3ca03f76be6bb2991ce0a55e82b" + sha256: "6c268b42ed578a53088d834796959e4a1814b5e9e164f147f580a386e5decf42" url: "https://pub.dev" source: hosted - version: "1.9.0" + version: "1.8.0" mime: dependency: transitive description: @@ -319,10 +319,10 @@ packages: dependency: transitive description: name: path - sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" + sha256: db9d4f58c908a4ba5953fcee2ae317c94889433e5024c27ce74a37f94267945b url: "https://pub.dev" source: hosted - version: "1.8.3" + version: "1.8.2" petitparser: dependency: transitive description: @@ -468,26 +468,26 @@ packages: dependency: transitive description: name: test - sha256: b54d427664c00f2013ffb87797a698883c46aee9288e027a50b46eaee7486fa2 + sha256: a5fcd2d25eeadbb6589e80198a47d6a464ba3e2049da473943b8af9797900c2d url: "https://pub.dev" source: hosted - version: "1.22.2" + version: "1.22.0" test_api: dependency: transitive description: name: test_api - sha256: "6182294da5abf431177fccc1ee02401f6df30f766bc6130a0852c6b6d7ee6b2d" + sha256: ad540f65f92caa91bf21dfc8ffb8c589d6e4dc0c2267818b4cc2792857706206 url: "https://pub.dev" source: hosted - version: "0.4.18" + version: "0.4.16" test_core: dependency: transitive description: name: test_core - sha256: "95ecc12692d0dd59080ab2d38d9cf32c7e9844caba23ff6cd285690398ee8ef4" + sha256: "0ef9755ec6d746951ba0aabe62f874b707690b5ede0fecc818b138fcc9b14888" url: "https://pub.dev" source: hosted - version: "0.4.22" + version: "0.4.20" typed_data: dependency: transitive description: @@ -553,5 +553,5 @@ packages: source: hosted version: "3.1.1" sdks: - dart: ">=2.19.0 <4.0.0" + dart: ">=2.18.0 <3.0.0" flutter: ">=1.20.0" From 5fc566cb5b2e199e1b08103a7a26f29649f8b4d9 Mon Sep 17 00:00:00 2001 From: Zemlaynikin Max Date: Thu, 6 Apr 2023 22:30:06 +0400 Subject: [PATCH 010/151] 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 --- CHANGELOG.md | 3 + example/pubspec.lock | 2 +- lib/src/layout/window.dart | 158 +++++++++++++++++++++---------------- pubspec.yaml | 2 +- 4 files changed, 95 insertions(+), 70 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 55ffc59e..741b038b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +## [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) diff --git a/example/pubspec.lock b/example/pubspec.lock index 546efaa5..0d45eb9e 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -97,7 +97,7 @@ packages: path: ".." relative: true source: path - version: "1.12.3" + version: "1.12.5" matcher: dependency: transitive description: diff --git a/lib/src/layout/window.dart b/lib/src/layout/window.dart index f20979b9..21e82e07 100644 --- a/lib/src/layout/window.dart +++ b/lib/src/layout/window.dart @@ -52,8 +52,8 @@ class MacosWindow extends StatefulWidget { } 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,39 +74,59 @@ 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(() {})); - } + _addSidebarScrollControllerListenerIfNeeded(); + _addEndSidebarScrollControllerListenerIfNeeded(); } @override void didUpdateWidget(covariant MacosWindow old) { super.didUpdateWidget(old); - if (widget.sidebar == null) { + final sidebar = widget.sidebar; + if (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; + } else if (sidebar.minWidth != old.sidebar!.minWidth || + sidebar.maxWidth != old.sidebar!.maxWidth) { + if (sidebar.minWidth > _sidebarWidth) { + _sidebarWidth = sidebar.minWidth; } - if (widget.sidebar!.maxWidth! < _sidebarWidth) { - _sidebarWidth = widget.sidebar!.maxWidth!; + if (sidebar.maxWidth! < _sidebarWidth) { + _sidebarWidth = sidebar.maxWidth!; } } - if (widget.endSidebar == null) { + if (sidebar?.key != old.sidebar?.key) { + _sidebarScrollController.dispose(); + _sidebarScrollController = ScrollController(); + _addSidebarScrollControllerListenerIfNeeded(); + } + final endSidebar = widget.endSidebar; + if (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; + } else if (endSidebar.minWidth != old.endSidebar!.minWidth || + endSidebar.maxWidth != old.endSidebar!.maxWidth) { + if (endSidebar.minWidth > _endSidebarWidth) { + _endSidebarWidth = 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 @@ -120,13 +140,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; @@ -137,8 +159,8 @@ 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!; + if (sidebar?.decoration?.color != null) { + sidebarBackgroundColor = sidebar!.decoration!.color!; } else if (isMac && MediaQuery.of(context).platformBrightness.isDark == theme.brightness.isDark) { @@ -154,8 +176,8 @@ class _MacosWindowState extends State { } // Respect the end sidebar color override from parent if one is given - if (widget.endSidebar?.decoration?.color != null) { - endSidebarBackgroundColor = widget.endSidebar!.decoration!.color!; + if (endSidebar?.decoration?.color != null) { + endSidebarBackgroundColor = endSidebar!.decoration!.color!; } else if (isMac && MediaQuery.of(context).platformBrightness.isDark == theme.brightness.isDark) { @@ -173,9 +195,8 @@ 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 isAtBreakpoint = width <= (sidebar?.windowBreakpoint ?? 0); + final isAtEndBreakpoint = width <= (endSidebar?.windowBreakpoint ?? 0); final canShowSidebar = _showSidebar && !isAtBreakpoint; final canShowEndSidebar = _showEndSidebar && !isAtEndBreakpoint; final visibleSidebarWidth = canShowSidebar ? _sidebarWidth : 0.0; @@ -195,8 +216,9 @@ class _MacosWindowState extends State { ), // Sidebar - if (widget.sidebar != null) + if (sidebar != null) AnimatedPositioned( + key: sidebar.key, curve: curve, duration: duration, height: height, @@ -206,39 +228,39 @@ 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 (sidebar.topOffset > 0) + SizedBox(height: sidebar.topOffset), if (_sidebarScrollController.hasClients && _sidebarScrollController.offset > 0.0) Divider(thickness: 1, height: 1, color: dividerColor), - if (widget.sidebar!.top != null && - constraints.maxHeight > 81) + if (sidebar.top != null && constraints.maxHeight > 81) Padding( padding: const EdgeInsets.symmetric(horizontal: 8.0), - child: widget.sidebar!.top!, + child: sidebar.top!, ), Expanded( child: MacosScrollbar( controller: _sidebarScrollController, child: Padding( - padding: widget.sidebar?.padding ?? EdgeInsets.zero, - child: widget.sidebar! - .builder(context, _sidebarScrollController), + padding: sidebar.padding, + child: sidebar.builder( + context, + _sidebarScrollController, + ), ), ), ), - if (widget.sidebar?.bottom != null && - constraints.maxHeight > 141) + if (sidebar.bottom != null && constraints.maxHeight > 141) Padding( padding: const EdgeInsets.all(16.0), - child: widget.sidebar!.bottom!, + child: sidebar.bottom!, ), ], ), @@ -273,7 +295,7 @@ class _MacosWindowState extends State { ), // Sidebar resizer - if (widget.sidebar?.isResizable ?? false) + if (sidebar?.isResizable ?? false) AnimatedPositioned( curve: curve, duration: duration, @@ -287,13 +309,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!) { @@ -338,8 +359,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, @@ -350,38 +372,39 @@ 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 (endSidebar.topOffset > 0) + SizedBox(height: endSidebar.topOffset), if (_endSidebarScrollController.hasClients && _endSidebarScrollController.offset > 0.0) Divider(thickness: 1, height: 1, color: dividerColor), - if (widget.endSidebar!.top != null) + if (endSidebar.top != null) Padding( padding: const EdgeInsets.symmetric(horizontal: 8.0), - child: widget.endSidebar!.top!, + child: endSidebar.top!, ), Expanded( child: MacosScrollbar( controller: _endSidebarScrollController, child: Padding( - padding: - widget.endSidebar?.padding ?? EdgeInsets.zero, - child: widget.endSidebar! - .builder(context, _endSidebarScrollController), + padding: endSidebar.padding, + child: endSidebar.builder( + context, + _endSidebarScrollController, + ), ), ), ), - if (widget.endSidebar?.bottom != null) + if (endSidebar.bottom != null) Padding( padding: const EdgeInsets.all(16.0), - child: widget.endSidebar!.bottom!, + child: endSidebar.bottom!, ), ], ), @@ -389,7 +412,7 @@ class _MacosWindowState extends State { ), // End sidebar resizer - if (widget.endSidebar?.isResizable ?? false) + if (endSidebar?.isResizable ?? false) AnimatedPositioned( curve: curve, duration: duration, @@ -403,13 +426,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!) { diff --git a/pubspec.yaml b/pubspec.yaml index 40ee143e..bf720d2b 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.4 +version: 1.12.5 homepage: "https://macosui.dev" repository: "https://github.com/GroovinChip/macos_ui" From a77d86f45d0f9df9cfdf755422027f00f540bf61 Mon Sep 17 00:00:00 2001 From: Adrian Samoticha <86920182+Adrian-Samoticha@users.noreply.github.com> Date: Tue, 11 Apr 2023 19:24:01 +0200 Subject: [PATCH 011/151] Migrate to macos_window_utils (#377) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 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 --- CHANGELOG.md | 10 + README.md | 104 +++------- example/lib/main.dart | 55 +++-- example/lib/pages/sliver_toolbar_page.dart | 23 ++- .../Flutter/GeneratedPluginRegistrant.swift | 2 + example/macos/Podfile | 2 +- example/macos/Podfile.lock | 8 +- .../macos/Runner.xcodeproj/project.pbxproj | 9 +- example/macos/Runner/MainFlutterWindow.swift | 75 +------ example/pubspec.lock | 14 +- lib/macos_ui.dart | 5 + lib/src/layout/scaffold.dart | 20 +- lib/src/layout/toolbar/sliver_toolbar.dart | 34 ++- lib/src/layout/toolbar/toolbar.dart | 176 +++++++++++----- 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 | 194 +++++++++++------- lib/src/macos_window_utils_config.dart | 120 +++++++++++ lib/src/theme/macos_theme.dart | 5 +- lib/src/theme/overlay_filter.dart | 67 +++--- lib/src/utils.dart | 27 +++ pubspec.lock | 12 +- pubspec.yaml | 3 +- test/buttons/back_button_test.dart | 1 + test/buttons/help_button_test.dart | 1 + test/buttons/icon_button_test.dart | 1 + test/buttons/push_button_test.dart | 1 + test/buttons/radio_button_test.dart | 1 + test/layout/macos_list_tile_test.dart | 1 + test/layout/resizeable_pane_test.dart | 2 + test/layout/sliver_toolbar_test.dart | 10 + test/layout/window_test.dart | 27 +++ test/selectors/date_picker_test.dart | 2 + test/theme/help_button_theme_test.dart | 1 + test/theme/icon_button_theme_test.dart | 1 + test/theme/icon_theme_test.dart | 1 + test/theme/popup_button_theme_test.dart | 1 + test/theme/pulldown_button_theme_test.dart | 1 + test/theme/push_button_theme_test.dart | 1 + test/theme/search_field_theme_test.dart | 1 + 43 files changed, 993 insertions(+), 353 deletions(-) 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 diff --git a/CHANGELOG.md b/CHANGELOG.md index 741b038b..07499bb1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,13 @@ +## [2.0.0-beta.1] +🚨 Breaking Changes 🚨 +* Migrate macos_ui to [macos_window_utils](https://pub.dev/packages/macos_window_utils), 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. + ## [1.12.5] * Fixed a bug where the `Sidebar.key` parameter wasn't used, which caused certain layouts to be unachievable. diff --git a/README.md b/README.md index a88690b9..4c8f9447 100644 --- a/README.md +++ b/README.md @@ -226,96 +226,42 @@ 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: +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: -```swift -import Cocoa -import FlutterMacOS - -class BlurryContainerViewController: NSViewController { - let flutterViewController = FlutterViewController() - - init() { - super.init(nibName: nil, bundle: nil) - } - - required init?(coder: NSCoder) { - fatalError() - } +```podspec +platform :osx, '10.14.6' +``` - 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 MacosUIGalleryApp()); } +``` -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() - } +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 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 - } +```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 diff --git a/example/lib/main.dart b/example/lib/main.dart index 3d1a24e7..cba22164 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -13,7 +13,15 @@ import 'package:provider/provider.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 { + await _configureMacosWindowUtils(); + runApp(const MacosUIGalleryApp()); } @@ -55,23 +63,25 @@ class _WidgetGalleryState extends State { 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(), + final List pageBuilders = [ + (bool isVisible) => CupertinoTabView( + builder: (_) => const ButtonsPage(), + ), + (bool isVisible) => const IndicatorsPage(), + (bool isVisible) => const FieldsPage(), + (bool isVisible) => const ColorsPage(), + (bool isVisible) => const Center( + child: MacosIcon( + CupertinoIcons.add, + ), + ), + (bool isVisible) => const DialogsPage(), + (bool isVisible) => const ToolbarPage(), + (bool isVisible) => SliverToolbarPage( + isVisible: isVisible, + ), + (bool isVisible) => const TabViewPage(), + (bool isVisible) => const SelectorsPage(), ]; @override @@ -291,7 +301,14 @@ class _WidgetGalleryState extends State { ), child: IndexedStack( index: pageIndex, - children: pages, + children: pageBuilders + .asMap() + .map((index, builder) { + final widget = builder(index == pageIndex); + return MapEntry(index, widget); + }) + .values + .toList(), ), ), ); 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/macos/Flutter/GeneratedPluginRegistrant.swift b/example/macos/Flutter/GeneratedPluginRegistrant.swift index 722b1dd3..b06f1179 100644 --- a/example/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/example/macos/Flutter/GeneratedPluginRegistrant.swift @@ -6,7 +6,9 @@ import FlutterMacOS import Foundation import macos_ui +import macos_window_utils func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { MacOSUiPlugin.register(with: registry.registrar(forPlugin: "MacOSUiPlugin")) + MacOSWindowUtilsPlugin.register(with: registry.registrar(forPlugin: "MacOSWindowUtilsPlugin")) } 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..42b5eb53 100644 --- a/example/macos/Podfile.lock +++ b/example/macos/Podfile.lock @@ -2,21 +2,27 @@ PODS: - FlutterMacOS (1.0.0) - macos_ui (0.1.0): - FlutterMacOS + - macos_window_utils (1.0.0): + - 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`) 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 SPEC CHECKSUMS: FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24 macos_ui: 6229a8922cd97bafb7d9636c8eb8dfb0744183ca + macos_window_utils: 933f91f64805e2eb91a5bd057cf97cd097276663 -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/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/pubspec.lock b/example/pubspec.lock index 0d45eb9e..a440e555 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -97,7 +97,15 @@ packages: path: ".." relative: true source: path - version: "1.12.5" + version: "2.0.0-beta.1" + macos_window_utils: + dependency: transitive + description: + name: macos_window_utils + sha256: "510de576b5432dd9ef9e4c258abcc021c6dfbb17a78a344688848a6784b352b8" + url: "https://pub.dev" + source: hosted + version: "1.1.2" matcher: dependency: transitive description: @@ -208,5 +216,5 @@ packages: source: hosted version: "2.1.4" sdks: - dart: ">=2.18.0 <3.0.0" - flutter: ">=1.20.0" + dart: ">=2.18.5 <3.0.0" + flutter: ">=3.7.0" diff --git a/lib/macos_ui.dart b/lib/macos_ui.dart index 6a71ceec..4d3ac251 100644 --- a/lib/macos_ui.dart +++ b/lib/macos_ui.dart @@ -82,3 +82,8 @@ export 'src/theme/search_field_theme.dart'; export 'src/theme/time_picker_theme.dart'; export 'src/theme/tooltip_theme.dart'; export 'src/theme/typography.dart'; +export 'src/layout/wallpaper_tinted_area.dart'; +export 'src/macos_window_utils_config.dart'; + +export 'package:macos_window_utils/macos_window_utils.dart'; +export 'package:macos_window_utils/macos/ns_window_delegate.dart'; diff --git a/lib/src/layout/scaffold.dart b/lib/src/layout/scaffold.dart index 712ab1a7..871dba0d 100644 --- a/lib/src/layout/scaffold.dart +++ b/lib/src/layout/scaffold.dart @@ -1,14 +1,8 @@ import 'dart:math' as math; 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. /// @@ -92,11 +86,15 @@ class _MacosScaffoldState extends State { top: 0, width: width, height: height, - child: MediaQuery( - data: mediaQuery.copyWith( - padding: EdgeInsets.only(top: topPadding), + child: WallpaperTintedArea( + backgroundColor: backgroundColor, + insertRepaintBoundary: true, + child: MediaQuery( + data: mediaQuery.copyWith( + padding: EdgeInsets.only(top: topPadding), + ), + child: _ScaffoldBody(children: children), ), - child: _ScaffoldBody(children: children), ), ), diff --git a/lib/src/layout/toolbar/sliver_toolbar.dart b/lib/src/layout/toolbar/sliver_toolbar.dart index a5a00dbf..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; @@ -293,6 +321,8 @@ class _SliverToolBarDelegate extends SliverPersistentHeaderDelegate { dividerColor: dividerColor, alignment: alignment, height: height, + enableBlur: true, + allowWallpaperTintingOverrides: allowWallpaperTintingOverrides, ), ); } @@ -311,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..bad588c8 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); @@ -233,57 +265,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: !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 +350,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/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 21e82e07..3171decc 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,6 +45,40 @@ 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(); } @@ -74,6 +106,11 @@ class _MacosWindowState extends State { _endSidebarWidth = (widget.endSidebar?.startWidth ?? widget.endSidebar?.minWidth) ?? _endSidebarWidth; + + widget.disableWallpaperTinting + ? GlobalWallpaperTintingSettings.disableWallpaperTinting() + : GlobalWallpaperTintingSettings.allowWallpaperTinting(); + _addSidebarScrollControllerListenerIfNeeded(); _addEndSidebarScrollControllerListenerIfNeeded(); } @@ -133,6 +170,7 @@ class _MacosWindowState extends State { void dispose() { _sidebarScrollController.dispose(); _endSidebarScrollController.dispose(); + super.dispose(); } @@ -161,26 +199,17 @@ class _MacosWindowState extends State { // Respect the sidebar color override from parent if one is given if (sidebar?.decoration?.color != null) { sidebarBackgroundColor = 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; } 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 (endSidebar?.decoration?.color != null) { endSidebarBackgroundColor = endSidebar!.decoration!.color!; - } else if (isMac && - MediaQuery.of(context).platformBrightness.isDark == - theme.brightness.isDark) { + } else if (isMac) { endSidebarBackgroundColor = theme.canvasColor; } else { endSidebarBackgroundColor = theme.brightness.isDark @@ -202,6 +231,7 @@ class _MacosWindowState extends State { final visibleSidebarWidth = canShowSidebar ? _sidebarWidth : 0.0; final visibleEndSidebarWidth = canShowEndSidebar ? _endSidebarWidth : 0.0; + final sidebarState = widget.sidebarState; final layout = Stack( children: [ @@ -233,36 +263,41 @@ class _MacosWindowState extends State { minHeight: height, maxHeight: height, ).normalize(), - child: Column( - children: [ - if (sidebar.topOffset > 0) - SizedBox(height: sidebar.topOffset), - 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, + child: TransparentMacOSSidebar( + state: sidebarState, + child: Column( + children: [ + if ((sidebar.topOffset) > 0) + SizedBox(height: sidebar.topOffset), + 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!, - ), - ], + if (sidebar.bottom != null && + constraints.maxHeight > 141) + Padding( + padding: const EdgeInsets.all(16.0), + child: sidebar.bottom!, + ), + ], + ), ), ), ), @@ -377,36 +412,45 @@ class _MacosWindowState extends State { minHeight: height, maxHeight: height, ).normalize(), - 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: const EdgeInsets.symmetric(horizontal: 8.0), - child: endSidebar.top!, - ), - Expanded( - child: MacosScrollbar( - controller: _endSidebarScrollController, - child: Padding( - padding: endSidebar.padding, - child: endSidebar.builder( - context, - _endSidebarScrollController, + 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: + 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 (endSidebar.bottom != null) - Padding( - padding: const EdgeInsets.all(16.0), - child: endSidebar.bottom!, - ), - ], + if (endSidebar.bottom != null) + Padding( + padding: const EdgeInsets.all(16.0), + child: endSidebar.bottom!, + ), + ], + ), ), ), ), 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/theme/macos_theme.dart b/lib/src/theme/macos_theme.dart index c8ea1f55..78508f00 100644 --- a/lib/src/theme/macos_theme.dart +++ b/lib/src/theme/macos_theme.dart @@ -213,9 +213,10 @@ 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; + ? const Color.fromRGBO(40, 40, 40, 1.0) + : const Color.fromRGBO(246, 246, 246, 1.0); typography ??= MacosTypography( color: _brightness == Brightness.light ? CupertinoColors.black 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/utils.dart b/lib/src/utils.dart index 08f2debc..a986d412 100644 --- a/lib/src/utils.dart +++ b/lib/src/utils.dart @@ -1,3 +1,5 @@ +import 'dart:io'; + import 'package:macos_ui/macos_ui.dart'; import 'package:macos_ui/src/library.dart'; @@ -93,3 +95,28 @@ class Unsupported { 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 (!Platform.isMacOS) { + return; + } + + if (currentBrightness == _lastBrightness) { + return; + } + + WindowManipulator.overrideMacOSBrightness(dark: currentBrightness.isDark); + _lastBrightness = currentBrightness; + } +} diff --git a/pubspec.lock b/pubspec.lock index 6cfa9f14..3e85534f 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -259,6 +259,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.1" + macos_window_utils: + dependency: "direct main" + description: + name: macos_window_utils + sha256: "510de576b5432dd9ef9e4c258abcc021c6dfbb17a78a344688848a6784b352b8" + url: "https://pub.dev" + source: hosted + version: "1.1.2" matcher: dependency: transitive description: @@ -553,5 +561,5 @@ packages: source: hosted version: "3.1.1" sdks: - dart: ">=2.18.0 <3.0.0" - flutter: ">=1.20.0" + dart: ">=2.18.5 <3.0.0" + flutter: ">=3.7.0" diff --git a/pubspec.yaml b/pubspec.yaml index bf720d2b..afacfecd 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.5 +version: 2.0.0-beta.1 homepage: "https://macosui.dev" repository: "https://github.com/GroovinChip/macos_ui" @@ -11,6 +11,7 @@ environment: dependencies: flutter: sdk: flutter + macos_window_utils: ^1.1.2 dev_dependencies: flutter_test: 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/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/push_button_test.dart b/test/buttons/push_button_test.dart index 96dc571e..3843c6a2 100644 --- a/test/buttons/push_button_test.dart +++ b/test/buttons/push_button_test.dart @@ -55,6 +55,7 @@ void main() { pushButtonTheme: darkPushButtonThemeData, ), home: MacosWindow( + disableWallpaperTinting: true, child: MacosScaffold( children: [ ContentArea( 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/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..56164763 100644 --- a/test/layout/resizeable_pane_test.dart +++ b/test/layout/resizeable_pane_test.dart @@ -29,6 +29,7 @@ void main() { final view = side == ResizableSide.top ? MacosApp( home: MacosWindow( + disableWallpaperTinting: true, child: MacosScaffold( children: [ ContentArea( @@ -52,6 +53,7 @@ void main() { ) : MacosApp( home: MacosWindow( + disableWallpaperTinting: true, child: MacosScaffold( children: [ resizablePane, 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..b0e50b5d 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( diff --git a/test/theme/help_button_theme_test.dart b/test/theme/help_button_theme_test.dart index 702a85ed..98556548 100644 --- a/test/theme/help_button_theme_test.dart +++ b/test/theme/help_button_theme_test.dart @@ -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..b45e4de6 100644 --- a/test/theme/icon_theme_test.dart +++ b/test/theme/icon_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/popup_button_theme_test.dart b/test/theme/popup_button_theme_test.dart index daa039de..730d86a6 100644 --- a/test/theme/popup_button_theme_test.dart +++ b/test/theme/popup_button_theme_test.dart @@ -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..0ef34561 100644 --- a/test/theme/pulldown_button_theme_test.dart +++ b/test/theme/pulldown_button_theme_test.dart @@ -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..a910d981 100644 --- a/test/theme/push_button_theme_test.dart +++ b/test/theme/push_button_theme_test.dart @@ -58,6 +58,7 @@ void main() { await tester.pumpWidget( MacosApp( home: MacosWindow( + disableWallpaperTinting: true, child: MacosScaffold( children: [ ContentArea( 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( From 5269fcdb5c0901c05af63e1285d2365b90dc64a2 Mon Sep 17 00:00:00 2001 From: Michelle Raouf <72160249+the-best-is-best@users.noreply.github.com> Date: Wed, 17 May 2023 01:41:26 +0300 Subject: [PATCH 012/151] support flutter 3.10 minimum dart3 (#426) --- example/lib/pages/fields_page.dart | 12 +-- example/lib/pages/toolbar_page.dart | 4 +- example/pubspec.lock | 40 ++++---- lib/src/fields/text_field.dart | 7 +- lib/src/layout/scaffold.dart | 2 +- pubspec.lock | 152 +++++++++++++++------------- pubspec.yaml | 4 +- test/indicators/scrollbar_test.dart | 4 +- test/indicators/slider_test.dart | 14 ++- 9 files changed, 124 insertions(+), 115 deletions(-) diff --git a/example/lib/pages/fields_page.dart b/example/lib/pages/fields_page.dart index 94785708..00b2aa81 100644 --- a/example/lib/pages/fields_page.dart +++ b/example/lib/pages/fields_page.dart @@ -352,8 +352,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 +365,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 +378,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/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/pubspec.lock b/example/pubspec.lock index a440e555..c9114539 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,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" cupertino_icons: dependency: "direct main" description: @@ -79,18 +79,18 @@ packages: 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: @@ -110,10 +110,10 @@ packages: 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: @@ -126,10 +126,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: @@ -142,10 +142,10 @@ packages: dependency: transitive description: name: path - sha256: db9d4f58c908a4ba5953fcee2ae317c94889433e5024c27ce74a37f94267945b + sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" url: "https://pub.dev" source: hosted - version: "1.8.2" + version: "1.8.3" provider: dependency: "direct main" description: @@ -203,10 +203,10 @@ packages: dependency: transitive description: name: test_api - sha256: ad540f65f92caa91bf21dfc8ffb8c589d6e4dc0c2267818b4cc2792857706206 + sha256: eb6ac1540b26de412b3403a163d919ba86f6a973fe6cc50ae3541b80092fdcfb url: "https://pub.dev" source: hosted - version: "0.4.16" + version: "0.5.1" vector_math: dependency: transitive description: @@ -216,5 +216,5 @@ packages: source: hosted version: "2.1.4" sdks: - dart: ">=2.18.5 <3.0.0" - flutter: ">=3.7.0" + dart: ">=3.0.0 <4.0.0" + flutter: ">=3.10.0" diff --git a/lib/src/fields/text_field.dart b/lib/src/fields/text_field.dart index f3cf0888..08a37f6f 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); } } diff --git a/lib/src/layout/scaffold.dart b/lib/src/layout/scaffold.dart index 871dba0d..2df5a94d 100644 --- a/lib/src/layout/scaffold.dart +++ b/lib/src/layout/scaffold.dart @@ -113,7 +113,7 @@ class _MacosScaffoldState extends State { } class _ScaffoldBody extends MultiChildRenderObjectWidget { - _ScaffoldBody({ + const _ScaffoldBody({ super.children, }); diff --git a/pubspec.lock b/pubspec.lock index 3e85534f..d14c154e 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -5,18 +5,18 @@ 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" + version: "5.11.1" analyzer_plugin: dependency: transitive description: @@ -37,18 +37,18 @@ packages: 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 +61,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 +77,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,10 +101,10 @@ packages: dependency: transitive description: name: crypto - sha256: aa274aa7774f8964e4f4f38cc994db7b6158dd36e9187aaceaddc994b35c6c67 + sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab url: "https://pub.dev" source: hosted - version: "3.0.2" + version: "3.0.3" csslib: dependency: transitive description: @@ -117,26 +117,26 @@ packages: dependency: "direct dev" description: name: dart_code_metrics - sha256: "026e28da197a03caeccccc0b174ec98ef03da3c81c4543314d7add121aab4375" + sha256: "162c81dbd0a2ba182f38ca615335f3e8878f212ec7beea83d6bfad4e99eb541a" url: "https://pub.dev" source: hosted - version: "5.6.0" + version: "5.7.3" dart_code_metrics_presets: dependency: transitive description: name: dart_code_metrics_presets - sha256: "9c51724f836aebc4465228954cb5757e5a99737af26a452b5dec0a2d5d0b4d66" + sha256: "22e27f98e8c7d8b11cca43d2656a822935280747050ae65e8cd03c52d09c0d1c" url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "1.7.0" dart_style: dependency: transitive description: name: dart_style - sha256: "7a03456c3490394c8e7665890333e91ae8a49be43542b616e414449ac358acd4" + sha256: f4f1f73ab3fd2afcbcca165ee601fe980d966af6a21b5970c6c9376955c528ad url: "https://pub.dev" source: hosted - version: "2.2.4" + version: "2.3.1" fake_async: dependency: transitive description: @@ -191,18 +191,18 @@ packages: dependency: transitive description: name: html - sha256: d9793e10dbe0e6c364f4c59bf3e01fb33a9b2a674bc7a1081693dba0614b6269 + sha256: "58e3491f7bf0b6a4ea5110c0c688877460d1a6366731155c4a4580e7ded773e8" url: "https://pub.dev" source: hosted - version: "0.15.1" + version: "0.15.3" http: dependency: transitive description: name: http - sha256: "6aa2946395183537c8b880962d935877325d6a09a2867c3970c05c0fed6ac482" + sha256: "5895291c13fa8a3bd82e76d5627f69e0d85ca6a30dcac95c4ea19a5d555879c2" url: "https://pub.dev" source: hosted - version: "0.13.5" + version: "0.13.6" http_multi_server: dependency: transitive description: @@ -231,26 +231,26 @@ packages: dependency: transitive description: name: js - sha256: "5528c2f391ededb7775ec1daa69e65a2d61276f7552de2b5f7b8d34ee9fd4ab7" + sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 url: "https://pub.dev" source: hosted - version: "0.6.5" + version: "0.6.7" json_annotation: dependency: transitive description: name: json_annotation - sha256: c33da08e136c3df0190bd5bbe51ae1df4a7d96e7954d1d7249fea2968a72d317 + sha256: b10a7b2ff83d83c777edba3c6a0f97045ddadd56c944e1a23a3fdf43a1bf4467 url: "https://pub.dev" source: hosted - version: "4.8.0" + version: "4.8.1" 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: @@ -271,10 +271,10 @@ packages: 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: @@ -287,10 +287,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: @@ -311,10 +311,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: @@ -327,18 +327,18 @@ packages: dependency: transitive description: name: path - sha256: db9d4f58c908a4ba5953fcee2ae317c94889433e5024c27ce74a37f94267945b + sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" url: "https://pub.dev" source: hosted - version: "1.8.2" + version: "1.8.3" petitparser: dependency: transitive description: name: petitparser - sha256: "49392a45ced973e8d94a85fdb21293fbb40ba805fc49f2965101ae748a3683b4" + sha256: cb3798bef7fc021ac45b308f4b51208a152792445cce0448c9a4ba5879dd8750 url: "https://pub.dev" source: hosted - version: "5.1.0" + version: "5.4.0" platform: dependency: transitive description: @@ -367,50 +367,50 @@ packages: dependency: transitive description: name: pub_semver - sha256: "307de764d305289ff24ad257ad5c5793ce56d04947599ad68b3baa124105fc17" + sha256: "40d3ab1bbd474c4c2328c91e3a7df8c6dd629b79ece4c4bd04bee496a224fb0c" url: "https://pub.dev" source: hosted - version: "2.1.3" + version: "2.1.4" pub_updater: dependency: transitive description: name: pub_updater - sha256: "42890302ab2672adf567dc2b20e55b4ecc29d7e19c63b6b98143ab68dd717d3a" + sha256: "05ae70703e06f7fdeb05f7f02dd680b8aad810e87c756a618f33e1794635115c" url: "https://pub.dev" source: hosted - version: "0.2.4" + version: "0.3.0" 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 @@ -476,34 +476,42 @@ 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" + uuid: + dependency: transitive + description: + name: uuid + sha256: "648e103079f7c64a36dc7d39369cabb358d377078a051d6ae2ad3aa539519313" + url: "https://pub.dev" + source: hosted + version: "3.0.7" vector_math: dependency: transitive description: @@ -516,26 +524,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: @@ -548,18 +556,18 @@ packages: dependency: transitive description: name: xml - sha256: "979ee37d622dec6365e2efa4d906c37470995871fe9ae080d967e192d88286b5" + sha256: "5bc72e1e45e941d825fd7468b9b4cc3b9327942649aeb6fc5cdbf135f0a86e84" url: "https://pub.dev" source: hosted - version: "6.2.2" + version: "6.3.0" 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.5 <3.0.0" - flutter: ">=3.7.0" + dart: ">=3.0.0 <4.0.0" + flutter: ">=3.10.0" diff --git a/pubspec.yaml b/pubspec.yaml index afacfecd..76275906 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -5,8 +5,8 @@ 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: 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); }); } From e99a19b7833c687e9d55b4691646d697c56fa77d Mon Sep 17 00:00:00 2001 From: Reuben Turner Date: Tue, 16 May 2023 18:43:30 -0400 Subject: [PATCH 013/151] Update dart_code_metrics.yaml --- .github/workflows/dart_code_metrics.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/dart_code_metrics.yaml b/.github/workflows/dart_code_metrics.yaml index a22ecac7..29c79dd3 100644 --- a/.github/workflows/dart_code_metrics.yaml +++ b/.github/workflows/dart_code_metrics.yaml @@ -6,7 +6,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Run Dart Code Metrics uses: dart-code-checker/dart-code-metrics-action@v3 From f7e03338dc5a52f9dd1849f354763b730fcb4ec3 Mon Sep 17 00:00:00 2001 From: Reuben Turner Date: Wed, 17 May 2023 11:20:11 -0400 Subject: [PATCH 014/151] Update dart_code_metrics.yaml --- .github/workflows/dart_code_metrics.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/dart_code_metrics.yaml b/.github/workflows/dart_code_metrics.yaml index 29c79dd3..7d8f1ef1 100644 --- a/.github/workflows/dart_code_metrics.yaml +++ b/.github/workflows/dart_code_metrics.yaml @@ -9,7 +9,7 @@ jobs: - uses: actions/checkout@v4 - name: Run Dart Code Metrics - uses: dart-code-checker/dart-code-metrics-action@v3 + uses: dart-code-checker/dart-code-metrics-action@v4.0.0 with: github_token: ${{ secrets.GITHUB_TOKEN }} pull_request_comment: true From b64e2e46a9667eb0d74d8d123530147b4078612e Mon Sep 17 00:00:00 2001 From: Reuben Turner Date: Wed, 17 May 2023 11:23:29 -0400 Subject: [PATCH 015/151] Update dart_code_metrics.yaml --- .github/workflows/dart_code_metrics.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/dart_code_metrics.yaml b/.github/workflows/dart_code_metrics.yaml index 7d8f1ef1..16194aed 100644 --- a/.github/workflows/dart_code_metrics.yaml +++ b/.github/workflows/dart_code_metrics.yaml @@ -6,7 +6,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v3 - name: Run Dart Code Metrics uses: dart-code-checker/dart-code-metrics-action@v4.0.0 From f1053fc31958dbaa362a0a4c8288e8cf0919ab8c Mon Sep 17 00:00:00 2001 From: Bernardo Ferrari Date: Wed, 17 May 2023 12:25:47 -0300 Subject: [PATCH 016/151] Fix EnumProperty test. (#419) Fix test. Co-authored-by: Reuben Turner --- lib/src/fields/text_field.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/fields/text_field.dart b/lib/src/fields/text_field.dart index 08a37f6f..8564301d 100644 --- a/lib/src/fields/text_field.dart +++ b/lib/src/fields/text_field.dart @@ -759,7 +759,7 @@ class MacosTextField extends StatefulWidget { 'clearButtonMode', clearButtonMode, )); - properties.add(EnumProperty( + properties.add(DiagnosticsProperty( 'keyboardType', keyboardType, defaultValue: TextInputType.text, From 15fb991dbf6885a41c2a91a0c607b9b426306a40 Mon Sep 17 00:00:00 2001 From: GroovinChip Date: Mon, 3 Jul 2023 13:16:35 -0400 Subject: [PATCH 017/151] update dependencies --- example/pubspec.lock | 4 ++-- pubspec.lock | 12 ++++++------ pubspec.yaml | 6 +++--- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/example/pubspec.lock b/example/pubspec.lock index c9114539..bf64e601 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -102,10 +102,10 @@ packages: dependency: transitive description: name: macos_window_utils - sha256: "510de576b5432dd9ef9e4c258abcc021c6dfbb17a78a344688848a6784b352b8" + sha256: b78a210aa70ca7ccad6e7b7b810fb4689c507f4a46e299214900b2a1eb70ea23 url: "https://pub.dev" source: hosted - version: "1.1.2" + version: "1.1.3" matcher: dependency: transitive description: diff --git a/pubspec.lock b/pubspec.lock index d14c154e..4408d237 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -117,10 +117,10 @@ packages: dependency: "direct dev" description: name: dart_code_metrics - sha256: "162c81dbd0a2ba182f38ca615335f3e8878f212ec7beea83d6bfad4e99eb541a" + sha256: "1dc1fa763b73ed52147bd91b015d81903edc3f227b77b1672fcddba43390ed18" url: "https://pub.dev" source: hosted - version: "5.7.3" + version: "5.7.5" dart_code_metrics_presets: dependency: transitive description: @@ -162,10 +162,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 @@ -263,10 +263,10 @@ packages: dependency: "direct main" description: name: macos_window_utils - sha256: "510de576b5432dd9ef9e4c258abcc021c6dfbb17a78a344688848a6784b352b8" + sha256: b78a210aa70ca7ccad6e7b7b810fb4689c507f4a46e299214900b2a1eb70ea23 url: "https://pub.dev" source: hosted - version: "1.1.2" + version: "1.1.3" matcher: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 76275906..e79f0cbc 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -11,13 +11,13 @@ environment: dependencies: flutter: sdk: flutter - macos_window_utils: ^1.1.2 + macos_window_utils: ^1.1.3 dev_dependencies: flutter_test: sdk: flutter - dart_code_metrics: ^5.6.0 - flutter_lints: ^2.0.1 + dart_code_metrics: ^5.7.5 + flutter_lints: ^2.0.2 mocktail: ^0.3.0 flutter: From 3af34b1a743e6a7f3520160af74172a3ba6017bc Mon Sep 17 00:00:00 2001 From: GroovinChip Date: Mon, 3 Jul 2023 13:18:16 -0400 Subject: [PATCH 018/151] update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 07499bb1..2c6bd4af 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ * 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. +* Support Flutter 3.10 and Dart 3 To migrate an existing application, please refer to the “Modern window look” section in the README. From a56544aad7b67ac2b6999a78c5eb3783a12ecb40 Mon Sep 17 00:00:00 2001 From: GroovinChip Date: Mon, 3 Jul 2023 13:20:55 -0400 Subject: [PATCH 019/151] fix dart version constraint --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index e79f0cbc..02ae5581 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -5,7 +5,7 @@ homepage: "https://macosui.dev" repository: "https://github.com/GroovinChip/macos_ui" environment: - sdk: ">=3.0.0 <=4.0.0" + sdk: ">=3.0.0 <4.0.0" flutter: ">=3.10.0" dependencies: From 2988b94e705ca8ba4dd23859f1c31555bc119136 Mon Sep 17 00:00:00 2001 From: Reuben Turner Date: Mon, 3 Jul 2023 14:27:30 -0400 Subject: [PATCH 020/151] 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 --- CHANGELOG.md | 10 +- README.md | 24 +- example/lib/pages/buttons_page.dart | 31 +- example/pubspec.lock | 2 +- lib/macos_ui.dart | 13 +- lib/src/buttons/switch.dart | 663 ++++++++++++++++++++- lib/src/enums/control_size.dart | 25 + lib/src/theme/icon_theme.dart | 2 +- lib/src/theme/macos_colors.dart | 71 +++ pubspec.yaml | 2 +- test/buttons/switch_test.dart | 2 + test/theme/help_button_theme_test.dart | 4 +- test/theme/icon_theme_test.dart | 2 +- test/theme/popup_button_theme_test.dart | 4 +- test/theme/pulldown_button_theme_test.dart | 6 +- test/theme/push_button_theme_test.dart | 4 +- 16 files changed, 810 insertions(+), 55 deletions(-) create mode 100644 lib/src/enums/control_size.dart diff --git a/CHANGELOG.md b/CHANGELOG.md index 2c6bd4af..4a426fce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,14 @@ +## [2.0.0-beta.2] +✨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. + +🔄 Updated 🔄 +* Some previously missing elements of the `MacosColor` class have been added. + ## [2.0.0-beta.1] 🚨 Breaking Changes 🚨 -* Migrate macos_ui to [macos_window_utils](https://pub.dev/packages/macos_window_utils), which provides the following benefits: +* Migrate `macos_ui` to [macos_window_utils](https://pub.dev/packages/macos_window_utils), 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. diff --git a/README.md b/README.md index 4c8f9447..8164314d 100644 --- a/README.md +++ b/README.md @@ -657,26 +657,34 @@ 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. -| On | Off | -| ------------------------------------------ | ------------------------------------------ | -| | | +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` + +| 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 diff --git a/example/lib/pages/buttons_page.dart b/example/lib/pages/buttons_page.dart index 6c373f5f..befbaef1 100644 --- a/example/lib/pages/buttons_page.dart +++ b/example/lib/pages/buttons_page.dart @@ -217,11 +217,32 @@ class _ButtonsPageState extends State { const SizedBox(height: 20), const Text('MacosSwitch'), const SizedBox(height: 8), - MacosSwitch( - value: switchValue, - onChanged: (value) { - setState(() => switchValue = value); - }, + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + MacosSwitch( + value: switchValue, + size: ControlSize.mini, + onChanged: (value) { + setState(() => switchValue = value); + }, + ), + const SizedBox(width: 16.0), + MacosSwitch( + value: switchValue, + size: ControlSize.small, + onChanged: (value) { + setState(() => switchValue = value); + }, + ), + const SizedBox(width: 16.0), + MacosSwitch( + value: switchValue, + onChanged: (value) { + setState(() => switchValue = value); + }, + ), + ], ), const SizedBox(height: 20), const Text('MacosPulldownButton'), diff --git a/example/pubspec.lock b/example/pubspec.lock index bf64e601..931e90c9 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -97,7 +97,7 @@ packages: path: ".." relative: true source: path - version: "2.0.0-beta.1" + version: "2.0.0-beta.2" macos_window_utils: dependency: transitive description: diff --git a/lib/macos_ui.dart b/lib/macos_ui.dart index 4d3ac251..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'; @@ -82,8 +88,3 @@ export 'src/theme/search_field_theme.dart'; export 'src/theme/time_picker_theme.dart'; export 'src/theme/tooltip_theme.dart'; export 'src/theme/typography.dart'; -export 'src/layout/wallpaper_tinted_area.dart'; -export 'src/macos_window_utils_config.dart'; - -export 'package:macos_window_utils/macos_window_utils.dart'; -export 'package:macos_window_utils/macos/ns_window_delegate.dart'; 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/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/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/pubspec.yaml b/pubspec.yaml index 02ae5581..c52118c0 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: 2.0.0-beta.1 +version: 2.0.0-beta.2 homepage: "https://macosui.dev" repository: "https://github.com/GroovinChip/macos_ui" 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/theme/help_button_theme_test.dart b/test/theme/help_button_theme_test.dart index 98556548..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)', ], ); }); diff --git a/test/theme/icon_theme_test.dart b/test/theme/icon_theme_test.dart index b45e4de6..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', ], diff --git a/test/theme/popup_button_theme_test.dart b/test/theme/popup_button_theme_test.dart index 730d86a6..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)', ], ); diff --git a/test/theme/pulldown_button_theme_test.dart b/test/theme/pulldown_button_theme_test.dart index 0ef34561..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)', ], ); }); diff --git a/test/theme/push_button_theme_test.dart b/test/theme/push_button_theme_test.dart index a910d981..a26619f8 100644 --- a/test/theme/push_button_theme_test.dart +++ b/test/theme/push_button_theme_test.dart @@ -46,8 +46,8 @@ void main() { expect( description, [ - 'color: Color(0xff0433ff)', - 'disabledColor: Color(0xff8e8e93)', + 'color: MacosColor(0xff0433ff)', + 'disabledColor: MacosColor(0xff8e8e93)', 'secondaryColor: Color(0x19000000)', ], ); From 46eb9caca2e8f5f5ae0456a4bac921ba4e9c79c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20K=C5=82os?= Date: Mon, 3 Jul 2023 21:22:04 +0200 Subject: [PATCH 021/151] fix: UX of the click on the calendar elements in `MacosDatePicker` (#417) fix: UX of the click on the calendar elements --- CHANGELOG.md | 4 ++++ example/pubspec.lock | 2 +- lib/src/selectors/date_picker.dart | 1 + pubspec.yaml | 2 +- 4 files changed, 7 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4a426fce..57d8d057 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## [2.0.0-beta.3] +🛠️ Fixed 🛠️ +* Better UX of the click on the calendar elements in `MacosDatePicker` + ## [2.0.0-beta.2] ✨New ✨ * `MacosSwitch` has been completely rewritten and now matches the native macOS switch in appearance and behavior. diff --git a/example/pubspec.lock b/example/pubspec.lock index 931e90c9..ce6e9a1a 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -97,7 +97,7 @@ packages: path: ".." relative: true source: path - version: "2.0.0-beta.2" + version: "2.0.0-beta.3" macos_window_utils: dependency: transitive description: diff --git a/lib/src/selectors/date_picker.dart b/lib/src/selectors/date_picker.dart index 348fec85..d5145dc4 100644 --- a/lib/src/selectors/date_picker.dart +++ b/lib/src/selectors/date_picker.dart @@ -526,6 +526,7 @@ class _MacosDatePickerState extends State { } Widget dayWidget = GestureDetector( + behavior: HitTestBehavior.opaque, onTap: () { setState(() { _isDaySelected = true; diff --git a/pubspec.yaml b/pubspec.yaml index c52118c0..ceb9b29b 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: 2.0.0-beta.2 +version: 2.0.0-beta.3 homepage: "https://macosui.dev" repository: "https://github.com/GroovinChip/macos_ui" From 32fa64cb0cc75b07b6fccfb60446e35bc5b37721 Mon Sep 17 00:00:00 2001 From: GroovinChip Date: Mon, 3 Jul 2023 15:24:03 -0400 Subject: [PATCH 022/151] fix minor formatting fix --- lib/src/buttons/pulldown_button.dart | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/src/buttons/pulldown_button.dart b/lib/src/buttons/pulldown_button.dart index 51b783f4..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 = - EdgeInsets.symmetric(horizontal: 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) From 1df03cbc11b72524bb6a084fecd49c7bc4ff3425 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20K=C5=82os?= Date: Tue, 4 Jul 2023 15:48:48 +0200 Subject: [PATCH 023/151] 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 --- CHANGELOG.md | 3 + lib/src/selectors/date_picker.dart | 35 ++++++++++- test/selectors/date_picker_test.dart | 92 ++++++++++++++++++++++++++++ 3 files changed, 127 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 57d8d057..3ad855e5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,7 @@ ## [2.0.0-beta.3] +✨ New ✨ +* Added support for `startWeekOnMonday` to `MacosDatePicker`. + 🛠️ Fixed 🛠️ * Better UX of the click on the calendar elements in `MacosDatePicker` diff --git a/lib/src/selectors/date_picker.dart b/lib/src/selectors/date_picker.dart index d5145dc4..c945bf0b 100644 --- a/lib/src/selectors/date_picker.dart +++ b/lib/src/selectors/date_picker.dart @@ -44,6 +44,7 @@ class MacosDatePicker extends StatefulWidget { this.style = DatePickerStyle.combined, required this.onDateChanged, this.initialDate, + this.startWeekOnMonday, }); /// The [DatePickerStyle] to use. @@ -59,6 +60,14 @@ class MacosDatePicker extends StatefulWidget { /// Defaults to `DateTime.now()`. final DateTime? initialDate; + /// 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(); } @@ -153,12 +162,23 @@ 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) { + + // 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 = _narrowWeekdays[i]; result.add( ExcludeSemantics( @@ -170,7 +190,6 @@ class _MacosDatePickerState extends State { ), ), ); - if (i == (localizations.firstDayOfWeekIndex - 1) % 7) break; } return result; } @@ -474,9 +493,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 = []; diff --git a/test/selectors/date_picker_test.dart b/test/selectors/date_picker_test.dart index b0e50b5d..4eb263b2 100644 --- a/test/selectors/date_picker_test.dart +++ b/test/selectors/date_picker_test.dart @@ -308,4 +308,96 @@ void main() { }, ); }); + + 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'); + }, + ); } From 37db0c3db35e6dc0bbe990c3f5c47405c6094ae9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20K=C5=82os?= Date: Tue, 4 Jul 2023 15:51:42 +0200 Subject: [PATCH 024/151] feat: Added support for `dateFormat` to `MacosDatePicker` (#415) Co-authored-by: Reuben Turner --- CHANGELOG.md | 1 + README.md | 12 +++ lib/src/selectors/date_picker.dart | 133 +++++++++++++++++++-------- test/selectors/date_picker_test.dart | 53 +++++++++++ 4 files changed, 159 insertions(+), 40 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3ad855e5..4986bc13 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ ## [2.0.0-beta.3] ✨ New ✨ +* Added support for `dateFormat` to `MacosDatePicker`. * Added support for `startWeekOnMonday` to `MacosDatePicker`. 🛠️ Fixed 🛠️ diff --git a/README.md b/README.md index 8164314d..97f5475b 100644 --- a/README.md +++ b/README.md @@ -974,6 +974,18 @@ There are three styles of `MacosDatePickers`: calendar-like interface to select a date. * `combined`: provides both `textual` and `graphical` interfaces. +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/lib/src/selectors/date_picker.dart b/lib/src/selectors/date_picker.dart index c945bf0b..149cb72c 100644 --- a/lib/src/selectors/date_picker.dart +++ b/lib/src/selectors/date_picker.dart @@ -44,6 +44,7 @@ class MacosDatePicker extends StatefulWidget { this.style = DatePickerStyle.combined, required this.onDateChanged, this.initialDate, + this.dateFormat, this.startWeekOnMonday, }); @@ -60,6 +61,19 @@ class MacosDatePicker extends StatefulWidget { /// Defaults to `DateTime.now()`. final DateTime? initialDate; + /// 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. /// @@ -194,6 +208,84 @@ class _MacosDatePickerState extends State { 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, @@ -214,46 +306,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(), ), ), ), diff --git a/test/selectors/date_picker_test.dart b/test/selectors/date_picker_test.dart index 4eb263b2..0d006b89 100644 --- a/test/selectors/date_picker_test.dart +++ b/test/selectors/date_picker_test.dart @@ -82,6 +82,59 @@ 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 { From 044af12cd130b3f9f86ea805cd9dcc5c42797055 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20K=C5=82os?= Date: Tue, 4 Jul 2023 19:38:15 +0200 Subject: [PATCH 025/151] 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 --- CHANGELOG.md | 1 + README.md | 4 ++ lib/src/selectors/date_picker.dart | 47 +++++++++++++++------- lib/src/utils.dart | 31 -------------- test/selectors/date_picker_test.dart | 60 ++++++++++++++++++++++++++++ 5 files changed, 98 insertions(+), 45 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4986bc13..9fca81d1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ ## [2.0.0-beta.3] ✨ New ✨ +* Added support for `weekdayAbbreviations` and `monthAbbreviations` to `MacosDatePicker`. * Added support for `dateFormat` to `MacosDatePicker`. * Added support for `startWeekOnMonday` to `MacosDatePicker`. diff --git a/README.md b/README.md index 97f5475b..19a5ea75 100644 --- a/README.md +++ b/README.md @@ -974,6 +974,10 @@ 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: diff --git a/lib/src/selectors/date_picker.dart b/lib/src/selectors/date_picker.dart index 149cb72c..a3e20fc0 100644 --- a/lib/src/selectors/date_picker.dart +++ b/lib/src/selectors/date_picker.dart @@ -44,6 +44,31 @@ 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, }); @@ -61,6 +86,12 @@ 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): @@ -87,18 +118,6 @@ class MacosDatePicker extends StatefulWidget { } 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; @@ -193,7 +212,7 @@ class _MacosDatePickerState extends State { } for (int i = firstDayOfWeekIndex; result.length < 7; i = (i + 1) % 7) { - final weekday = _narrowWeekdays[i]; + final weekday = widget.weekdayAbbreviations[i]; result.add( ExcludeSemantics( child: Center( @@ -398,7 +417,7 @@ class _MacosDatePickerState extends State { children: [ Expanded( child: Text( - '${intToMonthAbbr(_selectedMonth)} $_selectedYear', + '${widget.monthAbbreviations[_selectedMonth - 1]} $_selectedYear', style: const TextStyle( fontSize: 13.0, fontWeight: FontWeight.w700, diff --git a/lib/src/utils.dart b/lib/src/utils.dart index a986d412..b99c1796 100644 --- a/lib/src/utils.dart +++ b/lib/src/utils.dart @@ -59,37 +59,6 @@ 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); diff --git a/test/selectors/date_picker_test.dart b/test/selectors/date_picker_test.dart index 0d006b89..e5f4f495 100644 --- a/test/selectors/date_picker_test.dart +++ b/test/selectors/date_picker_test.dart @@ -360,6 +360,66 @@ 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( From 057b1b8230da609e91474d53ada939832b0b459a Mon Sep 17 00:00:00 2001 From: Reuben Turner Date: Wed, 5 Jul 2023 12:43:12 -0400 Subject: [PATCH 026/151] 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 --- CHANGELOG.md | 4 ++++ example/pubspec.lock | 2 +- lib/src/layout/window.dart | 6 ++++-- pubspec.yaml | 2 +- 4 files changed, 10 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9fca81d1..627308cb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## [2.0.0-beta.4] +🛠️ Fixed 🛠️ +* `ToolBar`s in use where a `SideBar` is not present will now have their title's avoid the traffic lights (native window controls). + ## [2.0.0-beta.3] ✨ New ✨ * Added support for `weekdayAbbreviations` and `monthAbbreviations` to `MacosDatePicker`. diff --git a/example/pubspec.lock b/example/pubspec.lock index ce6e9a1a..727c8aac 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -97,7 +97,7 @@ packages: path: ".." relative: true source: path - version: "2.0.0-beta.3" + version: "2.0.0-beta.4" macos_window_utils: dependency: transitive description: diff --git a/lib/src/layout/window.dart b/lib/src/layout/window.dart index 3171decc..4d8b4cf5 100644 --- a/lib/src/layout/window.dart +++ b/lib/src/layout/window.dart @@ -226,8 +226,10 @@ class _MacosWindowState extends State { final height = constraints.maxHeight; final isAtBreakpoint = width <= (sidebar?.windowBreakpoint ?? 0); final isAtEndBreakpoint = width <= (endSidebar?.windowBreakpoint ?? 0); - final canShowSidebar = _showSidebar && !isAtBreakpoint; - final canShowEndSidebar = _showEndSidebar && !isAtEndBreakpoint; + final canShowSidebar = + _showSidebar && !isAtBreakpoint && sidebar != null; + final canShowEndSidebar = + _showEndSidebar && !isAtEndBreakpoint && endSidebar != null; final visibleSidebarWidth = canShowSidebar ? _sidebarWidth : 0.0; final visibleEndSidebarWidth = canShowEndSidebar ? _endSidebarWidth : 0.0; diff --git a/pubspec.yaml b/pubspec.yaml index ceb9b29b..740361b5 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: 2.0.0-beta.3 +version: 2.0.0-beta.4 homepage: "https://macosui.dev" repository: "https://github.com/GroovinChip/macos_ui" From 2b9c084363d83d352f3e2289015a785b298af622 Mon Sep 17 00:00:00 2001 From: Reuben Turner Date: Wed, 5 Jul 2023 16:50:23 -0400 Subject: [PATCH 027/151] 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 --- .github/workflows/dart_code_metrics.yaml | 14 +- CHANGELOG.md | 10 + README.md | 15 +- analysis_options.yaml | 2 - example/lib/pages/buttons_page.dart | 461 ++++++++++++++++++++--- example/lib/pages/dialogs_page.dart | 32 +- example/pubspec.lock | 2 +- lib/src/buttons/push_button.dart | 179 ++++++--- lib/src/theme/macos_theme.dart | 4 +- pubspec.lock | 120 ------ pubspec.yaml | 3 +- test/buttons/push_button_test.dart | 10 +- test/theme/push_button_theme_test.dart | 8 +- 13 files changed, 578 insertions(+), 282 deletions(-) diff --git a/.github/workflows/dart_code_metrics.yaml b/.github/workflows/dart_code_metrics.yaml index 16194aed..1dd9587a 100644 --- a/.github/workflows/dart_code_metrics.yaml +++ b/.github/workflows/dart_code_metrics.yaml @@ -9,10 +9,14 @@ jobs: - uses: actions/checkout@v3 - name: Run Dart Code Metrics - uses: dart-code-checker/dart-code-metrics-action@v4.0.0 + uses: subosito/flutter-action@v2 + with: + channel: stable + + - name: Install dependencies + 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/CHANGELOG.md b/CHANGELOG.md index 627308cb..919e1927 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,13 @@ +## [2.0.0-beta.5] +🚨 Breaking Changes 🚨 +* `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`. + +🔄 Updated 🔄 +* `PushButton`'s secondary and disabled colors more closely match their native counterparts. + ## [2.0.0-beta.4] 🛠️ Fixed 🛠️ * `ToolBar`s in use where a `SideBar` is not present will now have their title's avoid the traffic lights (native window controls). diff --git a/README.md b/README.md index 19a5ea75..cf586f34 100644 --- a/README.md +++ b/README.md @@ -632,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'); }, diff --git a/analysis_options.yaml b/analysis_options.yaml index 74448811..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 diff --git a/example/lib/pages/buttons_page.dart b/example/lib/pages/buttons_page.dart index befbaef1..3bf5fe5d 100644 --- a/example/lib/pages/buttons_page.dart +++ b/example/lib/pages/buttons_page.dart @@ -146,71 +146,410 @@ class _ButtonsPageState extends State { ], ), const SizedBox(height: 20), - const Text('PushButton'), + const Text('Primary PushButton'), const SizedBox(height: 8), - Row( - mainAxisAlignment: MainAxisAlignment.center, + Column( + mainAxisSize: MainAxisSize.min, children: [ - PushButton( - buttonSize: ButtonSize.large, - child: const Text('Large'), - onPressed: () { - MacosWindowScope.of(context).toggleSidebar(); - }, + Row( + mainAxisAlignment: MainAxisAlignment.center, + 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: 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'), + ); + }, + ), + ], + ); + }, + ), + ); + }, + ), + 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.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(); - }, - ), - ); - }, - ), - ResizablePane( - minSize: 180, - startSize: 200, - windowBreakpoint: 700, - resizableSide: ResizableSide.left, - builder: (_, __) { - return const Center( - child: Text('Resizable Pane'), - ); - }, - ), - ], - ); - }, - ), - ); - }, + ], + ), + const SizedBox(height: 8), + const Text('Secondary PushButton'), + const SizedBox(height: 8), + Column( + mainAxisSize: MainAxisSize.min, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.center, + 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(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'), + ); + }, + ), + ], + ); + }, + ), + ); + }, + ), + 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(width: 20), - PushButton( - buttonSize: ButtonSize.large, - isSecondary: true, - child: const Text('Secondary'), - onPressed: () { - MacosWindowScope.of(context).toggleSidebar(); - }, + ], + ), + const SizedBox(height: 8), + const Text('Disabled Primary PushButton'), + const SizedBox(height: 8), + const Column( + mainAxisSize: MainAxisSize.min, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.center, + 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: 8), + const Text('Disabled Secondary PushButton'), + const SizedBox(height: 8), + const Column( + mainAxisSize: MainAxisSize.min, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.center, + 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'), + ), + ], ), ], ), diff --git a/example/lib/pages/dialogs_page.dart b/example/lib/pages/dialogs_page.dart index b7645978..9046b61c 100644 --- a/example/lib/pages/dialogs_page.dart +++ b/example/lib/pages/dialogs_page.dart @@ -36,7 +36,7 @@ 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, @@ -52,7 +52,7 @@ class _DialogsPageState extends State { ), //horizontalActions: false, primaryButton: PushButton( - buttonSize: ButtonSize.large, + controlSize: ControlSize.regular, onPressed: Navigator.of(context).pop, child: const Text('Primary'), ), @@ -61,7 +61,7 @@ class _DialogsPageState extends State { ), const SizedBox(height: 16), PushButton( - buttonSize: ButtonSize.large, + controlSize: ControlSize.regular, child: const Text('Show Alert Dialog 2'), onPressed: () => showMacosAlertDialog( context: context, @@ -78,13 +78,13 @@ class _DialogsPageState extends State { ), //horizontalActions: false, primaryButton: PushButton( - buttonSize: ButtonSize.large, + controlSize: ControlSize.regular, onPressed: Navigator.of(context).pop, child: const Text('Primary'), ), secondaryButton: PushButton( - buttonSize: ButtonSize.large, - isSecondary: true, + controlSize: ControlSize.regular, + secondary: true, onPressed: Navigator.of(context).pop, child: const Text('Secondary'), ), @@ -93,7 +93,7 @@ class _DialogsPageState extends State { ), const SizedBox(height: 16), PushButton( - buttonSize: ButtonSize.large, + controlSize: ControlSize.regular, child: const Text('Show Alert Dialog 3'), onPressed: () => showMacosAlertDialog( context: context, @@ -110,13 +110,13 @@ class _DialogsPageState extends State { ), horizontalActions: false, primaryButton: PushButton( - buttonSize: ButtonSize.large, + controlSize: ControlSize.regular, onPressed: Navigator.of(context).pop, child: const Text('Primary'), ), secondaryButton: PushButton( - buttonSize: ButtonSize.large, - isSecondary: true, + controlSize: ControlSize.regular, + secondary: true, onPressed: Navigator.of(context).pop, child: const Text('Secondary'), ), @@ -125,7 +125,7 @@ class _DialogsPageState extends State { ), const SizedBox(height: 16), PushButton( - buttonSize: ButtonSize.large, + controlSize: ControlSize.regular, child: const Text('Show Alert Dialog 4'), onPressed: () => showMacosAlertDialog( context: context, @@ -143,13 +143,13 @@ class _DialogsPageState extends State { ), horizontalActions: false, primaryButton: PushButton( - buttonSize: ButtonSize.large, + controlSize: ControlSize.regular, onPressed: Navigator.of(context).pop, child: const Text('Primary'), ), secondaryButton: PushButton( - buttonSize: ButtonSize.large, - isSecondary: true, + controlSize: ControlSize.regular, + secondary: true, onPressed: Navigator.of(context).pop, child: const Text('Secondary'), ), @@ -159,7 +159,7 @@ class _DialogsPageState extends State { ), const SizedBox(height: 16), PushButton( - buttonSize: ButtonSize.large, + controlSize: ControlSize.regular, child: const Text('Show sheet'), onPressed: () { showMacosSheet( @@ -249,7 +249,7 @@ class DemoSheet extends StatelessWidget { ), const Spacer(), PushButton( - buttonSize: ButtonSize.large, + controlSize: ControlSize.regular, child: const Text('Get started'), onPressed: () => Navigator.of(context).pop(), ), diff --git a/example/pubspec.lock b/example/pubspec.lock index 727c8aac..44b2f9a3 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -97,7 +97,7 @@ packages: path: ".." relative: true source: path - version: "2.0.0-beta.4" + version: "2.0.0-beta.5" macos_window_utils: dependency: transitive description: diff --git a/lib/src/buttons/push_button.dart b/lib/src/buttons/push_button.dart index 6f759c1d..4fa2258a 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,7 +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 = + final baseStyle = theme.typography.headline.copyWith(color: foregroundColor); return MouseRegion( @@ -272,25 +339,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/theme/macos_theme.dart b/lib/src/theme/macos_theme.dart index 78508f00..6de99369 100644 --- a/lib/src/theme/macos_theme.dart +++ b/lib/src/theme/macos_theme.dart @@ -225,8 +225,8 @@ class MacosThemeData with Diagnosticable { pushButtonTheme ??= PushButtonThemeData( color: primaryColor, secondaryColor: isDark - ? const Color.fromRGBO(56, 56, 56, 1.0) - : const Color.fromRGBO(218, 218, 223, 1.0), + ? 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/pubspec.lock b/pubspec.lock index 4408d237..f80cd1d5 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -17,22 +17,6 @@ packages: url: "https://pub.dev" source: hosted version: "5.11.1" - 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" args: dependency: transitive description: @@ -105,38 +89,6 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.3" - 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: "1dc1fa763b73ed52147bd91b015d81903edc3f227b77b1672fcddba43390ed18" - url: "https://pub.dev" - source: hosted - version: "5.7.5" - dart_code_metrics_presets: - dependency: transitive - description: - name: dart_code_metrics_presets - sha256: "22e27f98e8c7d8b11cca43d2656a822935280747050ae65e8cd03c52d09c0d1c" - url: "https://pub.dev" - source: hosted - version: "1.7.0" - dart_style: - dependency: transitive - description: - name: dart_style - sha256: f4f1f73ab3fd2afcbcca165ee601fe980d966af6a21b5970c6c9376955c528ad - url: "https://pub.dev" - source: hosted - version: "2.3.1" fake_async: dependency: transitive description: @@ -187,22 +139,6 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.1" - html: - dependency: transitive - description: - name: html - sha256: "58e3491f7bf0b6a4ea5110c0c688877460d1a6366731155c4a4580e7ded773e8" - url: "https://pub.dev" - source: hosted - version: "0.15.3" - http: - dependency: transitive - description: - name: http - sha256: "5895291c13fa8a3bd82e76d5627f69e0d85ca6a30dcac95c4ea19a5d555879c2" - url: "https://pub.dev" - source: hosted - version: "0.13.6" http_multi_server: dependency: transitive description: @@ -235,14 +171,6 @@ packages: url: "https://pub.dev" source: hosted version: "0.6.7" - json_annotation: - dependency: transitive - description: - name: json_annotation - sha256: b10a7b2ff83d83c777edba3c6a0f97045ddadd56c944e1a23a3fdf43a1bf4467 - url: "https://pub.dev" - source: hosted - version: "4.8.1" lints: dependency: transitive description: @@ -331,22 +259,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.8.3" - petitparser: - dependency: transitive - description: - name: petitparser - sha256: cb3798bef7fc021ac45b308f4b51208a152792445cce0448c9a4ba5879dd8750 - url: "https://pub.dev" - source: hosted - version: "5.4.0" - platform: - dependency: transitive - description: - name: platform - sha256: "4a451831508d7d6ca779f7ac6e212b4023dd5a7d08a27a63da33756410e32b76" - url: "https://pub.dev" - source: hosted - version: "3.1.0" pool: dependency: transitive description: @@ -355,14 +267,6 @@ 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: @@ -371,14 +275,6 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.4" - pub_updater: - dependency: transitive - description: - name: pub_updater - sha256: "05ae70703e06f7fdeb05f7f02dd680b8aad810e87c756a618f33e1794635115c" - url: "https://pub.dev" - source: hosted - version: "0.3.0" shelf: dependency: transitive description: @@ -504,14 +400,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.2" - uuid: - dependency: transitive - description: - name: uuid - sha256: "648e103079f7c64a36dc7d39369cabb358d377078a051d6ae2ad3aa539519313" - url: "https://pub.dev" - source: hosted - version: "3.0.7" vector_math: dependency: transitive description: @@ -552,14 +440,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.2.0" - xml: - dependency: transitive - description: - name: xml - sha256: "5bc72e1e45e941d825fd7468b9b4cc3b9327942649aeb6fc5cdbf135f0a86e84" - url: "https://pub.dev" - source: hosted - version: "6.3.0" yaml: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 740361b5..bed7d3d6 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: 2.0.0-beta.4 +version: 2.0.0-beta.5 homepage: "https://macosui.dev" repository: "https://github.com/GroovinChip/macos_ui" @@ -16,7 +16,6 @@ dependencies: dev_dependencies: flutter_test: sdk: flutter - dart_code_metrics: ^5.7.5 flutter_lints: ^2.0.2 mocktail: ^0.3.0 diff --git a/test/buttons/push_button_test.dart b/test/buttons/push_button_test.dart index 3843c6a2..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'), ); @@ -61,7 +61,7 @@ void main() { ContentArea( builder: (context, _) { return PushButton( - buttonSize: ButtonSize.small, + controlSize: ControlSize.regular, key: pushButtonKey, onPressed: mockOnTapCancelFunction.handler, child: const Text('Push me'), @@ -84,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); @@ -96,7 +96,7 @@ void main() { expect( description, [ - 'buttonSize: small', + 'controlSize: regular', 'color: null', 'disabledColor: null', 'pressedOpacity: 0.4', @@ -104,7 +104,7 @@ void main() { 'semanticLabel: null', 'borderRadius: BorderRadius.circular(4.0)', 'disabled', - 'isSecondary: null', + 'secondary: null', ], ); }); diff --git a/test/theme/push_button_theme_test.dart b/test/theme/push_button_theme_test.dart index a26619f8..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', () { @@ -65,7 +65,7 @@ void main() { builder: (context, _) { capturedContext = context; return const PushButton( - buttonSize: ButtonSize.small, + controlSize: ControlSize.regular, child: Text('Push me'), ); }, @@ -78,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); }); }); } From d85ab84446e579c7122d570c5b6de69e3f228084 Mon Sep 17 00:00:00 2001 From: Reuben Turner Date: Wed, 5 Jul 2023 17:41:06 -0400 Subject: [PATCH 028/151] fix: checkbox appearance (#448) * fix: checkbox appearance * fix: checkbox tests --- CHANGELOG.md | 4 ++ example/lib/pages/buttons_page.dart | 7 ++++ example/pubspec.lock | 2 +- lib/src/buttons/checkbox.dart | 61 +++++++++++++++++++++++------ pubspec.yaml | 2 +- test/buttons/checkbox_test.dart | 2 +- 6 files changed, 63 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 919e1927..6ab311b3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## [2.0.0-beta.6] +🔄 Updated 🔄 +* `MacosCheckbox` appearance more closely matches its native counterpart. + ## [2.0.0-beta.5] 🚨 Breaking Changes 🚨 * `PushButton` has been updated to support the `ControlSize` enum. diff --git a/example/lib/pages/buttons_page.dart b/example/lib/pages/buttons_page.dart index 3bf5fe5d..7c480ce9 100644 --- a/example/lib/pages/buttons_page.dart +++ b/example/lib/pages/buttons_page.dart @@ -734,6 +734,13 @@ class _ButtonsPageState extends State { }).toList(), ), const SizedBox(height: 20), + MacosCheckbox( + value: switchValue, + onChanged: (value) { + setState(() => switchValue = value); + }, + ), + const SizedBox(height: 20), Row( mainAxisAlignment: MainAxisAlignment.center, children: [ diff --git a/example/pubspec.lock b/example/pubspec.lock index 44b2f9a3..2b2ea121 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -97,7 +97,7 @@ packages: path: ".." relative: true source: path - version: "2.0.0-beta.5" + version: "2.0.0-beta.6" macos_window_utils: dependency: transitive description: diff --git a/lib/src/buttons/checkbox.dart b/lib/src/buttons/checkbox.dart index 1ad46d62..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, @@ -108,18 +108,55 @@ class MacosCheckbox extends StatelessWidget { ), 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: const BorderRadius.all(Radius.circular(4.0)), - ), child: Icon( isDisabled || value == false ? null diff --git a/pubspec.yaml b/pubspec.yaml index bed7d3d6..33d64578 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: 2.0.0-beta.5 +version: 2.0.0-beta.6 homepage: "https://macosui.dev" repository: "https://github.com/GroovinChip/macos_ui" 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)', From 331895a94fb04ebf4258a2fe0752d9c01ead1af8 Mon Sep 17 00:00:00 2001 From: Reuben Turner Date: Sun, 9 Jul 2023 20:54:18 -0400 Subject: [PATCH 029/151] 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 --- CHANGELOG.md | 9 + README.md | 8 +- example/lib/main.dart | 96 +- example/lib/pages/buttons_page.dart | 1501 ++++++++--------- example/lib/pages/colors_page.dart | 23 +- example/lib/pages/dialogs_page.dart | 93 +- example/lib/pages/fields_page.dart | 23 +- example/lib/pages/indicators_page.dart | 23 +- example/lib/pages/resizable_pane_page.dart | 90 + example/lib/pages/selectors_page.dart | 23 +- example/lib/pages/tabview_page.dart | 25 +- .../Flutter/GeneratedPluginRegistrant.swift | 2 + example/macos/Podfile.lock | 7 + .../macos/Runner/DebugProfile.entitlements | 2 + example/pubspec.lock | 150 +- example/pubspec.yaml | 7 +- lib/src/dialogs/macos_alert_dialog.dart | 49 +- lib/src/theme/typography.dart | 6 + pubspec.yaml | 2 +- 19 files changed, 1213 insertions(+), 926 deletions(-) create mode 100644 example/lib/pages/resizable_pane_page.dart diff --git a/CHANGELOG.md b/CHANGELOG.md index 6ab311b3..345ceb9c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,12 @@ +## [2.0.0-beta.7] +✨ New ✨ +* You can now call `MacosTypography.of(context)` as a shorthand for retrieving the typography used in your `MacosTheme`. + +🔄 Updated 🔄 +* `MacosAlertDialog` now defines `primaryButton` and `secondaryButton` to be of type `PushButton`. +* `MacosAlertDialog` now requires that `primaryButton` and `secondaryButton` to have `controlSize`s of `ControlSize.large`. +* `MacosAlertDialog` docs now suggest that `appIcon` should be of size 64x64. + ## [2.0.0-beta.6] 🔄 Updated 🔄 * `MacosCheckbox` appearance more closely matches its native counterpart. diff --git a/README.md b/README.md index cf586f34..b9a911d1 100644 --- a/README.md +++ b/README.md @@ -703,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, @@ -713,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: () {}, ), diff --git a/example/lib/main.dart b/example/lib/main.dart index cba22164..3b7d2ed2 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -3,6 +3,7 @@ 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'; @@ -55,33 +56,21 @@ 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 pageBuilders = [ - (bool isVisible) => CupertinoTabView( - builder: (_) => const ButtonsPage(), - ), - (bool isVisible) => const IndicatorsPage(), - (bool isVisible) => const FieldsPage(), - (bool isVisible) => const ColorsPage(), - (bool isVisible) => const Center( - child: MacosIcon( - CupertinoIcons.add, - ), - ), - (bool isVisible) => const DialogsPage(), - (bool isVisible) => const ToolbarPage(), - (bool isVisible) => SliverToolbarPage( - isVisible: isVisible, - ), - (bool isVisible) => const TabViewPage(), - (bool isVisible) => const SelectorsPage(), + (_) => CupertinoTabView(builder: (_) => const ButtonsPage()), + (_) => const IndicatorsPage(), + (_) => const FieldsPage(), + (_) => const ColorsPage(), + (_) => const DialogsPage(), + (_) => const ToolbarPage(), + (isVisible) => SliverToolbarPage(isVisible: isVisible), + (_) => const TabViewPage(), + (_) => const ResizablePanePage(), + (_) => const SelectorsPage(), ]; @override @@ -152,7 +141,7 @@ class _WidgetGalleryState extends State { break; case 'Dialogs and Sheets': setState(() { - pageIndex = 5; + pageIndex = 4; searchFieldController.clear(); }); break; @@ -162,12 +151,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(); } @@ -179,6 +174,7 @@ class _WidgetGalleryState extends State { SearchResultItem('Colors'), SearchResultItem('Dialogs and Sheets'), SearchResultItem('Toolbar'), + SearchResultItem('ResizablePane'), SearchResultItem('Selectors'), ], ), @@ -189,17 +185,14 @@ class _WidgetGalleryState extends State { onChanged: (i) => 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', @@ -207,7 +200,7 @@ class _WidgetGalleryState extends State { ), label: Text('Indicators'), ), - const SidebarItem( + SidebarItem( leading: MacosImageIcon( AssetImage( 'assets/sf_symbols/character_cursor_ibeam_2x.png', @@ -216,36 +209,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', @@ -269,13 +242,16 @@ 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'), ), diff --git a/example/lib/pages/buttons_page.dart b/example/lib/pages/buttons_page.dart index 7c480ce9..d26501c3 100644 --- a/example/lib/pages/buttons_page.dart +++ b/example/lib/pages/buttons_page.dart @@ -1,5 +1,6 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; +import 'package:google_fonts/google_fonts.dart'; import 'package:macos_ui/macos_ui.dart'; import 'package:provider/provider.dart'; @@ -59,779 +60,715 @@ 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('Primary PushButton'), - const SizedBox(height: 8), - Column( - mainAxisSize: MainAxisSize.min, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.center, - 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: 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'), - ); - }, - ), - ], - ); - }, - ), - ); - }, - ), - 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(height: 8), - const Text('Secondary PushButton'), - const SizedBox(height: 8), - Column( - mainAxisSize: MainAxisSize.min, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.center, - 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(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'), - ); - }, - ), - ], - ); - }, - ), - ); - }, - ), - 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: 8), - const Text('Disabled Primary PushButton'), - const SizedBox(height: 8), - const Column( - mainAxisSize: MainAxisSize.min, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.center, - 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: 8), - const Text('Disabled Secondary PushButton'), - const SizedBox(height: 8), - const Column( - mainAxisSize: MainAxisSize.min, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.center, - 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: 20), - const Text('MacosSwitch'), - const SizedBox(height: 8), - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - MacosSwitch( - value: switchValue, - size: ControlSize.mini, - onChanged: (value) { - setState(() => switchValue = value); + 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: 16.0), - MacosSwitch( - value: switchValue, - size: ControlSize.small, - onChanged: (value) { - setState(() => switchValue = value); + ); + }, + ), + 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'), + ); + }, + ), + ], + ); }, ), - const SizedBox(width: 16.0), - MacosSwitch( - value: switchValue, - onChanged: (value) { - setState(() => switchValue = value); + ); + }, + ), + 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(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(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'), + ); + }, + ), + ], ); - }).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), - MacosCheckbox( - value: switchValue, - onChanged: (value) { - setState(() => switchValue = value); - }, - ), - 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.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'), + ); + }, + ), + ], + ); }, ), - ], - ), - const SizedBox(height: 8), - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Text('Dark Theme'), - const SizedBox(width: 26), - MacosRadioButton( - groupValue: context.watch().mode, - value: ThemeMode.dark, - onChanged: (value) { - context.read().mode = value!; + ); + }, + ), + const SizedBox(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), + 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: 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.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: () {}, + ), + ], ), - ), - 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, + ), + ], + ), + ], + ), ); }, ), @@ -876,3 +813,57 @@ const languages = [ 'Romanian', 'Dutch' ]; + +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), + ), + ), + ); + } +} + +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/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 9046b61c..1fea4908 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( @@ -41,20 +55,14 @@ class _DialogsPageState extends State { 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( - controlSize: ControlSize.regular, + controlSize: ControlSize.large, onPressed: Navigator.of(context).pop, - child: const Text('Primary'), + child: const Text('Label'), ), ), ), @@ -66,27 +74,23 @@ class _DialogsPageState extends State { 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( - controlSize: ControlSize.regular, + controlSize: ControlSize.large, onPressed: Navigator.of(context).pop, - child: const Text('Primary'), + child: const Text('Label'), ), secondaryButton: PushButton( - controlSize: ControlSize.regular, + controlSize: ControlSize.large, secondary: true, onPressed: Navigator.of(context).pop, - child: const Text('Secondary'), + child: const Text('Label'), ), ), ), @@ -98,27 +102,23 @@ class _DialogsPageState extends State { 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( - controlSize: ControlSize.regular, + controlSize: ControlSize.large, onPressed: Navigator.of(context).pop, - child: const Text('Primary'), + child: const Text('Label'), ), secondaryButton: PushButton( - controlSize: ControlSize.regular, + controlSize: ControlSize.large, secondary: true, onPressed: Navigator.of(context).pop, - child: const Text('Secondary'), + child: const Text('Label'), ), ), ), @@ -130,25 +130,20 @@ class _DialogsPageState extends State { 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( - controlSize: ControlSize.regular, + controlSize: ControlSize.large, onPressed: Navigator.of(context).pop, child: const Text('Primary'), ), secondaryButton: PushButton( - controlSize: ControlSize.regular, + controlSize: ControlSize.large, secondary: true, onPressed: Navigator.of(context).pop, child: const Text('Secondary'), diff --git a/example/lib/pages/fields_page.dart b/example/lib/pages/fields_page.dart index 00b2aa81..ff5ae256 100644 --- a/example/lib/pages/fields_page.dart +++ b/example/lib/pages/fields_page.dart @@ -15,16 +15,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( diff --git a/example/lib/pages/indicators_page.dart b/example/lib/pages/indicators_page.dart index 1dd4caf5..55347f10 100644 --- a/example/lib/pages/indicators_page.dart +++ b/example/lib/pages/indicators_page.dart @@ -20,16 +20,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( diff --git a/example/lib/pages/resizable_pane_page.dart b/example/lib/pages/resizable_pane_page.dart new file mode 100644 index 00000000..b850a0c3 --- /dev/null +++ b/example/lib/pages/resizable_pane_page.dart @@ -0,0 +1,90 @@ +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, + ), + ], + ); + }, + ), + ResizablePane( + minSize: 180, + startSize: 200, + // windowBreakpoint: 800, + resizableSide: ResizableSide.left, + builder: (_, __) { + return const Center( + child: Text('Right Resizable Pane'), + ); + }, + ), + ], + ); + } +} diff --git a/example/lib/pages/selectors_page.dart b/example/lib/pages/selectors_page.dart index 8256a33e..9c9a8364 100644 --- a/example/lib/pages/selectors_page.dart +++ b/example/lib/pages/selectors_page.dart @@ -15,16 +15,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( 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/macos/Flutter/GeneratedPluginRegistrant.swift b/example/macos/Flutter/GeneratedPluginRegistrant.swift index b06f1179..15deecb4 100644 --- a/example/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/example/macos/Flutter/GeneratedPluginRegistrant.swift @@ -7,8 +7,10 @@ import Foundation import macos_ui import macos_window_utils +import path_provider_foundation 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")) } diff --git a/example/macos/Podfile.lock b/example/macos/Podfile.lock index 42b5eb53..97b47e1b 100644 --- a/example/macos/Podfile.lock +++ b/example/macos/Podfile.lock @@ -4,11 +4,15 @@ PODS: - FlutterMacOS - macos_window_utils (1.0.0): - FlutterMacOS + - path_provider_foundation (0.0.1): + - Flutter + - 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`) EXTERNAL SOURCES: FlutterMacOS: @@ -17,11 +21,14 @@ EXTERNAL SOURCES: :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 SPEC CHECKSUMS: FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24 macos_ui: 6229a8922cd97bafb7d9636c8eb8dfb0744183ca macos_window_utils: 933f91f64805e2eb91a5bd057cf97cd097276663 + path_provider_foundation: eaf5b3e458fc0e5fbb9940fb09980e853fe058b8 PODFILE CHECKSUM: ff0a9a3ce75ee73f200ca7e2f47745698c917ef9 diff --git a/example/macos/Runner/DebugProfile.entitlements b/example/macos/Runner/DebugProfile.entitlements index dddb8a30..c946719a 100644 --- a/example/macos/Runner/DebugProfile.entitlements +++ b/example/macos/Runner/DebugProfile.entitlements @@ -8,5 +8,7 @@ com.apple.security.network.server + com.apple.security.network.client + diff --git a/example/pubspec.lock b/example/pubspec.lock index 2b2ea121..3f05abc6 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -41,6 +41,14 @@ packages: 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: "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,15 +90,39 @@ 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" + 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: @@ -97,7 +145,7 @@ packages: path: ".." relative: true source: path - version: "2.0.0-beta.6" + version: "2.0.0-beta.7" macos_window_utils: dependency: transitive description: @@ -146,6 +194,78 @@ packages: 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: "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: @@ -207,6 +327,14 @@ packages: 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" vector_math: dependency: transitive description: @@ -215,6 +343,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: ">=3.0.0 <4.0.0" flutter: ">=3.10.0" diff --git a/example/pubspec.yaml b/example/pubspec.yaml index e68c5535..79564134 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,13 @@ dependencies: cupertino_icons: ^1.0.5 macos_ui: path: .. - provider: ^6.0.3 + provider: ^6.0.5 + google_fonts: ^5.1.0 dev_dependencies: flutter_test: sdk: flutter - flutter_lints: ^2.0.1 + flutter_lints: ^2.0.2 flutter: assets: 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/theme/typography.dart b/lib/src/theme/typography.dart index d3efbeaf..c5a9eefd 100644 --- a/lib/src/theme/typography.dart +++ b/lib/src/theme/typography.dart @@ -1,5 +1,6 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/foundation.dart'; +import 'package:macos_ui/src/theme/macos_theme.dart'; const _kDefaultFontFamily = '.AppleSystemUIFont'; @@ -230,6 +231,11 @@ 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); diff --git a/pubspec.yaml b/pubspec.yaml index 33d64578..5ce15348 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: 2.0.0-beta.6 +version: 2.0.0-beta.7 homepage: "https://macosui.dev" repository: "https://github.com/GroovinChip/macos_ui" From 7343ee33880808acfee1a58a650c52569752c393 Mon Sep 17 00:00:00 2001 From: Reuben Turner Date: Mon, 10 Jul 2023 17:09:54 -0400 Subject: [PATCH 030/151] 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 --- example/.metadata | 24 ++++- example/lib/pages/buttons_page.dart | 57 +--------- example/lib/pages/fields_page.dart | 18 ++-- example/lib/pages/indicators_page.dart | 100 +++++++++++++----- example/lib/pages/selectors_page.dart | 35 ++++-- example/lib/widgets/widget_text_title1.dart | 30 ++++++ example/lib/widgets/widget_text_title2.dart | 30 ++++++ .../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 example/macos/RunnerTests/RunnerTests.swift | 12 +++ 19 files changed, 240 insertions(+), 130 deletions(-) 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 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/pages/buttons_page.dart b/example/lib/pages/buttons_page.dart index d26501c3..80ff6cf8 100644 --- a/example/lib/pages/buttons_page.dart +++ b/example/lib/pages/buttons_page.dart @@ -1,6 +1,7 @@ +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:google_fonts/google_fonts.dart'; import 'package:macos_ui/macos_ui.dart'; import 'package:provider/provider.dart'; @@ -813,57 +814,3 @@ const languages = [ 'Romanian', 'Dutch' ]; - -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), - ), - ), - ); - } -} - -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/lib/pages/fields_page.dart b/example/lib/pages/fields_page.dart index ff5ae256..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 { @@ -43,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( @@ -95,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( @@ -136,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'), - ); - }, - ), ], ); } diff --git a/example/lib/pages/indicators_page.dart b/example/lib/pages/indicators_page.dart index 55347f10..7e8a6b20 100644 --- a/example/lib/pages/indicators_page.dart +++ b/example/lib/pages/indicators_page.dart @@ -1,3 +1,4 @@ +import 'package:example/widgets/widget_text_title1.dart'; import 'package:macos_ui/macos_ui.dart'; // ignore: implementation_imports import 'package:macos_ui/src/library.dart'; @@ -48,47 +49,98 @@ 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 WidgetTextTitle1(widgetName: 'ProgressCircle'), + Divider(color: MacosTheme.of(context).dividerColor), + 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), const RelevanceIndicator( value: 25, amount: 50, ), - const SizedBox(height: 20), - const Label( - icon: MacosIcon(CupertinoIcons.tag), - text: SelectableText('A determinate progress circle: '), - child: ProgressCircle(value: 50), - ), ], ), ); diff --git a/example/lib/pages/selectors_page.dart b/example/lib/pages/selectors_page.dart index 9c9a8364..fdc5fbeb 100644 --- a/example/lib/pages/selectors_page.dart +++ b/example/lib/pages/selectors_page.dart @@ -1,4 +1,7 @@ +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'; class SelectorsPage extends StatefulWidget { @@ -44,20 +47,40 @@ 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), + const SizedBox(height: 20), + const WidgetTextTitle1(widgetName: 'MacosColorWell'), + Divider(color: MacosTheme.of(context).dividerColor), MacosColorWell( onColorSelected: (color) => debugPrint('$color'), ), diff --git a/example/lib/widgets/widget_text_title1.dart b/example/lib/widgets/widget_text_title1.dart new file mode 100644 index 00000000..d7dafc90 --- /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), + ), + ), + ); + } +} \ No newline at end of file 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/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 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 literal 14800 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 vNcLuCM3Rj?iBw@67X_p?_CF#jIyB-s Date: Tue, 11 Jul 2023 14:39:19 -0400 Subject: [PATCH 031/151] 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 --- .github/workflows/dart_code_metrics.yaml | 4 +- CHANGELOG.md | 7 + example/lib/main.dart | 6 + example/lib/pages/typography_page.dart | 394 +++++++++++++++++++++++ example/pubspec.lock | 2 +- lib/src/selectors/date_picker.dart | 4 +- lib/src/theme/macos_theme.dart | 5 +- lib/src/theme/typography.dart | 149 ++++++++- pubspec.yaml | 2 +- 9 files changed, 562 insertions(+), 11 deletions(-) create mode 100644 example/lib/pages/typography_page.dart diff --git a/.github/workflows/dart_code_metrics.yaml b/.github/workflows/dart_code_metrics.yaml index 1dd9587a..03336a6a 100644 --- a/.github/workflows/dart_code_metrics.yaml +++ b/.github/workflows/dart_code_metrics.yaml @@ -8,12 +8,12 @@ jobs: steps: - uses: actions/checkout@v3 - - name: Run Dart Code Metrics + - name: Install Flutter uses: subosito/flutter-action@v2 with: channel: stable - - name: Install dependencies + - name: Set Up DCM run: flutter pub get - uses: CQLabs/setup-dcm@v1.0.0 with: diff --git a/CHANGELOG.md b/CHANGELOG.md index 345ceb9c..10ee2492 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## [2.0.0-beta.8] +✨ New ✨ +* `MacosFontWeight` allows using Apple-specific font weights like `w510`, `w590`, and `w860`. + +🛠️ Fixed 🛠️ +* `MacosTypography.black` and `MacosTypography.white` now conform to specification by using `MacosColors.labelColor` + ## [2.0.0-beta.7] ✨ New ✨ * You can now call `MacosTypography.of(context)` as a shorthand for retrieving the typography used in your `MacosTheme`. diff --git a/example/lib/main.dart b/example/lib/main.dart index 3b7d2ed2..04f48411 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -8,6 +8,7 @@ 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:flutter/cupertino.dart'; import 'package:macos_ui/macos_ui.dart'; import 'package:provider/provider.dart'; @@ -71,6 +72,7 @@ class _WidgetGalleryState extends State { (_) => const TabViewPage(), (_) => const ResizablePanePage(), (_) => const SelectorsPage(), + (_) => const TypographyPage(), ]; @override @@ -255,6 +257,10 @@ class _WidgetGalleryState extends State { ), label: Text('Selectors'), ), + SidebarItem( + leading: MacosIcon(CupertinoIcons.textformat_size), + label: Text('Typography'), + ), ], ); }, 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/pubspec.lock b/example/pubspec.lock index 3f05abc6..6ef37937 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -145,7 +145,7 @@ packages: path: ".." relative: true source: path - version: "2.0.0-beta.7" + version: "2.0.0-beta.8" macos_window_utils: dependency: transitive description: diff --git a/lib/src/selectors/date_picker.dart b/lib/src/selectors/date_picker.dart index a3e20fc0..2b3cd9b0 100644 --- a/lib/src/selectors/date_picker.dart +++ b/lib/src/selectors/date_picker.dart @@ -107,9 +107,9 @@ class MacosDatePicker extends StatefulWidget { /// 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; diff --git a/lib/src/theme/macos_theme.dart b/lib/src/theme/macos_theme.dart index 6de99369..0cae2b9a 100644 --- a/lib/src/theme/macos_theme.dart +++ b/lib/src/theme/macos_theme.dart @@ -224,9 +224,8 @@ class MacosThemeData with Diagnosticable { ); pushButtonTheme ??= PushButtonThemeData( color: primaryColor, - secondaryColor: isDark - ? const Color.fromRGBO(110, 109, 112, 1.0) - : MacosColors.white, + 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/typography.dart b/lib/src/theme/typography.dart index c5a9eefd..e600a3e0 100644 --- a/lib/src/theme/typography.dart +++ b/lib/src/theme/typography.dart @@ -1,5 +1,6 @@ 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'; @@ -142,9 +143,9 @@ class MacosTypography with Diagnosticable { }); static final MacosTypography black = - MacosTypography(color: CupertinoColors.black); + MacosTypography(color: MacosColors.labelColor.color); static final MacosTypography white = - MacosTypography(color: CupertinoColors.white); + MacosTypography(color: MacosColors.labelColor.darkColor); /// Style used for body text. final TextStyle body; @@ -297,3 +298,147 @@ 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/pubspec.yaml b/pubspec.yaml index 5ce15348..4c2877e4 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: 2.0.0-beta.7 +version: 2.0.0-beta.8 homepage: "https://macosui.dev" repository: "https://github.com/GroovinChip/macos_ui" From de3e35cb2522eb3063649004caa6e4af4445e3ba Mon Sep 17 00:00:00 2001 From: st merlhin <77164238+stMerlHin@users.noreply.github.com> Date: Wed, 12 Jul 2023 13:31:55 +0000 Subject: [PATCH 032/151] Non-scrollable `ResizablePane` (#420) --- CHANGELOG.md | 3 + example/lib/pages/resizable_pane_page.dart | 12 +- example/lib/widgets/widget_text_title1.dart | 2 +- example/pubspec.lock | 2 +- lib/src/layout/resizable_pane.dart | 62 ++++- pubspec.yaml | 2 +- test/layout/resizeable_pane_test.dart | 240 +++++++++++++++++++- test/selectors/date_picker_test.dart | 56 ++--- 8 files changed, 330 insertions(+), 49 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 10ee2492..88c90a37 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +## [2.0.0-beta.9] +* `ResizablePane` can now disallow the usage of its internal scrollbar via the `ReziablePane.noScrollBar` constructor. + ## [2.0.0-beta.8] ✨ New ✨ * `MacosFontWeight` allows using Apple-specific font weights like `w510`, `w590`, and `w860`. diff --git a/example/lib/pages/resizable_pane_page.dart b/example/lib/pages/resizable_pane_page.dart index b850a0c3..78ed3320 100644 --- a/example/lib/pages/resizable_pane_page.dart +++ b/example/lib/pages/resizable_pane_page.dart @@ -73,16 +73,12 @@ class _ResizablePanePageState extends State { ); }, ), - ResizablePane( + const ResizablePane.noScrollBar( minSize: 180, startSize: 200, - // windowBreakpoint: 800, - resizableSide: ResizableSide.left, - builder: (_, __) { - return const Center( - child: Text('Right Resizable Pane'), - ); - }, + windowBreakpoint: 700, + resizableSide: ResizableSide.right, + child: Center(child: Text('Right non-scrollable Resizable Pane')), ), ], ); diff --git a/example/lib/widgets/widget_text_title1.dart b/example/lib/widgets/widget_text_title1.dart index d7dafc90..77ab7314 100644 --- a/example/lib/widgets/widget_text_title1.dart +++ b/example/lib/widgets/widget_text_title1.dart @@ -27,4 +27,4 @@ class WidgetTextTitle1 extends StatelessWidget { ), ); } -} \ No newline at end of file +} diff --git a/example/pubspec.lock b/example/pubspec.lock index 6ef37937..8872a184 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -145,7 +145,7 @@ packages: path: ".." relative: true source: path - version: "2.0.0-beta.8" + version: "2.0.0-beta.9" macos_window_utils: dependency: transitive description: diff --git a/lib/src/layout/resizable_pane.dart b/lib/src/layout/resizable_pane.dart index b3f6b338..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; @@ -277,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/pubspec.yaml b/pubspec.yaml index 4c2877e4..c14c5d6b 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: 2.0.0-beta.8 +version: 2.0.0-beta.9 homepage: "https://macosui.dev" repository: "https://github.com/GroovinChip/macos_ui" diff --git a/test/layout/resizeable_pane_test.dart b/test/layout/resizeable_pane_test.dart index 56164763..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, @@ -79,6 +79,238 @@ void main() { 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( + 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( + '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/selectors/date_picker_test.dart b/test/selectors/date_picker_test.dart index e5f4f495..f0996546 100644 --- a/test/selectors/date_picker_test.dart +++ b/test/selectors/date_picker_test.dart @@ -84,30 +84,32 @@ void main() { testWidgets( 'Textual MacosDatePicker renders the date with respect to "dateFormat" property', - (tester) async { + (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, - ), - ); - }, + 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; + 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); @@ -363,7 +365,7 @@ void main() { testWidgets( 'Graphical MacosDatePicker renders abbreviations based on "weekdayAbbreviations" and "monthAbbreviations" properties', - (tester) async { + (tester) async { await tester.pumpWidget( MacosApp( home: MacosWindow( @@ -424,7 +426,7 @@ void main() { testWidgets( 'Graphical MacosDatePicker with "startWeekOnMonday" set to true shows Monday as the first day of the week', - (tester) async { + (tester) async { await tester.pumpWidget( MacosApp( home: MacosWindow( @@ -453,7 +455,8 @@ void main() { matching: find.byType(Text), ); final firstWeekday = dayHeaders.first; - final firstWeekdayText = (firstWeekday.evaluate().first.widget as Text).data; + final firstWeekdayText = + (firstWeekday.evaluate().first.widget as Text).data; await tester.pumpAndSettle(); expect(firstWeekdayText, 'Mo'); @@ -474,7 +477,7 @@ void main() { // 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 { + (tester) async { await tester.pumpWidget( MacosApp( supportedLocales: const [ @@ -506,7 +509,8 @@ void main() { matching: find.byType(Text), ); final firstWeekday = dayHeaders.first; - final firstWeekdayText = (firstWeekday.evaluate().first.widget as Text).data; + 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 From 64472fe21c9200bb1229455906be0dc7a2d961c1 Mon Sep 17 00:00:00 2001 From: GroovinChip Date: Wed, 19 Jul 2023 09:24:18 -0400 Subject: [PATCH 033/151] chore: add missing trailing commas & format --- lib/src/theme/typography.dart | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/src/theme/typography.dart b/lib/src/theme/typography.dart index e600a3e0..2462208b 100644 --- a/lib/src/theme/typography.dart +++ b/lib/src/theme/typography.dart @@ -383,7 +383,7 @@ class MacosFontWeight implements FontWeight { w700, w800, w860, - w900 + w900, ]; /// Linearly interpolates between two font weights. @@ -408,7 +408,10 @@ class MacosFontWeight implements FontWeight { /// Values for `t` are usually obtained from an [Animation], such as /// an [AnimationController]. static MacosFontWeight? lerp( - MacosFontWeight? a, MacosFontWeight? b, double t) { + MacosFontWeight? a, + MacosFontWeight? b, + double t, + ) { if (a == null && b == null) { return null; } From 0e8231f7ce9bfff5af08051646c0dc81cd90fa52 Mon Sep 17 00:00:00 2001 From: Reuben Turner Date: Wed, 19 Jul 2023 12:59:35 -0400 Subject: [PATCH 034/151] 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 --- CHANGELOG.md | 12 +++ example/lib/main.dart | 73 ++++++++++--------- example/lib/pages/selectors_page.dart | 13 ++-- example/lib/platform_menus.dart | 52 +++++++++++++ .../Flutter/GeneratedPluginRegistrant.swift | 2 + example/macos/Podfile.lock | 6 ++ example/pubspec.lock | 71 +++++++++++++++++- example/pubspec.yaml | 1 + lib/src/buttons/push_button.dart | 3 +- lib/src/layout/scaffold.dart | 46 ++++++++---- lib/src/layout/toolbar/toolbar.dart | 13 ++-- lib/src/layout/window.dart | 57 ++++++++++++++- lib/src/theme/macos_theme.dart | 7 +- lib/src/theme/typography.dart | 28 +++---- lib/src/utils.dart | 11 +-- pubspec.yaml | 2 +- 16 files changed, 301 insertions(+), 96 deletions(-) create mode 100644 example/lib/platform_menus.dart diff --git a/CHANGELOG.md b/CHANGELOG.md index 88c90a37..786279c3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,15 @@ +## [2.0.0-beta.10] +🛠️ Fixed 🛠️ +* Ensure builds targeting web do not utilize any `macos_window_utils` code +* Ensure builds targeting web are themed correctly + +🔄 Updated 🔄 +* `MacosTypography` white and black are now factory constructors called `darkOpaque()` and `lightOpaque()` to reflect +Apple's naming conventions. +* `PushButton` now uses the correct `body` text style instead of the incorrect `headline` +* `Toolbar` now uses the correct `title3` text style instead of the incorrect `headline` +* `MacosTheme` sets the global typography, per theme, more efficiently + ## [2.0.0-beta.9] * `ResizablePane` can now disallow the usage of its internal scrollbar via the `ReziablePane.noScrollBar` constructor. diff --git a/example/lib/main.dart b/example/lib/main.dart index 04f48411..8869a817 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -1,3 +1,5 @@ +import 'dart:io'; + import 'package:example/pages/buttons_page.dart'; import 'package:example/pages/colors_page.dart'; import 'package:example/pages/dialogs_page.dart'; @@ -9,9 +11,12 @@ 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'; @@ -22,7 +27,11 @@ Future _configureMacosWindowUtils() async { } Future main() async { - await _configureMacosWindowUtils(); + if (!kIsWeb) { + if (Platform.isMacOS) { + await _configureMacosWindowUtils(); + } + } runApp(const MacosUIGalleryApp()); } @@ -78,38 +87,7 @@ class _WidgetGalleryState extends State { @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( @@ -184,7 +162,17 @@ 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 [ @@ -281,7 +269,7 @@ class _WidgetGalleryState extends State { ); }, ), - child: IndexedStack( + /*child: IndexedStack( index: pageIndex, children: pageBuilders .asMap() @@ -291,7 +279,20 @@ class _WidgetGalleryState extends State { }) .values .toList(), - ), + ),*/ + 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/selectors_page.dart b/example/lib/pages/selectors_page.dart index fdc5fbeb..74ed4b47 100644 --- a/example/lib/pages/selectors_page.dart +++ b/example/lib/pages/selectors_page.dart @@ -1,6 +1,7 @@ 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'; @@ -79,11 +80,13 @@ class _SelectorsPageState extends State { ], ), const SizedBox(height: 20), - const WidgetTextTitle1(widgetName: 'MacosColorWell'), - Divider(color: MacosTheme.of(context).dividerColor), - MacosColorWell( - onColorSelected: (color) => debugPrint('$color'), - ), + if (!kIsWeb) ...[ + const WidgetTextTitle1(widgetName: 'MacosColorWell'), + Divider(color: MacosTheme.of(context).dividerColor), + MacosColorWell( + onColorSelected: (color) => debugPrint('$color'), + ), + ], ], ), ); 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/macos/Flutter/GeneratedPluginRegistrant.swift b/example/macos/Flutter/GeneratedPluginRegistrant.swift index 15deecb4..0179c12c 100644 --- a/example/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/example/macos/Flutter/GeneratedPluginRegistrant.swift @@ -8,9 +8,11 @@ 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.lock b/example/macos/Podfile.lock index 97b47e1b..2fa81174 100644 --- a/example/macos/Podfile.lock +++ b/example/macos/Podfile.lock @@ -7,12 +7,15 @@ PODS: - 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: @@ -23,12 +26,15 @@ EXTERNAL SOURCES: :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: ff0a9a3ce75ee73f200ca7e2f47745698c917ef9 diff --git a/example/pubspec.lock b/example/pubspec.lock index 8872a184..7e7d770a 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -99,6 +99,11 @@ packages: 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: @@ -145,7 +150,7 @@ packages: path: ".." relative: true source: path - version: "2.0.0-beta.9" + version: "2.0.0-beta.10" macos_window_utils: dependency: transitive description: @@ -335,6 +340,70 @@ packages: 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: "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: diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 79564134..0a008b51 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -15,6 +15,7 @@ dependencies: path: .. provider: ^6.0.5 google_fonts: ^5.1.0 + url_launcher: ^6.1.12 dev_dependencies: flutter_test: diff --git a/lib/src/buttons/push_button.dart b/lib/src/buttons/push_button.dart index 4fa2258a..19180016 100644 --- a/lib/src/buttons/push_button.dart +++ b/lib/src/buttons/push_button.dart @@ -324,8 +324,7 @@ class PushButtonState extends State ? const Color.fromRGBO(255, 255, 255, 0.25) : const Color.fromRGBO(0, 0, 0, 0.25); - final baseStyle = - theme.typography.headline.copyWith(color: foregroundColor); + final baseStyle = theme.typography.body.copyWith(color: foregroundColor); return MouseRegion( cursor: widget.mouseCursor!, diff --git a/lib/src/layout/scaffold.dart b/lib/src/layout/scaffold.dart index 2df5a94d..59678372 100644 --- a/lib/src/layout/scaffold.dart +++ b/lib/src/layout/scaffold.dart @@ -1,5 +1,6 @@ import 'dart:math' as math; +import 'package:flutter/foundation.dart'; import 'package:flutter/rendering.dart'; import 'package:macos_ui/macos_ui.dart'; import 'package:macos_ui/src/library.dart'; @@ -63,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) { @@ -76,19 +77,34 @@ 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: WallpaperTintedArea( - backgroundColor: backgroundColor, - insertRepaintBoundary: true, + 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), @@ -96,7 +112,7 @@ class _MacosScaffoldState extends State { child: _ScaffoldBody(children: children), ), ), - ), + ], // Toolbar if (widget.toolBar != null) diff --git a/lib/src/layout/toolbar/toolbar.dart b/lib/src/layout/toolbar/toolbar.dart index bad588c8..efd62e3c 100644 --- a/lib/src/layout/toolbar/toolbar.dart +++ b/lib/src/layout/toolbar/toolbar.dart @@ -223,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, ), ); @@ -266,7 +263,7 @@ class _ToolBarState extends State { ), ), child: _WallpaperTintedAreaOrBlurFilter( - enableWallpaperTintedArea: !widget.enableBlur, + enableWallpaperTintedArea: kIsWeb ? false : !widget.enableBlur, isWidgetVisible: widget.allowWallpaperTintingOverrides, backgroundColor: theme.canvasColor, widgetOpacity: widget.decoration?.color?.opacity, diff --git a/lib/src/layout/window.dart b/lib/src/layout/window.dart index 4d8b4cf5..85c20417 100644 --- a/lib/src/layout/window.dart +++ b/lib/src/layout/window.dart @@ -265,12 +265,65 @@ class _MacosWindowState extends State { minHeight: height, maxHeight: height, ).normalize(), - child: TransparentMacOSSidebar( + 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 (sidebar.bottom != null && + constraints.maxHeight > 141) + Padding( + padding: const EdgeInsets.all(16.0), + child: sidebar.bottom!, + ), + ], + ), + ) : TransparentMacOSSidebar( state: sidebarState, child: Column( children: [ - if ((sidebar.topOffset) > 0) + // 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), diff --git a/lib/src/theme/macos_theme.dart b/lib/src/theme/macos_theme.dart index 0cae2b9a..87405882 100644 --- a/lib/src/theme/macos_theme.dart +++ b/lib/src/theme/macos_theme.dart @@ -217,11 +217,8 @@ class MacosThemeData with Diagnosticable { canvasColor ??= isDark ? const Color.fromRGBO(40, 40, 40, 1.0) : const Color.fromRGBO(246, 246, 246, 1.0); - typography ??= MacosTypography( - color: _brightness == Brightness.light - ? CupertinoColors.black - : CupertinoColors.white, - ); + typography ??= + isDark ? MacosTypography.lightOpaque() : MacosTypography.darkOpaque(); pushButtonTheme ??= PushButtonThemeData( color: primaryColor, secondaryColor: diff --git a/lib/src/theme/typography.dart b/lib/src/theme/typography.dart index 2462208b..7b2f72a7 100644 --- a/lib/src/theme/typography.dart +++ b/lib/src/theme/typography.dart @@ -18,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]. @@ -67,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, @@ -92,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, @@ -108,7 +108,7 @@ class MacosTypography with Diagnosticable { ); caption2 ??= TextStyle( fontFamily: _kDefaultFontFamily, - fontWeight: FontWeight.w400, + fontWeight: MacosFontWeight.w510, fontSize: 10, letterSpacing: 0.12, color: color, @@ -142,9 +142,9 @@ class MacosTypography with Diagnosticable { required this.caption2, }); - static final MacosTypography black = + factory MacosTypography.darkOpaque() => MacosTypography(color: MacosColors.labelColor.color); - static final MacosTypography white = + factory MacosTypography.lightOpaque() => MacosTypography(color: MacosColors.labelColor.darkColor); /// Style used for body text. @@ -240,7 +240,7 @@ class MacosTypography with Diagnosticable { @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); - final defaultStyle = MacosTypography.black; + final defaultStyle = MacosTypography.darkOpaque(); properties.add(DiagnosticsProperty( 'largeTitle', largeTitle, diff --git a/lib/src/utils.dart b/lib/src/utils.dart index b99c1796..1f2d8a1e 100644 --- a/lib/src/utils.dart +++ b/lib/src/utils.dart @@ -1,5 +1,6 @@ import 'dart:io'; +import 'package:flutter/foundation.dart'; import 'package:macos_ui/macos_ui.dart'; import 'package:macos_ui/src/library.dart'; @@ -77,13 +78,9 @@ class MacOSBrightnessOverrideHandler { /// [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 (!Platform.isMacOS) { - return; - } - - if (currentBrightness == _lastBrightness) { - return; - } + if (kIsWeb) return; + if (!Platform.isMacOS) return; + if (currentBrightness == _lastBrightness) return; WindowManipulator.overrideMacOSBrightness(dark: currentBrightness.isDark); _lastBrightness = currentBrightness; diff --git a/pubspec.yaml b/pubspec.yaml index c14c5d6b..e76d7bfb 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: 2.0.0-beta.9 +version: 2.0.0-beta.10 homepage: "https://macosui.dev" repository: "https://github.com/GroovinChip/macos_ui" From d7eaf5b20b10c995a63a88ec3d25d3faad683d6e Mon Sep 17 00:00:00 2001 From: GroovinChip Date: Wed, 19 Jul 2023 13:08:46 -0400 Subject: [PATCH 035/151] gallery: remove commented & deprecated code from `main.dart` --- example/lib/main.dart | 25 ------------------------- 1 file changed, 25 deletions(-) diff --git a/example/lib/main.dart b/example/lib/main.dart index 8869a817..193bef1f 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -70,20 +70,6 @@ class _WidgetGalleryState extends State { late final searchFieldController = TextEditingController(); - final List pageBuilders = [ - (_) => CupertinoTabView(builder: (_) => const ButtonsPage()), - (_) => const IndicatorsPage(), - (_) => const FieldsPage(), - (_) => const ColorsPage(), - (_) => const DialogsPage(), - (_) => const ToolbarPage(), - (isVisible) => SliverToolbarPage(isVisible: isVisible), - (_) => const TabViewPage(), - (_) => const ResizablePanePage(), - (_) => const SelectorsPage(), - (_) => const TypographyPage(), - ]; - @override Widget build(BuildContext context) { return PlatformMenuBar( @@ -269,17 +255,6 @@ class _WidgetGalleryState extends State { ); }, ), - /*child: IndexedStack( - index: pageIndex, - children: pageBuilders - .asMap() - .map((index, builder) { - final widget = builder(index == pageIndex); - return MapEntry(index, widget); - }) - .values - .toList(), - ),*/ child: [ CupertinoTabView(builder: (_) => const ButtonsPage()), const IndicatorsPage(), From be20db081beda08027617a03dcb4261fe86325d9 Mon Sep 17 00:00:00 2001 From: Reuben Turner Date: Wed, 19 Jul 2023 14:19:33 -0400 Subject: [PATCH 036/151] 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 --- CHANGELOG.md | 7 ++ README.md | 91 ++++++++----------- example/lib/pages/buttons_page.dart | 4 + example/lib/pages/dialogs_page.dart | 79 ++++++++-------- example/lib/pages/indicators_page.dart | 16 +++- example/pubspec.lock | 2 +- lib/src/buttons/help_button.dart | 20 ++-- lib/src/indicators/relevance_indicator.dart | 1 + pubspec.yaml | 2 +- test/indicators/relevance_indicator_test.dart | 31 ------- 10 files changed, 112 insertions(+), 141 deletions(-) delete mode 100644 test/indicators/relevance_indicator_test.dart diff --git a/CHANGELOG.md b/CHANGELOG.md index 786279c3..397f0187 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## [2.0.0-beta.11] +🚨 Breaking Changes 🚨 +* `RelevanceIndicator` has been deprecated + +🔄 Updated 🔄 +* `HelpButton` now sizes itself according to specification + ## [2.0.0-beta.10] 🛠️ Fixed 🛠️ * Ensure builds targeting web do not utilize any `macos_window_utils` code diff --git a/README.md b/README.md index b9a911d1..482e67a5 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ Guides, codelabs, and other documentation can be found at https://macosui.dev [![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) - + ## 🚨 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,21 +218,20 @@ 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, 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: +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: ```podspec platform :osx, '10.14.6' ``` +You may also need to open up your app's `Runner.xcodeproj` in XCode and set the minimum deployment version there. + Now, configure your window inside your `main()` as follows: ```dart @@ -246,7 +246,7 @@ Future _configureMacosWindowUtils() async { void main() async { await _configureMacosWindowUtils(); - runApp(const MacosUIGalleryApp()); + runApp(const YourAppHere()); } ``` @@ -335,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)): @@ -408,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. @@ -472,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: @@ -496,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: @@ -517,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: @@ -636,8 +636,8 @@ Push buttons are the standard button type in macOS. Push buttons contain text— 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. @@ -666,8 +666,8 @@ control sizes: * `regular` | Off | On | -|--------------------------------------------|--------------------------------------------| -| | | +| ------------------------------------------ | ------------------------------------------ | +| | | Here's an example of how to create a basic toggle switch: @@ -689,7 +689,7 @@ Learn more about switches [here](https://developer.apple.com/design/human-interf 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. @@ -722,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 @@ -736,7 +737,7 @@ showMacosSheet( ); ``` -![](https://imgur.com/NV0o5Ws.png) +![](https://imgur.com/Mnw2ywm.png) # Fields @@ -823,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. @@ -870,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: @@ -919,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. @@ -939,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 diff --git a/example/lib/pages/buttons_page.dart b/example/lib/pages/buttons_page.dart index 80ff6cf8..fd61774f 100644 --- a/example/lib/pages/buttons_page.dart +++ b/example/lib/pages/buttons_page.dart @@ -431,6 +431,10 @@ class _ButtonsPageState extends State { ], ), 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, diff --git a/example/lib/pages/dialogs_page.dart b/example/lib/pages/dialogs_page.dart index 1fea4908..33764bda 100644 --- a/example/lib/pages/dialogs_page.dart +++ b/example/lib/pages/dialogs_page.dart @@ -209,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( - controlSize: ControlSize.regular, - 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/indicators_page.dart b/example/lib/pages/indicators_page.dart index 7e8a6b20..511b40c6 100644 --- a/example/lib/pages/indicators_page.dart +++ b/example/lib/pages/indicators_page.dart @@ -1,4 +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'; @@ -117,8 +118,17 @@ class _IndicatorsPageState extends State { onChanged: (v) => setState(() => ratingValue = v), ), const SizedBox(height: 20), - const WidgetTextTitle1(widgetName: 'ProgressCircle'), + Text( + 'Progress Indicators', + style: MacosTypography.of(context).title1, + ), 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'), @@ -137,10 +147,6 @@ class _IndicatorsPageState extends State { const WidgetTextTitle1(widgetName: 'RelevanceIndicator'), Divider(color: MacosTheme.of(context).dividerColor), const SizedBox(height: 8), - const RelevanceIndicator( - value: 25, - amount: 50, - ), ], ), ); diff --git a/example/pubspec.lock b/example/pubspec.lock index 7e7d770a..45c959ef 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -150,7 +150,7 @@ packages: path: ".." relative: true source: path - version: "2.0.0-beta.10" + version: "2.0.0-beta.11" macos_window_utils: dependency: transitive description: 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/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/pubspec.yaml b/pubspec.yaml index e76d7bfb..9cf52acf 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: 2.0.0-beta.10 +version: 2.0.0-beta.11 homepage: "https://macosui.dev" repository: "https://github.com/GroovinChip/macos_ui" 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', - ], - ); - }); -} From 34fad11c7c7d83166f6a31e871b2c465fa9c14a2 Mon Sep 17 00:00:00 2001 From: Reuben Turner Date: Wed, 19 Jul 2023 15:52:56 -0400 Subject: [PATCH 037/151] 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 --- CHANGELOG.md | 117 ++++++++++++++++--------------------------- README.md | 2 +- example/pubspec.lock | 2 +- pubspec.yaml | 2 +- 4 files changed, 45 insertions(+), 78 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 397f0187..6a4ed815 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,86 +1,53 @@ -## [2.0.0-beta.11] -🚨 Breaking Changes 🚨 -* `RelevanceIndicator` has been deprecated - -🔄 Updated 🔄 -* `HelpButton` now sizes itself according to specification - -## [2.0.0-beta.10] -🛠️ Fixed 🛠️ -* Ensure builds targeting web do not utilize any `macos_window_utils` code -* Ensure builds targeting web are themed correctly - -🔄 Updated 🔄 -* `MacosTypography` white and black are now factory constructors called `darkOpaque()` and `lightOpaque()` to reflect -Apple's naming conventions. -* `PushButton` now uses the correct `body` text style instead of the incorrect `headline` -* `Toolbar` now uses the correct `title3` text style instead of the incorrect `headline` -* `MacosTheme` sets the global typography, per theme, more efficiently - -## [2.0.0-beta.9] -* `ResizablePane` can now disallow the usage of its internal scrollbar via the `ReziablePane.noScrollBar` constructor. - -## [2.0.0-beta.8] -✨ New ✨ -* `MacosFontWeight` allows using Apple-specific font weights like `w510`, `w590`, and `w860`. - -🛠️ Fixed 🛠️ -* `MacosTypography.black` and `MacosTypography.white` now conform to specification by using `MacosColors.labelColor` - -## [2.0.0-beta.7] -✨ New ✨ -* You can now call `MacosTypography.of(context)` as a shorthand for retrieving the typography used in your `MacosTheme`. - -🔄 Updated 🔄 -* `MacosAlertDialog` now defines `primaryButton` and `secondaryButton` to be of type `PushButton`. -* `MacosAlertDialog` now requires that `primaryButton` and `secondaryButton` to have `controlSize`s of `ControlSize.large`. -* `MacosAlertDialog` docs now suggest that `appIcon` should be of size 64x64. - -## [2.0.0-beta.6] -🔄 Updated 🔄 -* `MacosCheckbox` appearance more closely matches its native counterpart. +## [2.0.0] +### 🚨 Breaking Changes 🚨 +* Migrate `macos_ui` to [macos_window_utils](https://pub.dev/packages/macos_window_utils), 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. -## [2.0.0-beta.5] -🚨 Breaking Changes 🚨 +* 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`. - -🔄 Updated 🔄 -* `PushButton`'s secondary and disabled colors more closely match their native counterparts. - -## [2.0.0-beta.4] -🛠️ Fixed 🛠️ -* `ToolBar`s in use where a `SideBar` is not present will now have their title's avoid the traffic lights (native window controls). - -## [2.0.0-beta.3] -✨ New ✨ -* Added support for `weekdayAbbreviations` and `monthAbbreviations` to `MacosDatePicker`. -* Added support for `dateFormat` to `MacosDatePicker`. -* Added support for `startWeekOnMonday` to `MacosDatePicker`. - -🛠️ Fixed 🛠️ -* Better UX of the click on the calendar elements in `MacosDatePicker` +* `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. -## [2.0.0-beta.2] -✨New ✨ +### ✨ 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. -🔄 Updated 🔄 -* Some previously missing elements of the `MacosColor` class have been added. - -## [2.0.0-beta.1] -🚨 Breaking Changes 🚨 -* Migrate `macos_ui` to [macos_window_utils](https://pub.dev/packages/macos_window_utils), 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. -* Support Flutter 3.10 and Dart 3 - -To migrate an existing application, please refer to the “Modern window look” section in the README. +### 🛠️ 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. diff --git a/README.md b/README.md index 482e67a5..23a54c9a 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ 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) diff --git a/example/pubspec.lock b/example/pubspec.lock index 45c959ef..dc586a2a 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -150,7 +150,7 @@ packages: path: ".." relative: true source: path - version: "2.0.0-beta.11" + version: "2.0.0" macos_window_utils: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 9cf52acf..13865b31 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: 2.0.0-beta.11 +version: 2.0.0 homepage: "https://macosui.dev" repository: "https://github.com/GroovinChip/macos_ui" From e4d3645718d2ffb2406e95d206db1d983a073e46 Mon Sep 17 00:00:00 2001 From: Reuben Turner Date: Wed, 19 Jul 2023 15:54:25 -0400 Subject: [PATCH 038/151] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6a4ed815..0c59bae9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ ## [2.0.0] ### 🚨 Breaking Changes 🚨 -* Migrate `macos_ui` to [macos_window_utils](https://pub.dev/packages/macos_window_utils), which provides the following benefits: +* `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. From f7dc715b9c77b096425f159ddd2b503275d6e5cb Mon Sep 17 00:00:00 2001 From: Kate Lovett Date: Mon, 31 Jul 2023 17:10:01 -0500 Subject: [PATCH 039/151] Update CONTRIBUTING.md Fixes typo in contributing doc --- CONTRIBUTING.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 15075873..97e7a96c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -25,8 +25,7 @@ This repository uses [conventional commits](https://www.conventionalcommits.org/ ## Pull Requests As mentioned above, all pull requests should target `dev`. -Before opening your pull request, please ensure that the following -following requirements are met: +Before opening your pull request, please ensure that the following requirements are met: * You have run `flutter pub get` at the package level * You have incremented the version number in `pubspec.yaml` properly * You have updated the `CHANGELOG.md` file accordingly @@ -38,4 +37,4 @@ A note for authorized maintainers: Pull requests should **always** be merged via ### Versioning -`macos_ui` uses semantic versioning. Please ensure your updates follow this method accordingly. \ No newline at end of file +`macos_ui` uses semantic versioning. Please ensure your updates follow this method accordingly. From 8c250edb0b1015a9db9ef09033f0d5ab15a144ce Mon Sep 17 00:00:00 2001 From: Kate Lovett Date: Mon, 31 Jul 2023 17:21:48 -0500 Subject: [PATCH 040/151] Alter copy of mock_canvas to unblock moving to flutter_test --- CHANGELOG.md | 3 + example/pubspec.lock | 46 +++++++------- pubspec.lock | 62 ++++++++++++------- test/indicators/capacity_indicators_test.dart | 4 +- test/mock_canvas.dart | 8 +-- 5 files changed, 71 insertions(+), 52 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0c59bae9..02a7bb2a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +## [2.0.1] +* Modified mock_canvas copy from the framework to unblock move to flutter_test + ## [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: diff --git a/example/pubspec.lock b/example/pubspec.lock index dc586a2a..3dbde41a 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -37,10 +37,10 @@ packages: dependency: transitive description: name: collection - sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c" + sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a url: "https://pub.dev" source: hosted - version: "1.17.1" + version: "1.18.0" crypto: dependency: transitive description: @@ -128,14 +128,6 @@ packages: url: "https://pub.dev" source: hosted version: "4.0.2" - js: - dependency: transitive - description: - name: js - sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 - url: "https://pub.dev" - source: hosted - version: "0.6.7" lints: dependency: transitive description: @@ -163,18 +155,18 @@ packages: dependency: transitive description: name: matcher - sha256: "6501fbd55da300384b768785b83e5ce66991266cec21af89ab9ae7f5ce1c4cbb" + sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e" url: "https://pub.dev" source: hosted - version: "0.12.15" + version: "0.12.16" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724 + sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41" url: "https://pub.dev" source: hosted - version: "0.2.0" + version: "0.5.0" meta: dependency: transitive description: @@ -288,26 +280,26 @@ packages: dependency: transitive description: name: source_span - sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250 + sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" url: "https://pub.dev" source: hosted - version: "1.9.1" + version: "1.10.0" stack_trace: dependency: transitive description: name: stack_trace - sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5 + sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" url: "https://pub.dev" source: hosted - version: "1.11.0" + version: "1.11.1" stream_channel: dependency: transitive description: name: stream_channel - sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8" + sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.2" string_scanner: dependency: transitive description: @@ -328,10 +320,10 @@ packages: dependency: transitive description: name: test_api - sha256: eb6ac1540b26de412b3403a163d919ba86f6a973fe6cc50ae3541b80092fdcfb + sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b" url: "https://pub.dev" source: hosted - version: "0.5.1" + version: "0.6.1" typed_data: dependency: transitive description: @@ -412,6 +404,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.4" + web: + dependency: transitive + description: + name: web + sha256: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10 + url: "https://pub.dev" + source: hosted + version: "0.1.4-beta" win32: dependency: transitive description: @@ -429,5 +429,5 @@ packages: source: hosted version: "1.0.0" sdks: - dart: ">=3.0.0 <4.0.0" + dart: ">=3.1.0-185.0.dev <4.0.0" flutter: ">=3.10.0" diff --git a/pubspec.lock b/pubspec.lock index f80cd1d5..708421c4 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -5,18 +5,18 @@ packages: dependency: transitive description: name: _fe_analyzer_shared - sha256: "8880b4cfe7b5b17d57c052a5a3a8cc1d4f546261c7cc8fbd717bd53f48db0568" + sha256: "0816708f5fbcacca324d811297153fe3c8e047beb5c6752e12292d2974c17045" url: "https://pub.dev" source: hosted - version: "59.0.0" + version: "62.0.0" analyzer: dependency: transitive description: name: analyzer - sha256: a89627f49b0e70e068130a36571409726b04dab12da7e5625941d2c8ec278b96 + sha256: "21862995c9932cd082f89d72ae5f5e2c110d1a0204ad06e4ebaee8307b76b834" url: "https://pub.dev" source: hosted - version: "5.11.1" + version: "6.0.0" args: dependency: transitive description: @@ -61,10 +61,10 @@ packages: dependency: transitive description: name: collection - sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c" + sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a url: "https://pub.dev" source: hosted - version: "1.17.1" + version: "1.18.0" convert: dependency: transitive description: @@ -89,6 +89,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.3" + dart_internal: + dependency: transitive + description: + name: dart_internal + sha256: "689dccc3d5f62affd339534cca548dce12b3a6b32f0f10861569d3025efc0567" + url: "https://pub.dev" + source: hosted + version: "0.2.9" fake_async: dependency: transitive description: @@ -199,18 +207,18 @@ packages: dependency: transitive description: name: matcher - sha256: "6501fbd55da300384b768785b83e5ce66991266cec21af89ab9ae7f5ce1c4cbb" + sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e" url: "https://pub.dev" source: hosted - version: "0.12.15" + version: "0.12.16" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724 + sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41" url: "https://pub.dev" source: hosted - version: "0.2.0" + version: "0.5.0" meta: dependency: transitive description: @@ -332,26 +340,26 @@ packages: dependency: transitive description: name: source_span - sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250 + sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" url: "https://pub.dev" source: hosted - version: "1.9.1" + version: "1.10.0" stack_trace: dependency: transitive description: name: stack_trace - sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5 + sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" url: "https://pub.dev" source: hosted - version: "1.11.0" + version: "1.11.1" stream_channel: dependency: transitive description: name: stream_channel - sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8" + sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.2" string_scanner: dependency: transitive description: @@ -372,26 +380,26 @@ packages: dependency: transitive description: name: test - sha256: "3dac9aecf2c3991d09b9cdde4f98ded7b30804a88a0d7e4e7e1678e78d6b97f4" + sha256: b9a384c4b9c4966dbf7215e7c033a78db1da7e5dcaf8da9232c5f24735f65652 url: "https://pub.dev" source: hosted - version: "1.24.1" + version: "1.24.5" test_api: dependency: transitive description: name: test_api - sha256: eb6ac1540b26de412b3403a163d919ba86f6a973fe6cc50ae3541b80092fdcfb + sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b" url: "https://pub.dev" source: hosted - version: "0.5.1" + version: "0.6.1" test_core: dependency: transitive description: name: test_core - sha256: "5138dbffb77b2289ecb12b81c11ba46036590b72a64a7a90d6ffb880f1a29e93" + sha256: c6a536288535efef8526eea8adfa4e25fdd2849fa7f457ecb2a52099998ce8f7 url: "https://pub.dev" source: hosted - version: "0.5.1" + version: "0.5.5" typed_data: dependency: transitive description: @@ -424,6 +432,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.0" + web: + dependency: transitive + description: + name: web + sha256: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10 + url: "https://pub.dev" + source: hosted + version: "0.1.4-beta" web_socket_channel: dependency: transitive description: @@ -449,5 +465,5 @@ packages: source: hosted version: "3.1.2" sdks: - dart: ">=3.0.0 <4.0.0" + dart: ">=3.1.0-185.0.dev <3.3.0" flutter: ">=3.10.0" diff --git a/test/indicators/capacity_indicators_test.dart b/test/indicators/capacity_indicators_test.dart index 5f5eb33a..3286af26 100644 --- a/test/indicators/capacity_indicators_test.dart +++ b/test/indicators/capacity_indicators_test.dart @@ -81,7 +81,7 @@ void main() { expect( find.byType(CapacityIndicator), // each discrete segment is drawn 3 times, two times with fill, last time with stroke - paintsExactlyCountTimes(#drawRRect, 20 * 3), + paintedExactlyCountTimes(#drawRRect, 20 * 3), ); }, ); @@ -110,7 +110,7 @@ void main() { // each discrete segment is drawn 3 times, background - fill - stroke // a filled segment is drawn by fromLTRBR with LTRB=0,0,8,16 // an empty segment is drawnby fromLTRBAndCorners with LTRB=0,0,0,16 - paints + painted ..rrect( rrect: RRect.fromLTRBR( 0.0, diff --git a/test/mock_canvas.dart b/test/mock_canvas.dart index c86c7482..7c7cd5f7 100644 --- a/test/mock_canvas.dart +++ b/test/mock_canvas.dart @@ -40,16 +40,16 @@ import 'recording_canvas.dart'; /// To match something which paints nothing, see [paintsNothing]. /// /// To match something which asserts instead of painting, see [paintsAssertion]. -PaintPattern get paints => _TestRecordingCanvasPatternMatcher(); +PaintPattern get painted => _TestRecordingCanvasPatternMatcher(); /// Matches objects or functions that does not paint anything on the canvas. -Matcher get paintsNothing => _TestRecordingCanvasPaintsNothingMatcher(); +Matcher get paintedNothing => _TestRecordingCanvasPaintsNothingMatcher(); /// Matches objects or functions that assert when they try to paint. -Matcher get paintsAssertion => _TestRecordingCanvasPaintsAssertionMatcher(); +Matcher get paintedAssertion => _TestRecordingCanvasPaintsAssertionMatcher(); /// Matches objects or functions that draw `methodName` exactly `count` number of times. -Matcher paintsExactlyCountTimes(Symbol methodName, int count) { +Matcher paintedExactlyCountTimes(Symbol methodName, int count) { return _TestRecordingCanvasPaintsCountMatcher(methodName, count); } From a5e85324e8831633084bfcb4169f3f0ba4bffb62 Mon Sep 17 00:00:00 2001 From: Kate Lovett Date: Wed, 2 Aug 2023 20:06:40 -0500 Subject: [PATCH 041/151] Fix compilation errors from mock canvas copy (#468) * Alter copy of mock_canvas to unblock moving to flutter_test * Fix compilation errors * changelog * Update changelog --- CHANGELOG.md | 3 - test/indicators/capacity_indicators_test.dart | 1 + test/mock_canvas.dart | 60 +++++++++---------- test/recording_canvas.dart | 28 ++++----- 4 files changed, 45 insertions(+), 47 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 02a7bb2a..0c59bae9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,3 @@ -## [2.0.1] -* Modified mock_canvas copy from the framework to unblock move to flutter_test - ## [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: diff --git a/test/indicators/capacity_indicators_test.dart b/test/indicators/capacity_indicators_test.dart index 3286af26..1780a362 100644 --- a/test/indicators/capacity_indicators_test.dart +++ b/test/indicators/capacity_indicators_test.dart @@ -5,6 +5,7 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:macos_ui/macos_ui.dart'; +// TODO(): Remove once mock_canvas in flutter_test reaches stable. import '../mock_canvas.dart'; void main() { diff --git a/test/mock_canvas.dart b/test/mock_canvas.dart index 7c7cd5f7..ee18e4f3 100644 --- a/test/mock_canvas.dart +++ b/test/mock_canvas.dart @@ -564,7 +564,7 @@ class _MismatchedCall { const _MismatchedCall(this.message, this.callIntroduction, this.call); final String message; final String callIntroduction; - final RecordedInvocation call; + final RecordInvocation call; } bool _evaluatePainter(Object? object, Canvas canvas, PaintingContext context) { @@ -593,9 +593,9 @@ bool _evaluatePainter(Object? object, Canvas canvas, PaintingContext context) { abstract class _TestRecordingCanvasMatcher extends Matcher { @override bool matches(Object? object, Map matchState) { - final TestRecordingCanvas canvas = TestRecordingCanvas(); - final TestRecordingPaintingContext context = - TestRecordingPaintingContext(canvas); + final TestRecordCanvas canvas = TestRecordCanvas(); + final TestRecordPaintingContext context = + TestRecordPaintingContext(canvas); final StringBuffer description = StringBuffer(); String prefixMessage = 'unexpectedly failed.'; bool result = false; @@ -618,7 +618,7 @@ abstract class _TestRecordingCanvasMatcher extends Matcher { if (!result) { if (canvas.invocations.isNotEmpty) { description.write('The complete display list was:'); - for (final RecordedInvocation call in canvas.invocations) { + for (final RecordInvocation call in canvas.invocations) { description.write('\n * $call'); } } @@ -628,7 +628,7 @@ abstract class _TestRecordingCanvasMatcher extends Matcher { } bool _evaluatePredicates( - Iterable calls, StringBuffer description); + Iterable calls, StringBuffer description); @override Description describeMismatch( @@ -658,9 +658,9 @@ class _TestRecordingCanvasPaintsCountMatcher @override bool _evaluatePredicates( - Iterable calls, StringBuffer description) { + Iterable calls, StringBuffer description) { int count = 0; - for (final RecordedInvocation call in calls) { + for (final RecordInvocation call in calls) { if (call.invocation.isMethod && call.invocation.memberName == _methodName) { count++; @@ -683,8 +683,8 @@ class _TestRecordingCanvasPaintsNothingMatcher @override bool _evaluatePredicates( - Iterable calls, StringBuffer description) { - final Iterable paintingCalls = + Iterable calls, StringBuffer description) { + final Iterable paintingCalls = _filterCanvasCalls(calls); if (paintingCalls.isEmpty) { return true; @@ -702,10 +702,10 @@ class _TestRecordingCanvasPaintsNothingMatcher ]; // Filters out canvas calls that are not painting anything. - static Iterable _filterCanvasCalls( - Iterable canvasCalls) { + static Iterable _filterCanvasCalls( + Iterable canvasCalls) { return canvasCalls.where( - (RecordedInvocation canvasCall) => + (RecordInvocation canvasCall) => !_nonPaintingOperations.contains(canvasCall.invocation.memberName), ); } @@ -714,9 +714,9 @@ class _TestRecordingCanvasPaintsNothingMatcher class _TestRecordingCanvasPaintsAssertionMatcher extends Matcher { @override bool matches(Object? object, Map matchState) { - final TestRecordingCanvas canvas = TestRecordingCanvas(); - final TestRecordingPaintingContext context = - TestRecordingPaintingContext(canvas); + final TestRecordCanvas canvas = TestRecordCanvas(); + final TestRecordPaintingContext context = + TestRecordPaintingContext(canvas); final StringBuffer description = StringBuffer(); String prefixMessage = 'unexpectedly failed.'; bool result = false; @@ -738,7 +738,7 @@ class _TestRecordingCanvasPaintsAssertionMatcher extends Matcher { if (!result) { if (canvas.invocations.isNotEmpty) { description.write('The complete display list was:'); - for (final RecordedInvocation call in canvas.invocations) { + for (final RecordInvocation call in canvas.invocations) { description.write('\n * $call'); } } @@ -1017,7 +1017,7 @@ class _TestRecordingCanvasPatternMatcher extends _TestRecordingCanvasMatcher @override bool _evaluatePredicates( - Iterable calls, StringBuffer description) { + Iterable calls, StringBuffer description) { if (calls.isEmpty) { description.writeln('It painted nothing.'); return false; @@ -1030,7 +1030,7 @@ class _TestRecordingCanvasPatternMatcher extends _TestRecordingCanvasMatcher return false; } final Iterator<_PaintPredicate> predicate = _predicates.iterator; - final Iterator call = calls.iterator..moveNext(); + final Iterator call = calls.iterator..moveNext(); try { while (predicate.moveNext()) { predicate.current.match(call); @@ -1056,12 +1056,12 @@ class _TestRecordingCanvasPatternMatcher extends _TestRecordingCanvasMatcher } abstract class _PaintPredicate { - void match(Iterator call); + void match(Iterator call); @protected - void checkMethod(Iterator call, Symbol symbol) { + void checkMethod(Iterator call, Symbol symbol) { int others = 0; - final RecordedInvocation firstCall = call.current; + final RecordInvocation firstCall = call.current; while (!call.current.invocation.isMethod || call.current.invocation.memberName != symbol) { others += 1; @@ -1108,7 +1108,7 @@ abstract class _DrawCommandPaintPredicate extends _PaintPredicate { String get methodName => _symbolName(symbol); @override - void match(Iterator call) { + void match(Iterator call) { checkMethod(call, symbol); final int actualArgumentCount = call.current.invocation.positionalArguments.length; @@ -1605,7 +1605,7 @@ class _ShadowPredicate extends _PaintPredicate { } @override - void match(Iterator call) { + void match(Iterator call) { checkMethod(call, symbol); verifyArguments(call.current.invocation.positionalArguments); call.moveNext(); @@ -1771,8 +1771,8 @@ class _SomethingPaintPredicate extends _PaintPredicate { final PaintPatternPredicate predicate; @override - void match(Iterator call) { - RecordedInvocation currentCall; + void match(Iterator call) { + RecordInvocation currentCall; bool testedAllCalls = false; do { if (testedAllCalls) { @@ -1807,9 +1807,9 @@ class _EverythingPaintPredicate extends _PaintPredicate { final PaintPatternPredicate predicate; @override - void match(Iterator call) { + void match(Iterator call) { do { - final RecordedInvocation currentCall = call.current; + final RecordInvocation currentCall = call.current; if (!currentCall.invocation.isMethod) { throw 'It called $currentCall, which was not a method, when the paint pattern expected a method call'; } @@ -1842,7 +1842,7 @@ class _FunctionPaintPredicate extends _PaintPredicate { final List arguments; @override - void match(Iterator call) { + void match(Iterator call) { checkMethod(call, symbol); if (call.current.invocation.positionalArguments.length != arguments.length) { @@ -1874,7 +1874,7 @@ class _FunctionPaintPredicate extends _PaintPredicate { class _SaveRestorePairPaintPredicate extends _PaintPredicate { @override - void match(Iterator call) { + void match(Iterator call) { checkMethod(call, #save); int depth = 1; while (depth > 0) { diff --git a/test/recording_canvas.dart b/test/recording_canvas.dart index d2bd2f33..8ff6abe4 100644 --- a/test/recording_canvas.dart +++ b/test/recording_canvas.dart @@ -7,9 +7,9 @@ import 'package:flutter/rendering.dart'; /// An [Invocation] and the [stack] trace that led to it. /// /// Used by [TestRecordingCanvas] to trace canvas calls. -class RecordedInvocation { +class RecordInvocation { /// Create a record for an invocation list. - const RecordedInvocation(this.invocation, {required this.stack}); + const RecordInvocation(this.invocation, {required this.stack}); /// The method that was called and its arguments. /// @@ -37,13 +37,13 @@ class RecordedInvocation { /// A [Canvas] for tests that records its method calls. /// -/// This class can be used in conjunction with [TestRecordingPaintingContext] +/// This class can be used in conjunction with [TestRecordPaintingContext] /// to record the [Canvas] method calls made by a renderer. For example: /// /// ```dart /// RenderBox box = tester.renderObject(find.text('ABC')); -/// TestRecordingCanvas canvas = TestRecordingCanvas(); -/// TestRecordingPaintingContext context = TestRecordingPaintingContext(canvas); +/// TestRecordCanvas canvas = TestRecordCanvas(); +/// TestRecordPaintingContext context = TestRecordPaintingContext(canvas); /// box.paint(context, Offset.zero); /// // Now test the expected canvas.invocations. /// ``` @@ -53,10 +53,10 @@ class RecordedInvocation { /// that the test requires. /// /// For simple tests, consider using the [paints] matcher, which overlays a -/// pattern matching API over [TestRecordingCanvas]. -class TestRecordingCanvas implements Canvas { +/// pattern matching API over [TestRecordCanvas]. +class TestRecordCanvas implements Canvas { /// All of the method calls on this canvas. - final List invocations = []; + final List invocations = []; int _saveCount = 0; @@ -67,13 +67,13 @@ class TestRecordingCanvas implements Canvas { void save() { _saveCount += 1; invocations - .add(RecordedInvocation(_MethodCall(#save), stack: StackTrace.current)); + .add(RecordInvocation(_MethodCall(#save), stack: StackTrace.current)); } @override void saveLayer(Rect? bounds, Paint paint) { _saveCount += 1; - invocations.add(RecordedInvocation( + invocations.add(RecordInvocation( _MethodCall(#saveLayer, [bounds, paint]), stack: StackTrace.current)); } @@ -83,20 +83,20 @@ class TestRecordingCanvas implements Canvas { _saveCount -= 1; assert(_saveCount >= 0); invocations.add( - RecordedInvocation(_MethodCall(#restore), stack: StackTrace.current)); + RecordInvocation(_MethodCall(#restore), stack: StackTrace.current)); } @override void noSuchMethod(Invocation invocation) { - invocations.add(RecordedInvocation(invocation, stack: StackTrace.current)); + invocations.add(RecordInvocation(invocation, stack: StackTrace.current)); } } /// A [PaintingContext] for tests that use [TestRecordingCanvas]. -class TestRecordingPaintingContext extends ClipContext +class TestRecordPaintingContext extends ClipContext implements PaintingContext { /// Creates a [PaintingContext] for tests that use [TestRecordingCanvas]. - TestRecordingPaintingContext(this.canvas); + TestRecordPaintingContext(this.canvas); @override final Canvas canvas; From aae8eb9f85d0a1c5c1bf127dc022fe3d08d48eca Mon Sep 17 00:00:00 2001 From: Adrian Samoticha Date: Fri, 4 Aug 2023 19:37:48 +0200 Subject: [PATCH 042/151] automatic changes --- example/macos/Podfile.lock | 2 +- example/pubspec.lock | 46 ++++++++++++++-------------- pubspec.lock | 62 ++++++++++++++------------------------ 3 files changed, 47 insertions(+), 63 deletions(-) diff --git a/example/macos/Podfile.lock b/example/macos/Podfile.lock index 2fa81174..0ee0bdce 100644 --- a/example/macos/Podfile.lock +++ b/example/macos/Podfile.lock @@ -38,4 +38,4 @@ SPEC CHECKSUMS: PODFILE CHECKSUM: ff0a9a3ce75ee73f200ca7e2f47745698c917ef9 -COCOAPODS: 1.11.3 +COCOAPODS: 1.12.1 diff --git a/example/pubspec.lock b/example/pubspec.lock index 3dbde41a..dc586a2a 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -37,10 +37,10 @@ packages: dependency: transitive description: name: collection - sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a + sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c" url: "https://pub.dev" source: hosted - version: "1.18.0" + version: "1.17.1" crypto: dependency: transitive description: @@ -128,6 +128,14 @@ packages: url: "https://pub.dev" source: hosted version: "4.0.2" + js: + dependency: transitive + description: + name: js + sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 + url: "https://pub.dev" + source: hosted + version: "0.6.7" lints: dependency: transitive description: @@ -155,18 +163,18 @@ packages: dependency: transitive description: name: matcher - sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e" + sha256: "6501fbd55da300384b768785b83e5ce66991266cec21af89ab9ae7f5ce1c4cbb" url: "https://pub.dev" source: hosted - version: "0.12.16" + version: "0.12.15" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41" + sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724 url: "https://pub.dev" source: hosted - version: "0.5.0" + version: "0.2.0" meta: dependency: transitive description: @@ -280,26 +288,26 @@ packages: dependency: transitive description: name: source_span - sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" + sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250 url: "https://pub.dev" source: hosted - version: "1.10.0" + version: "1.9.1" stack_trace: dependency: transitive description: name: stack_trace - sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" + sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5 url: "https://pub.dev" source: hosted - version: "1.11.1" + version: "1.11.0" stream_channel: dependency: transitive description: name: stream_channel - sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 + sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8" url: "https://pub.dev" source: hosted - version: "2.1.2" + version: "2.1.1" string_scanner: dependency: transitive description: @@ -320,10 +328,10 @@ packages: dependency: transitive description: name: test_api - sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b" + sha256: eb6ac1540b26de412b3403a163d919ba86f6a973fe6cc50ae3541b80092fdcfb url: "https://pub.dev" source: hosted - version: "0.6.1" + version: "0.5.1" typed_data: dependency: transitive description: @@ -404,14 +412,6 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.4" - web: - dependency: transitive - description: - name: web - sha256: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10 - url: "https://pub.dev" - source: hosted - version: "0.1.4-beta" win32: dependency: transitive description: @@ -429,5 +429,5 @@ packages: source: hosted version: "1.0.0" sdks: - dart: ">=3.1.0-185.0.dev <4.0.0" + dart: ">=3.0.0 <4.0.0" flutter: ">=3.10.0" diff --git a/pubspec.lock b/pubspec.lock index 708421c4..dfa8a61c 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -5,18 +5,18 @@ packages: dependency: transitive description: name: _fe_analyzer_shared - sha256: "0816708f5fbcacca324d811297153fe3c8e047beb5c6752e12292d2974c17045" + sha256: ae92f5d747aee634b87f89d9946000c2de774be1d6ac3e58268224348cd0101a url: "https://pub.dev" source: hosted - version: "62.0.0" + version: "61.0.0" analyzer: dependency: transitive description: name: analyzer - sha256: "21862995c9932cd082f89d72ae5f5e2c110d1a0204ad06e4ebaee8307b76b834" + sha256: ea3d8652bda62982addfd92fdc2d0214e5f82e43325104990d4f4c4a2a313562 url: "https://pub.dev" source: hosted - version: "6.0.0" + version: "5.13.0" args: dependency: transitive description: @@ -61,10 +61,10 @@ packages: dependency: transitive description: name: collection - sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a + sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c" url: "https://pub.dev" source: hosted - version: "1.18.0" + version: "1.17.1" convert: dependency: transitive description: @@ -89,14 +89,6 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.3" - dart_internal: - dependency: transitive - description: - name: dart_internal - sha256: "689dccc3d5f62affd339534cca548dce12b3a6b32f0f10861569d3025efc0567" - url: "https://pub.dev" - source: hosted - version: "0.2.9" fake_async: dependency: transitive description: @@ -207,18 +199,18 @@ packages: dependency: transitive description: name: matcher - sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e" + sha256: "6501fbd55da300384b768785b83e5ce66991266cec21af89ab9ae7f5ce1c4cbb" url: "https://pub.dev" source: hosted - version: "0.12.16" + version: "0.12.15" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41" + sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724 url: "https://pub.dev" source: hosted - version: "0.5.0" + version: "0.2.0" meta: dependency: transitive description: @@ -340,26 +332,26 @@ packages: dependency: transitive description: name: source_span - sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" + sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250 url: "https://pub.dev" source: hosted - version: "1.10.0" + version: "1.9.1" stack_trace: dependency: transitive description: name: stack_trace - sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" + sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5 url: "https://pub.dev" source: hosted - version: "1.11.1" + version: "1.11.0" stream_channel: dependency: transitive description: name: stream_channel - sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 + sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8" url: "https://pub.dev" source: hosted - version: "2.1.2" + version: "2.1.1" string_scanner: dependency: transitive description: @@ -380,26 +372,26 @@ packages: dependency: transitive description: name: test - sha256: b9a384c4b9c4966dbf7215e7c033a78db1da7e5dcaf8da9232c5f24735f65652 + sha256: "3dac9aecf2c3991d09b9cdde4f98ded7b30804a88a0d7e4e7e1678e78d6b97f4" url: "https://pub.dev" source: hosted - version: "1.24.5" + version: "1.24.1" test_api: dependency: transitive description: name: test_api - sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b" + sha256: eb6ac1540b26de412b3403a163d919ba86f6a973fe6cc50ae3541b80092fdcfb url: "https://pub.dev" source: hosted - version: "0.6.1" + version: "0.5.1" test_core: dependency: transitive description: name: test_core - sha256: c6a536288535efef8526eea8adfa4e25fdd2849fa7f457ecb2a52099998ce8f7 + sha256: "5138dbffb77b2289ecb12b81c11ba46036590b72a64a7a90d6ffb880f1a29e93" url: "https://pub.dev" source: hosted - version: "0.5.5" + version: "0.5.1" typed_data: dependency: transitive description: @@ -432,14 +424,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.0" - web: - dependency: transitive - description: - name: web - sha256: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10 - url: "https://pub.dev" - source: hosted - version: "0.1.4-beta" web_socket_channel: dependency: transitive description: @@ -465,5 +449,5 @@ packages: source: hosted version: "3.1.2" sdks: - dart: ">=3.1.0-185.0.dev <3.3.0" + dart: ">=3.0.0 <4.0.0" flutter: ">=3.10.0" From 968cd317b2c3935d7cec2f8e1cc2242d7e400054 Mon Sep 17 00:00:00 2001 From: Adrian Samoticha Date: Sun, 6 Aug 2023 21:39:25 +0200 Subject: [PATCH 043/151] =?UTF-8?q?imitate=20macOS=E2=80=99=20push=20butto?= =?UTF-8?q?n=20look?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- example/pubspec.lock | 8 + lib/src/buttons/push_button.dart | 389 +++++++++++++++++++++++++++++-- lib/src/enums/accent_color.dart | 11 + pubspec.lock | 8 + pubspec.yaml | 1 + 5 files changed, 396 insertions(+), 21 deletions(-) create mode 100644 lib/src/enums/accent_color.dart diff --git a/example/pubspec.lock b/example/pubspec.lock index dc586a2a..eab32583 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -112,6 +112,14 @@ packages: url: "https://pub.dev" source: hosted version: "5.1.0" + gradient_borders: + dependency: transitive + description: + name: gradient_borders + sha256: "69eeaff519d145a4c6c213ada1abae386bcc8981a4970d923e478ce7ba19e309" + url: "https://pub.dev" + source: hosted + version: "1.0.0" http: dependency: transitive description: diff --git a/lib/src/buttons/push_button.dart b/lib/src/buttons/push_button.dart index 19180016..d1fbb238 100644 --- a/lib/src/buttons/push_button.dart +++ b/lib/src/buttons/push_button.dart @@ -2,7 +2,9 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/rendering.dart'; +import 'package:gradient_borders/gradient_borders.dart'; import 'package:macos_ui/macos_ui.dart'; +import 'package:macos_ui/src/enums/accent_color.dart'; import 'package:macos_ui/src/library.dart'; const _kMiniButtonSize = Size(26.0, 11.0); @@ -300,29 +302,64 @@ class PushButtonState extends State @visibleForTesting bool buttonHeldDown = false; - @override - Widget build(BuildContext context) { - assert(debugCheckHasMacosTheme(context)); + AccentColor get _accentColor => AccentColor.blue; // TODO: make this dynamic + + BoxDecoration _getBoxDecoration() { + return _BoxDecorationBuilder.buildBoxDecoration( + accentColor: _accentColor, + isEnabled: widget.enabled, + isDarkModeEnabled: MacosTheme.of(context).brightness.isDark, + isSecondary: widget.secondary ?? false, + ); + } + + Color _getBackgroundColor() { final bool enabled = widget.enabled; final bool isSecondary = widget.secondary != null && widget.secondary!; final MacosThemeData theme = MacosTheme.of(context); - final Color backgroundColor = MacosDynamicColor.resolve( + + return MacosDynamicColor.resolve( widget.color ?? - (isSecondary - ? theme.pushButtonTheme.secondaryColor! - : theme.pushButtonTheme.color!), + _BoxDecorationBuilder.getGradientColors( + accentColor: _accentColor, + isEnabled: enabled, + isDarkModeEnabled: theme.brightness.isDark, + isSecondary: isSecondary) + .first, context, ); + } + + Color _getForegroundColor(Color backgroundColor) { + final MacosThemeData theme = MacosTheme.of(context); - final disabledColor = !isSecondary - ? backgroundColor.withOpacity(0.5) - : backgroundColor.withOpacity(0.25); + final blendedBackgroundColor = Color.lerp( + theme.canvasColor, + backgroundColor, + backgroundColor.opacity, + )!; - final Color foregroundColor = widget.enabled - ? textLuminance(backgroundColor) - : theme.brightness.isDark - ? const Color.fromRGBO(255, 255, 255, 0.25) - : const Color.fromRGBO(0, 0, 0, 0.25); + return widget.enabled + ? textLuminance(blendedBackgroundColor) + : textLuminance(blendedBackgroundColor).withOpacity(0.25); + } + + @override + Widget build(BuildContext context) { + assert(debugCheckHasMacosTheme(context)); + final bool enabled = widget.enabled; + final MacosThemeData theme = MacosTheme.of(context); + // TODO: remove this + // final Color backgroundColor = MacosDynamicColor.resolve( + // widget.color ?? + // (isSecondary + // ? theme.pushButtonTheme.secondaryColor! + // : theme.pushButtonTheme.color!), + // context, + // ); + final Color backgroundColor = _getBackgroundColor(); + + final Color foregroundColor = _getForegroundColor(backgroundColor); final baseStyle = theme.typography.body.copyWith(color: foregroundColor); @@ -342,12 +379,8 @@ class PushButtonState extends State child: FadeTransition( opacity: _opacityAnimation, child: DecoratedBox( - decoration: ShapeDecoration( - shape: RoundedRectangleBorder( - borderRadius: widget.controlSize.borderRadius, - ), - // color: !enabled ? disabledColor : backgroundColor, - color: enabled ? backgroundColor : disabledColor, + decoration: _getBoxDecoration().copyWith( + borderRadius: widget.controlSize.borderRadius, ), child: Padding( padding: widget.controlSize.padding, @@ -369,3 +402,317 @@ class PushButtonState extends State ); } } + +class _BoxDecorationBuilder { + /// Gets the colors to use for the [BoxDecoration]’s gradient based on the + /// provided [accentColor], [isEnabled], and [isDarkModeEnabled] properties. + static List getGradientColors({ + required AccentColor accentColor, + required bool isEnabled, + required bool isDarkModeEnabled, + required bool isSecondary, + }) { + final isEnabledFactor = isEnabled ? 1.0 : 0.5; + + if (isSecondary) { + return isDarkModeEnabled + ? [ + MacosColor.fromRGBO(255, 255, 255, 0.251 * isEnabledFactor), + MacosColor.fromRGBO(255, 255, 255, 0.251 * isEnabledFactor), + ] + : [ + MacosColor.fromRGBO(255, 255, 255, 1.0 * isEnabledFactor), + MacosColor.fromRGBO(255, 255, 255, 1.0 * isEnabledFactor), + ]; + } + + if (isDarkModeEnabled) { + switch (accentColor) { + case AccentColor.blue: + return [ + MacosColor.fromRGBO(0, 114, 238, 1.0 * isEnabledFactor), + MacosColor.fromRGBO(0, 94, 211, 1.0 * isEnabledFactor), + ]; + + case AccentColor.purple: + return [ + MacosColor.fromRGBO(135, 65, 131, 1.0 * isEnabledFactor), + MacosColor.fromRGBO(120, 57, 116, 1.0 * isEnabledFactor), + ]; + + case AccentColor.pink: + return [ + MacosColor.fromRGBO(188, 52, 105, 1.0 * isEnabledFactor), + MacosColor.fromRGBO(168, 46, 93, 1.0 * isEnabledFactor), + ]; + + case AccentColor.red: + return [ + MacosColor.fromRGBO(186, 53, 46, 1.0 * isEnabledFactor), + MacosColor.fromRGBO(166, 48, 41, 1.0 * isEnabledFactor), + ]; + + case AccentColor.orange: + return [ + MacosColor.fromRGBO(212, 133, 33, 1.0 * isEnabledFactor), + MacosColor.fromRGBO(189, 118, 30, 1.0 * isEnabledFactor), + ]; + + case AccentColor.yellow: + return [ + MacosColor.fromRGBO(229, 203, 35, 1.0 * isEnabledFactor), + MacosColor.fromRGBO(204, 179, 21, 1.0 * isEnabledFactor), + ]; + + case AccentColor.green: + return [ + MacosColor.fromRGBO(58, 138, 46, 1.0 * isEnabledFactor), + MacosColor.fromRGBO(52, 123, 39, 1.0 * isEnabledFactor), + ]; + + case AccentColor.graphite: + return [ + MacosColor.fromRGBO(64, 64, 64, 1.0 * isEnabledFactor), + MacosColor.fromRGBO(57, 57, 57, 1.0 * isEnabledFactor), + ]; + + default: + throw UnimplementedError(); + } + } else { + switch (accentColor) { + case AccentColor.blue: + return [ + MacosColor.fromRGBO(39, 125, 255, 1.0 * isEnabledFactor), + MacosColor.fromRGBO(1, 101, 255, 1.0 * isEnabledFactor), + ]; + + case AccentColor.purple: + return [ + MacosColor.fromRGBO(148, 73, 143, 1.0 * isEnabledFactor), + MacosColor.fromRGBO(128, 39, 121, 1.0 * isEnabledFactor), + ]; + + case AccentColor.pink: + return [ + MacosColor.fromRGBO(212, 71, 125, 1.0 * isEnabledFactor), + MacosColor.fromRGBO(203, 36, 101, 1.0 * isEnabledFactor), + ]; + + case AccentColor.red: + return [ + MacosColor.fromRGBO(198, 64, 57, 1.0 * isEnabledFactor), + MacosColor.fromRGBO(188, 29, 21, 1.0 * isEnabledFactor), + ]; + + case AccentColor.orange: + return [ + MacosColor.fromRGBO(237, 154, 51, 1.0 * isEnabledFactor), + MacosColor.fromRGBO(234, 136, 13, 1.0 * isEnabledFactor), + ]; + + case AccentColor.yellow: + return [ + MacosColor.fromRGBO(242, 211, 61, 1.0 * isEnabledFactor), + MacosColor.fromRGBO(240, 203, 25, 1.0 * isEnabledFactor), + ]; + + case AccentColor.green: + return [ + MacosColor.fromRGBO(77, 161, 63, 1.0 * isEnabledFactor), + MacosColor.fromRGBO(45, 143, 28, 1.0 * isEnabledFactor), + ]; + + case AccentColor.graphite: + return [ + MacosColor.fromRGBO(86, 86, 86, 1.0 * isEnabledFactor), + MacosColor.fromRGBO(55, 55, 55, 1.0 * isEnabledFactor), + ]; + + default: + throw UnimplementedError(); + } + } + } + + /// Gets the shadow to use for the [BoxDecoration] based on the provided + /// [accentColor], [isEnabled], and [isDarkModeEnabled] properties. + static List _getShadow({ + required AccentColor accentColor, + required bool isEnabled, + required bool isDarkModeEnabled, + required bool isSecondary, + }) { + final isEnabledFactor = isEnabled ? 1.0 : 0.5; + + if (isSecondary) { + return isDarkModeEnabled + ? [ + BoxShadow( + color: MacosColor.fromRGBO(0, 0, 0, 0.4 * isEnabledFactor), + blurRadius: 0.5, + offset: Offset.zero, + spreadRadius: 0.0, + blurStyle: BlurStyle.outer, + ), + ] + : [ + BoxShadow( + color: MacosColor.fromRGBO(0, 0, 0, 0.4 * isEnabledFactor), + blurRadius: 0.5, + offset: isEnabled ? const Offset(0.0, 0.3) : Offset.zero, + spreadRadius: 0.0, + blurStyle: isEnabled ? BlurStyle.normal : BlurStyle.outer, + ), + ]; + } + + if (isDarkModeEnabled) { + return [ + BoxShadow( + color: MacosColor.fromRGBO(0, 0, 0, 0.4 * isEnabledFactor), + blurRadius: 0.5, + offset: isEnabled ? const Offset(0.0, 0.3) : Offset.zero, + spreadRadius: 0.0, + blurStyle: isEnabled ? BlurStyle.normal : BlurStyle.outer, + ), + ]; + } else { + switch (accentColor) { + case AccentColor.blue: + return [ + BoxShadow( + color: MacosColor.fromRGBO(0, 103, 255, 0.21 * isEnabledFactor), + blurRadius: 0.5, + offset: isEnabled ? const Offset(0.0, 0.3) : Offset.zero, + spreadRadius: 0.0, + blurStyle: isEnabled ? BlurStyle.normal : BlurStyle.outer, + ), + ]; + + case AccentColor.purple: + return [ + BoxShadow( + color: MacosColor.fromRGBO(139, 29, 125, 0.21 * isEnabledFactor), + blurRadius: 0.5, + offset: isEnabled ? const Offset(0.0, 0.3) : Offset.zero, + spreadRadius: 0.0, + blurStyle: isEnabled ? BlurStyle.normal : BlurStyle.outer, + ), + ]; + + case AccentColor.pink: + return [ + BoxShadow( + color: MacosColor.fromRGBO(222, 0, 101, 0.21 * isEnabledFactor), + blurRadius: 0.5, + offset: isEnabled ? const Offset(0.0, 0.3) : Offset.zero, + spreadRadius: 0.0, + blurStyle: isEnabled ? BlurStyle.normal : BlurStyle.outer, + ), + ]; + + case AccentColor.red: + return [ + BoxShadow( + color: MacosColor.fromRGBO(188, 29, 21, 0.35 * isEnabledFactor), + blurRadius: 0.5, + offset: isEnabled ? const Offset(0.0, 0.3) : Offset.zero, + spreadRadius: 0.0, + blurStyle: isEnabled ? BlurStyle.normal : BlurStyle.outer, + ), + ]; + + case AccentColor.orange: + return [ + BoxShadow( + color: MacosColor.fromRGBO(234, 136, 13, 0.35 * isEnabledFactor), + blurRadius: 0.5, + offset: isEnabled ? const Offset(0.0, 0.3) : Offset.zero, + spreadRadius: 0.0, + blurStyle: isEnabled ? BlurStyle.normal : BlurStyle.outer, + ), + ]; + + case AccentColor.yellow: + return [ + BoxShadow( + color: MacosColor.fromRGBO(240, 203, 25, 0.35 * isEnabledFactor), + blurRadius: 0.5, + offset: isEnabled ? const Offset(0.0, 0.3) : Offset.zero, + spreadRadius: 0.0, + blurStyle: isEnabled ? BlurStyle.normal : BlurStyle.outer, + ), + ]; + + case AccentColor.green: + return [ + BoxShadow( + color: MacosColor.fromRGBO(45, 143, 28, 0.35 * isEnabledFactor), + blurRadius: 0.5, + offset: isEnabled ? const Offset(0.0, 0.3) : Offset.zero, + spreadRadius: 0.0, + blurStyle: isEnabled ? BlurStyle.normal : BlurStyle.outer, + ), + ]; + + case AccentColor.graphite: + return [ + BoxShadow( + color: MacosColor.fromRGBO(55, 55, 55, 0.35 * isEnabledFactor), + blurRadius: 0.5, + offset: isEnabled ? const Offset(0.0, 0.3) : Offset.zero, + spreadRadius: 0.0, + blurStyle: isEnabled ? BlurStyle.normal : BlurStyle.outer, + ), + ]; + + default: + throw UnimplementedError(); + } + } + } + + /// Builds a [BoxDecoration] for a [MacosPushButton]. + static BoxDecoration buildBoxDecoration({ + required AccentColor accentColor, + required bool isEnabled, + required bool isDarkModeEnabled, + required bool isSecondary, + }) { + final isEnabledFactor = isEnabled ? 1.0 : 0.5; + + return BoxDecoration( + border: isDarkModeEnabled + ? GradientBoxBorder( + gradient: LinearGradient( + colors: [ + MacosColor.fromRGBO(255, 255, 255, 0.43 * isEnabledFactor), + const MacosColor.fromRGBO(255, 255, 255, 0.0), + ], + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + stops: const [0.0, 0.2], + ), + width: 0.7, + ) + : null, + gradient: LinearGradient( + colors: getGradientColors( + accentColor: accentColor, + isEnabled: isEnabled, + isDarkModeEnabled: isDarkModeEnabled, + isSecondary: isSecondary, + ), + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + ), + boxShadow: _getShadow( + accentColor: accentColor, + isEnabled: isEnabled, + isDarkModeEnabled: isDarkModeEnabled, + isSecondary: isSecondary, + ), + ); + } +} diff --git a/lib/src/enums/accent_color.dart b/lib/src/enums/accent_color.dart new file mode 100644 index 00000000..ed21cfe8 --- /dev/null +++ b/lib/src/enums/accent_color.dart @@ -0,0 +1,11 @@ +// TODO: document this +enum AccentColor { + blue, + purple, + pink, + red, + orange, + yellow, + green, + graphite, +} diff --git a/pubspec.lock b/pubspec.lock index dfa8a61c..be900083 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -139,6 +139,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.1" + gradient_borders: + dependency: "direct main" + description: + name: gradient_borders + sha256: "69eeaff519d145a4c6c213ada1abae386bcc8981a4970d923e478ce7ba19e309" + url: "https://pub.dev" + source: hosted + version: "1.0.0" http_multi_server: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 13865b31..7b208ea5 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -12,6 +12,7 @@ dependencies: flutter: sdk: flutter macos_window_utils: ^1.1.3 + gradient_borders: ^1.0.0 dev_dependencies: flutter_test: From b93084c1bff0e4f9f5d630d5c67fa5e93a3564ff Mon Sep 17 00:00:00 2001 From: Adrian Samoticha Date: Sun, 6 Aug 2023 22:51:54 +0200 Subject: [PATCH 044/151] make push button appear secondary when window resigns main state --- lib/src/buttons/push_button.dart | 15 ++++++- lib/src/utils.dart | 72 ++++++++++++++++++++++++++++++++ 2 files changed, 86 insertions(+), 1 deletion(-) diff --git a/lib/src/buttons/push_button.dart b/lib/src/buttons/push_button.dart index d1fbb238..3aa9cfde 100644 --- a/lib/src/buttons/push_button.dart +++ b/lib/src/buttons/push_button.dart @@ -1,5 +1,7 @@ // ignore_for_file: prefer_if_null_operators +import 'dart:async'; + import 'package:flutter/foundation.dart'; import 'package:flutter/rendering.dart'; import 'package:gradient_borders/gradient_borders.dart'; @@ -237,6 +239,8 @@ class PushButtonState extends State late AnimationController _animationController; late Animation _opacityAnimation; + late StreamSubscription _onWindowMainStateChangedStreamSubcription; + @override void initState() { super.initState(); @@ -249,6 +253,10 @@ class PushButtonState extends State .drive(CurveTween(curve: Curves.decelerate)) .drive(_opacityTween); _setTween(); + + _onWindowMainStateChangedStreamSubcription = WindowMainStateListener + .instance.onChangedStream + .listen((_) => setState(() {})); } @override @@ -296,6 +304,7 @@ class PushButtonState extends State @override void dispose() { _animationController.dispose(); + _onWindowMainStateChangedStreamSubcription.cancel(); super.dispose(); } @@ -305,11 +314,15 @@ class PushButtonState extends State AccentColor get _accentColor => AccentColor.blue; // TODO: make this dynamic BoxDecoration _getBoxDecoration() { + // If the window isn’t currently the main window (that is, it is not in + // focus), make the button look as if it was a secondary button. + final isWindowMain = WindowMainStateListener.instance.isWindowMain; + return _BoxDecorationBuilder.buildBoxDecoration( accentColor: _accentColor, isEnabled: widget.enabled, isDarkModeEnabled: MacosTheme.of(context).brightness.isDark, - isSecondary: widget.secondary ?? false, + isSecondary: !isWindowMain || (widget.secondary ?? false), ); } diff --git a/lib/src/utils.dart b/lib/src/utils.dart index 1f2d8a1e..dcb8212a 100644 --- a/lib/src/utils.dart +++ b/lib/src/utils.dart @@ -1,3 +1,4 @@ +import 'dart:async'; import 'dart:io'; import 'package:flutter/foundation.dart'; @@ -86,3 +87,74 @@ class MacOSBrightnessOverrideHandler { _lastBrightness = currentBrightness; } } + +/// A class that listens for changes to the application's window being the main +/// window, and notifies listeners. +class WindowMainStateListener { + /// A shared instance of [WindowMainStateListener]. + static final instance = WindowMainStateListener(); + + /// A [NSWindowDelegateHandle], to be used when deiniting the listener. + NSWindowDelegateHandle? handle; + + /// Whether the window is currently the main window. + bool _isWindowMain = + true; // TODO: Initialize properly once macos_window_utils supports that, + // see https://github.com/macosui/macos_window_utils.dart/issues/31. + + /// Whether the window is currently the main window. + bool get isWindowMain => _isWindowMain; + + /// Notifies listeners when the window’s main state changes. + final _windowMainStateStreamController = StreamController.broadcast(); + + /// A stream of the window’s main state. Emits a new value whenever the state + /// changes. + Stream get onChangedStream => _windowMainStateStreamController.stream; + + /// Initializes the listener. This should only be called once. + void _init() { + final delegate = _WindowMainStateListenerDelegate( + onWindowDidBecomeMain: () { + _isWindowMain = true; + _windowMainStateStreamController.add(true); + }, + onWindowDidResignMain: () { + _isWindowMain = false; + _windowMainStateStreamController.add(false); + }, + ); + handle = WindowManipulator.addNSWindowDelegate(delegate); + } + + /// Deinitializes the listener. + void deinit() { + handle?.removeFromHandler(); + } + + /// A class that listens for changes to the application's window being the + /// main window, and notifies listeners. + WindowMainStateListener() { + _init(); + } +} + +/// The [NSWindowDelegate] used by [WindowMainStateListener]. +class _WindowMainStateListenerDelegate extends NSWindowDelegate { + _WindowMainStateListenerDelegate({ + required this.onWindowDidBecomeMain, + required this.onWindowDidResignMain, + }); + + /// Called when the window becomes the main window. + final void Function() onWindowDidBecomeMain; + + /// Called when the window resigns as the main window. + final void Function() onWindowDidResignMain; + + @override + void windowDidBecomeMain() => onWindowDidBecomeMain(); + + @override + void windowDidResignMain() => onWindowDidResignMain(); +} From b4d22f1ff340547c365911ae42f42bfd3768d85b Mon Sep 17 00:00:00 2001 From: Adrian Samoticha Date: Sun, 6 Aug 2023 22:58:36 +0200 Subject: [PATCH 045/151] migrate to stream builder --- lib/src/buttons/push_button.dart | 45 +++++++++++++++----------------- 1 file changed, 21 insertions(+), 24 deletions(-) diff --git a/lib/src/buttons/push_button.dart b/lib/src/buttons/push_button.dart index 3aa9cfde..ad7be046 100644 --- a/lib/src/buttons/push_button.dart +++ b/lib/src/buttons/push_button.dart @@ -239,8 +239,6 @@ class PushButtonState extends State late AnimationController _animationController; late Animation _opacityAnimation; - late StreamSubscription _onWindowMainStateChangedStreamSubcription; - @override void initState() { super.initState(); @@ -253,10 +251,6 @@ class PushButtonState extends State .drive(CurveTween(curve: Curves.decelerate)) .drive(_opacityTween); _setTween(); - - _onWindowMainStateChangedStreamSubcription = WindowMainStateListener - .instance.onChangedStream - .listen((_) => setState(() {})); } @override @@ -304,7 +298,6 @@ class PushButtonState extends State @override void dispose() { _animationController.dispose(); - _onWindowMainStateChangedStreamSubcription.cancel(); super.dispose(); } @@ -391,23 +384,27 @@ class PushButtonState extends State constraints: widget.controlSize.constraints, child: FadeTransition( opacity: _opacityAnimation, - child: DecoratedBox( - decoration: _getBoxDecoration().copyWith( - borderRadius: widget.controlSize.borderRadius, - ), - child: Padding( - padding: widget.controlSize.padding, - child: Align( - alignment: widget.alignment, - widthFactor: 1.0, - heightFactor: 1.0, - child: DefaultTextStyle( - style: widget.controlSize.textStyle(baseStyle), - child: widget.child, - ), - ), - ), - ), + child: StreamBuilder( + stream: WindowMainStateListener.instance.onChangedStream, + builder: (context, _) { + return DecoratedBox( + decoration: _getBoxDecoration().copyWith( + borderRadius: widget.controlSize.borderRadius, + ), + child: Padding( + padding: widget.controlSize.padding, + child: Align( + alignment: widget.alignment, + widthFactor: 1.0, + heightFactor: 1.0, + child: DefaultTextStyle( + style: widget.controlSize.textStyle(baseStyle), + child: widget.child, + ), + ), + ), + ); + }), ), ), ), From afc3a9c9006d68252116dd3944612c1fa8120a78 Mon Sep 17 00:00:00 2001 From: Adrian Samoticha Date: Sun, 6 Aug 2023 23:05:38 +0200 Subject: [PATCH 046/151] fix incorrect text color in push button when window resigns main state --- lib/src/buttons/push_button.dart | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/lib/src/buttons/push_button.dart b/lib/src/buttons/push_button.dart index ad7be046..1979204b 100644 --- a/lib/src/buttons/push_button.dart +++ b/lib/src/buttons/push_button.dart @@ -324,14 +324,18 @@ class PushButtonState extends State final bool isSecondary = widget.secondary != null && widget.secondary!; final MacosThemeData theme = MacosTheme.of(context); + // If the window isn’t currently the main window (that is, it is not in + // focus), make the button look as if it was a secondary button. + final isWindowMain = WindowMainStateListener.instance.isWindowMain; + return MacosDynamicColor.resolve( widget.color ?? _BoxDecorationBuilder.getGradientColors( - accentColor: _accentColor, - isEnabled: enabled, - isDarkModeEnabled: theme.brightness.isDark, - isSecondary: isSecondary) - .first, + accentColor: _accentColor, + isEnabled: enabled, + isDarkModeEnabled: theme.brightness.isDark, + isSecondary: isSecondary || !isWindowMain, + ).first, context, ); } @@ -363,11 +367,6 @@ class PushButtonState extends State // : theme.pushButtonTheme.color!), // context, // ); - final Color backgroundColor = _getBackgroundColor(); - - final Color foregroundColor = _getForegroundColor(backgroundColor); - - final baseStyle = theme.typography.body.copyWith(color: foregroundColor); return MouseRegion( cursor: widget.mouseCursor!, @@ -387,6 +386,14 @@ class PushButtonState extends State child: StreamBuilder( stream: WindowMainStateListener.instance.onChangedStream, builder: (context, _) { + final Color backgroundColor = _getBackgroundColor(); + + final Color foregroundColor = + _getForegroundColor(backgroundColor); + + final baseStyle = + theme.typography.body.copyWith(color: foregroundColor); + return DecoratedBox( decoration: _getBoxDecoration().copyWith( borderRadius: widget.controlSize.borderRadius, From ab3092e9022a47c3b2a93b25df8b6b3da333355b Mon Sep 17 00:00:00 2001 From: Adrian Samoticha Date: Mon, 7 Aug 2023 12:27:17 +0200 Subject: [PATCH 047/151] add missing comma --- lib/src/buttons/push_button.dart | 53 ++++++++++++++++---------------- 1 file changed, 27 insertions(+), 26 deletions(-) diff --git a/lib/src/buttons/push_button.dart b/lib/src/buttons/push_button.dart index 1979204b..2f714bb6 100644 --- a/lib/src/buttons/push_button.dart +++ b/lib/src/buttons/push_button.dart @@ -384,34 +384,35 @@ class PushButtonState extends State child: FadeTransition( opacity: _opacityAnimation, child: StreamBuilder( - stream: WindowMainStateListener.instance.onChangedStream, - builder: (context, _) { - final Color backgroundColor = _getBackgroundColor(); - - final Color foregroundColor = - _getForegroundColor(backgroundColor); - - final baseStyle = - theme.typography.body.copyWith(color: foregroundColor); - - return DecoratedBox( - decoration: _getBoxDecoration().copyWith( - borderRadius: widget.controlSize.borderRadius, - ), - child: Padding( - padding: widget.controlSize.padding, - child: Align( - alignment: widget.alignment, - widthFactor: 1.0, - heightFactor: 1.0, - child: DefaultTextStyle( - style: widget.controlSize.textStyle(baseStyle), - child: widget.child, - ), + stream: WindowMainStateListener.instance.onChangedStream, + builder: (context, _) { + final Color backgroundColor = _getBackgroundColor(); + + final Color foregroundColor = + _getForegroundColor(backgroundColor); + + final baseStyle = + theme.typography.body.copyWith(color: foregroundColor); + + return DecoratedBox( + decoration: _getBoxDecoration().copyWith( + borderRadius: widget.controlSize.borderRadius, + ), + child: Padding( + padding: widget.controlSize.padding, + child: Align( + alignment: widget.alignment, + widthFactor: 1.0, + heightFactor: 1.0, + child: DefaultTextStyle( + style: widget.controlSize.textStyle(baseStyle), + child: widget.child, ), ), - ); - }), + ), + ); + }, + ), ), ), ), From cb701132dce2169a1302c3d51f95f88004eaf3f6 Mon Sep 17 00:00:00 2001 From: Adrian Samoticha Date: Mon, 7 Aug 2023 14:02:36 +0200 Subject: [PATCH 048/151] implement `AccentColorListener` --- .../Flutter/GeneratedPluginRegistrant.swift | 2 + example/macos/Podfile.lock | 6 + example/pubspec.lock | 16 +++ lib/src/utils.dart | 133 ++++++++++++++++++ pubspec.lock | 24 ++++ pubspec.yaml | 1 + 6 files changed, 182 insertions(+) diff --git a/example/macos/Flutter/GeneratedPluginRegistrant.swift b/example/macos/Flutter/GeneratedPluginRegistrant.swift index 0179c12c..1e64f18d 100644 --- a/example/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/example/macos/Flutter/GeneratedPluginRegistrant.swift @@ -5,12 +5,14 @@ import FlutterMacOS import Foundation +import appkit_ui_element_colors import macos_ui import macos_window_utils import path_provider_foundation import url_launcher_macos func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + AppkitUiElementColorsPlugin.register(with: registry.registrar(forPlugin: "AppkitUiElementColorsPlugin")) MacOSUiPlugin.register(with: registry.registrar(forPlugin: "MacOSUiPlugin")) MacOSWindowUtilsPlugin.register(with: registry.registrar(forPlugin: "MacOSWindowUtilsPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) diff --git a/example/macos/Podfile.lock b/example/macos/Podfile.lock index 0ee0bdce..6aa1d86e 100644 --- a/example/macos/Podfile.lock +++ b/example/macos/Podfile.lock @@ -1,4 +1,6 @@ PODS: + - appkit_ui_element_colors (1.0.0): + - FlutterMacOS - FlutterMacOS (1.0.0) - macos_ui (0.1.0): - FlutterMacOS @@ -11,6 +13,7 @@ PODS: - FlutterMacOS DEPENDENCIES: + - appkit_ui_element_colors (from `Flutter/ephemeral/.symlinks/plugins/appkit_ui_element_colors/macos`) - 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`) @@ -18,6 +21,8 @@ DEPENDENCIES: - url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`) EXTERNAL SOURCES: + appkit_ui_element_colors: + :path: Flutter/ephemeral/.symlinks/plugins/appkit_ui_element_colors/macos FlutterMacOS: :path: Flutter/ephemeral macos_ui: @@ -30,6 +35,7 @@ EXTERNAL SOURCES: :path: Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos SPEC CHECKSUMS: + appkit_ui_element_colors: 39bb2d80be3f19b152ccf4c70d5bbe6cba43d74a FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24 macos_ui: 6229a8922cd97bafb7d9636c8eb8dfb0744183ca macos_window_utils: 933f91f64805e2eb91a5bd057cf97cd097276663 diff --git a/example/pubspec.lock b/example/pubspec.lock index eab32583..f642de3a 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -1,6 +1,14 @@ # Generated by pub # See https://dart.dev/tools/pub/glossary#lockfile packages: + appkit_ui_element_colors: + dependency: transitive + description: + name: appkit_ui_element_colors + sha256: c3e50f900aae314d339de489535736238627071457c4a4a2dbbb1545b4f04f22 + url: "https://pub.dev" + source: hosted + version: "1.0.0" async: dependency: transitive description: @@ -57,6 +65,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.5" + equatable: + dependency: transitive + description: + name: equatable + sha256: c2b87cb7756efdf69892005af546c56c0b5037f54d2a88269b4f347a505e3ca2 + url: "https://pub.dev" + source: hosted + version: "2.0.5" fake_async: dependency: transitive description: diff --git a/lib/src/utils.dart b/lib/src/utils.dart index dcb8212a..d71b8cd4 100644 --- a/lib/src/utils.dart +++ b/lib/src/utils.dart @@ -1,8 +1,11 @@ import 'dart:async'; import 'dart:io'; +import 'dart:math'; +import 'package:appkit_ui_element_colors/appkit_ui_element_colors.dart'; import 'package:flutter/foundation.dart'; import 'package:macos_ui/macos_ui.dart'; +import 'package:macos_ui/src/enums/accent_color.dart'; import 'package:macos_ui/src/library.dart'; /// Asserts that the given context has a [MacosTheme] ancestor. @@ -158,3 +161,133 @@ class _WindowMainStateListenerDelegate extends NSWindowDelegate { @override void windowDidResignMain() => onWindowDidResignMain(); } + +/// A class that listens to accent color changes. +class AccentColorListener { + /// A shared instance of [AccentColorListener]. + static final instance = AccentColorListener(); + + /// A map which maps hue components of the [UiElementColor.controlAccentColor] + /// color captured with the [NSAppearanceName.aqua] appearance in the + /// [NSColorSpace.genericRGB] color space to the corresponding [AccentColor]. + static final hueComponentToAccentColor = { + 0.6085324903200698: AccentColor.blue, + 0.8285987697113538: AccentColor.purple, + 0.9209523937489168: AccentColor.pink, + 0.9861913496946438: AccentColor.red, + 0.06543037411201169: AccentColor.orange, + 0.11813830353929083: AccentColor.yellow, + 0.29428158007138466: AccentColor.green, + 0.0: AccentColor.graphite, + }; + + /// The currently active accent color. + AccentColor? _currentAccentColor; + + /// The currently active accent color. + AccentColor? get currentAccentColor => _currentAccentColor; + + /// Notifies listeners when the accent color changes. + final _accentColorStreamController = StreamController.broadcast(); + + /// An accent color stream. Emits a new value whenever the accent color + /// changes. + Stream get onChangedStream => _accentColorStreamController.stream; + + /// A stream subcription for the [SystemColorObserver] stream. + StreamSubscription? _systemColorObserverStreamSubscription; + + /// Initializes this class. + void _init() { + _initCurrentAccentColor(); + _initSystemColorObserver(); + } + + /// Deinitializes this class. + void deinit() { + _systemColorObserverStreamSubscription?.cancel(); + } + + /// Initializes the current accent color. This method is to be called whenever + /// a change is detected. + Future _initCurrentAccentColor() async { + final hueComponent = await _getHueComponent(); + _currentAccentColor = _resolveAccentColorFromHueComponent(hueComponent); + _accentColorStreamController.add(null); + } + + /// Initializes the current system color observer. This method may only be + /// called once. + void _initSystemColorObserver() { + assert(_systemColorObserverStreamSubscription == null); + + _systemColorObserverStreamSubscription = + AppkitUiElementColors.systemColorObserver.stream.listen((_) { + _initCurrentAccentColor(); + _accentColorStreamController.add(null); + }); + } + + /// Returns the hue component of the currently active accent color on macOS. + Future _getHueComponent() async { + final color = await AppkitUiElementColors.getColorComponents( + uiElementColor: UiElementColor.controlAccentColor, + components: const { + NSColorComponent.hueComponent, + }, + colorSpace: NSColorSpace.genericRGB, + appearance: NSAppearanceName.aqua, + ); + + assert(color.containsKey("hueComponent")); + + return color["hueComponent"]!; + } + + /// Returns the [AccentColor] which corresponds to the provided + /// [hueComponent]. + AccentColor _resolveAccentColorFromHueComponent(double hueComponent) { + if (hueComponentToAccentColor.containsKey(hueComponent)) { + return hueComponentToAccentColor[hueComponent]!; + } + + // ignore: avoid_print + print( + 'Warning: Falling back on slow accent color resolution. It’s possible ' + 'that the accent colors have changed in a recent version of macOS, thus ' + 'invalidating macos_ui’s accent colors, which were captured in macOS ' + 'Ventura. If you see this message, please notify a maintainer of the ' + 'macos_ui package.', + ); + + return _slowlyResolveAccentColorFromHueComponent(hueComponent); + } + + /// This is a fallback method in case the above method fails. + AccentColor _slowlyResolveAccentColorFromHueComponent(double hueComponent) { + final entries = hueComponentToAccentColor.entries; + var lowestDistance = double.maxFinite; + var toBeReturnedAccentColor = AccentColor.values.first; + + for (var entry in entries) { + final distance = _distanceBetweenHueComponents(hueComponent, entry.key); + if (distance < lowestDistance) { + lowestDistance = distance; + toBeReturnedAccentColor = entry.value; + } + } + + return toBeReturnedAccentColor; + } + + /// Returns the distance between two hue components. + double _distanceBetweenHueComponents(double component1, double component2) { + final rawDifference = (component1 - component2).abs(); + return sin(rawDifference * pi); + } + + /// A class that listens to accent color changes. + AccentColorListener() { + _init(); + } +} diff --git a/pubspec.lock b/pubspec.lock index be900083..018f3b58 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -17,6 +17,14 @@ packages: url: "https://pub.dev" source: hosted version: "5.13.0" + appkit_ui_element_colors: + dependency: "direct main" + description: + name: appkit_ui_element_colors + sha256: c3e50f900aae314d339de489535736238627071457c4a4a2dbbb1545b4f04f22 + url: "https://pub.dev" + source: hosted + version: "1.0.0" args: dependency: transitive description: @@ -89,6 +97,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.3" + equatable: + dependency: transitive + description: + name: equatable + sha256: c2b87cb7756efdf69892005af546c56c0b5037f54d2a88269b4f347a505e3ca2 + url: "https://pub.dev" + source: hosted + version: "2.0.5" fake_async: dependency: transitive description: @@ -267,6 +283,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.8.3" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + sha256: "43798d895c929056255600343db8f049921cbec94d31ec87f1dc5c16c01935dd" + url: "https://pub.dev" + source: hosted + version: "2.1.5" pool: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 7b208ea5..64a10b1e 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -13,6 +13,7 @@ dependencies: sdk: flutter macos_window_utils: ^1.1.3 gradient_borders: ^1.0.0 + appkit_ui_element_colors: ^1.0.0 dev_dependencies: flutter_test: From 6e77ab91c1f788dd90538ea82127567266ccc199 Mon Sep 17 00:00:00 2001 From: Adrian Samoticha Date: Mon, 7 Aug 2023 14:02:47 +0200 Subject: [PATCH 049/151] make push button listen to accent color changes --- lib/src/buttons/push_button.dart | 68 +++++++++++++++++--------------- 1 file changed, 37 insertions(+), 31 deletions(-) diff --git a/lib/src/buttons/push_button.dart b/lib/src/buttons/push_button.dart index 2f714bb6..27b7d4d5 100644 --- a/lib/src/buttons/push_button.dart +++ b/lib/src/buttons/push_button.dart @@ -2,6 +2,7 @@ import 'dart:async'; +import 'package:appkit_ui_element_colors/appkit_ui_element_colors.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/rendering.dart'; import 'package:gradient_borders/gradient_borders.dart'; @@ -304,7 +305,8 @@ class PushButtonState extends State @visibleForTesting bool buttonHeldDown = false; - AccentColor get _accentColor => AccentColor.blue; // TODO: make this dynamic + AccentColor get _accentColor => + AccentColorListener.instance.currentAccentColor ?? AccentColor.blue; BoxDecoration _getBoxDecoration() { // If the window isn’t currently the main window (that is, it is not in @@ -383,36 +385,40 @@ class PushButtonState extends State constraints: widget.controlSize.constraints, child: FadeTransition( opacity: _opacityAnimation, - child: StreamBuilder( - stream: WindowMainStateListener.instance.onChangedStream, - builder: (context, _) { - final Color backgroundColor = _getBackgroundColor(); - - final Color foregroundColor = - _getForegroundColor(backgroundColor); - - final baseStyle = - theme.typography.body.copyWith(color: foregroundColor); - - return DecoratedBox( - decoration: _getBoxDecoration().copyWith( - borderRadius: widget.controlSize.borderRadius, - ), - child: Padding( - padding: widget.controlSize.padding, - child: Align( - alignment: widget.alignment, - widthFactor: 1.0, - heightFactor: 1.0, - child: DefaultTextStyle( - style: widget.controlSize.textStyle(baseStyle), - child: widget.child, - ), - ), - ), - ); - }, - ), + child: StreamBuilder( + stream: AccentColorListener.instance.onChangedStream, + builder: (context, _) { + return StreamBuilder( + stream: WindowMainStateListener.instance.onChangedStream, + builder: (context, _) { + final Color backgroundColor = _getBackgroundColor(); + + final Color foregroundColor = + _getForegroundColor(backgroundColor); + + final baseStyle = theme.typography.body + .copyWith(color: foregroundColor); + + return DecoratedBox( + decoration: _getBoxDecoration().copyWith( + borderRadius: widget.controlSize.borderRadius, + ), + child: Padding( + padding: widget.controlSize.padding, + child: Align( + alignment: widget.alignment, + widthFactor: 1.0, + heightFactor: 1.0, + child: DefaultTextStyle( + style: widget.controlSize.textStyle(baseStyle), + child: widget.child, + ), + ), + ), + ); + }, + ); + }), ), ), ), From ba42d7528b71359016f7ee8f4b924b9934be7978 Mon Sep 17 00:00:00 2001 From: Adrian Samoticha Date: Mon, 7 Aug 2023 14:03:03 +0200 Subject: [PATCH 050/151] add missing comma --- lib/src/buttons/push_button.dart | 63 ++++++++++++++++---------------- 1 file changed, 32 insertions(+), 31 deletions(-) diff --git a/lib/src/buttons/push_button.dart b/lib/src/buttons/push_button.dart index 27b7d4d5..6bea8db5 100644 --- a/lib/src/buttons/push_button.dart +++ b/lib/src/buttons/push_button.dart @@ -386,39 +386,40 @@ class PushButtonState extends State child: FadeTransition( opacity: _opacityAnimation, child: StreamBuilder( - stream: AccentColorListener.instance.onChangedStream, - builder: (context, _) { - return StreamBuilder( - stream: WindowMainStateListener.instance.onChangedStream, - builder: (context, _) { - final Color backgroundColor = _getBackgroundColor(); - - final Color foregroundColor = - _getForegroundColor(backgroundColor); - - final baseStyle = theme.typography.body - .copyWith(color: foregroundColor); - - return DecoratedBox( - decoration: _getBoxDecoration().copyWith( - borderRadius: widget.controlSize.borderRadius, - ), - child: Padding( - padding: widget.controlSize.padding, - child: Align( - alignment: widget.alignment, - widthFactor: 1.0, - heightFactor: 1.0, - child: DefaultTextStyle( - style: widget.controlSize.textStyle(baseStyle), - child: widget.child, - ), + stream: AccentColorListener.instance.onChangedStream, + builder: (context, _) { + return StreamBuilder( + stream: WindowMainStateListener.instance.onChangedStream, + builder: (context, _) { + final Color backgroundColor = _getBackgroundColor(); + + final Color foregroundColor = + _getForegroundColor(backgroundColor); + + final baseStyle = theme.typography.body + .copyWith(color: foregroundColor); + + return DecoratedBox( + decoration: _getBoxDecoration().copyWith( + borderRadius: widget.controlSize.borderRadius, + ), + child: Padding( + padding: widget.controlSize.padding, + child: Align( + alignment: widget.alignment, + widthFactor: 1.0, + heightFactor: 1.0, + child: DefaultTextStyle( + style: widget.controlSize.textStyle(baseStyle), + child: widget.child, ), ), - ); - }, - ); - }), + ), + ); + }, + ); + }, + ), ), ), ), From a549bca66b70568795f8bcf892e50f0aa31f3c2e Mon Sep 17 00:00:00 2001 From: Adrian Samoticha Date: Mon, 7 Aug 2023 14:09:07 +0200 Subject: [PATCH 051/151] deprecate `PushButtonTheme` --- lib/src/buttons/push_button.dart | 8 -------- lib/src/theme/push_button_theme.dart | 4 ++++ 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/lib/src/buttons/push_button.dart b/lib/src/buttons/push_button.dart index 6bea8db5..e37d8828 100644 --- a/lib/src/buttons/push_button.dart +++ b/lib/src/buttons/push_button.dart @@ -361,14 +361,6 @@ class PushButtonState extends State assert(debugCheckHasMacosTheme(context)); final bool enabled = widget.enabled; final MacosThemeData theme = MacosTheme.of(context); - // TODO: remove this - // final Color backgroundColor = MacosDynamicColor.resolve( - // widget.color ?? - // (isSecondary - // ? theme.pushButtonTheme.secondaryColor! - // : theme.pushButtonTheme.color!), - // context, - // ); return MouseRegion( cursor: widget.mouseCursor!, diff --git a/lib/src/theme/push_button_theme.dart b/lib/src/theme/push_button_theme.dart index c7d27039..dc35c2b7 100644 --- a/lib/src/theme/push_button_theme.dart +++ b/lib/src/theme/push_button_theme.dart @@ -7,6 +7,8 @@ import 'package:macos_ui/src/library.dart'; /// See also: /// /// * [PushButtonThemeData], which is used to configure this theme. +@Deprecated("'PushButton' no longer uses singular colors and therefore cannot " + "be themed using a 'PushButtonTheme'.") class PushButtonTheme extends InheritedTheme { /// Create a [PushButtonTheme]. /// @@ -54,6 +56,8 @@ class PushButtonTheme extends InheritedTheme { /// * [PushButtonTheme], the theme which is configured with this class. /// * [MacosThemeData.pushButtonTheme], which can be used to override the default /// style for [PushButton]s below the overall [MacosTheme]. +@Deprecated("'PushButton' no longer uses singular colors and therefore cannot " + "be themed using a 'PushButtonTheme'.") class PushButtonThemeData with Diagnosticable { /// Creates a [PushButtonThemeData]. const PushButtonThemeData({ From ba3d7ce39a45dc79051d3b41dbdc985ab3c9fd42 Mon Sep 17 00:00:00 2001 From: Adrian Samoticha Date: Mon, 7 Aug 2023 14:39:58 +0200 Subject: [PATCH 052/151] =?UTF-8?q?mimic=20macOS=E2=80=99=20push=20button?= =?UTF-8?q?=20click=20effect?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/src/buttons/push_button.dart | 119 ++++++++++++------------------- 1 file changed, 44 insertions(+), 75 deletions(-) diff --git a/lib/src/buttons/push_button.dart b/lib/src/buttons/push_button.dart index e37d8828..f25f312f 100644 --- a/lib/src/buttons/push_button.dart +++ b/lib/src/buttons/push_button.dart @@ -232,76 +232,29 @@ class PushButton extends StatefulWidget { class PushButtonState extends State with SingleTickerProviderStateMixin { - // Eyeballed values. Feel free to tweak. - static const Duration kFadeOutDuration = Duration(milliseconds: 10); - static const Duration kFadeInDuration = Duration(milliseconds: 100); - final Tween _opacityTween = Tween(begin: 1.0); - - late AnimationController _animationController; - late Animation _opacityAnimation; - - @override - void initState() { - super.initState(); - _animationController = AnimationController( - duration: const Duration(milliseconds: 200), - value: 0.0, - vsync: this, - ); - _opacityAnimation = _animationController - .drive(CurveTween(curve: Curves.decelerate)) - .drive(_opacityTween); - _setTween(); - } - @override void didUpdateWidget(PushButton oldWidget) { super.didUpdateWidget(oldWidget); - _setTween(); - } - - void _setTween() { - _opacityTween.end = widget.pressedOpacity ?? 1.0; } void _handleTapDown(TapDownDetails event) { if (!buttonHeldDown) { - buttonHeldDown = true; - _animate(); + setState(() => buttonHeldDown = true); } } void _handleTapUp(TapUpDetails event) { if (buttonHeldDown) { - buttonHeldDown = false; - _animate(); + setState(() => buttonHeldDown = false); } } void _handleTapCancel() { if (buttonHeldDown) { - buttonHeldDown = false; - _animate(); + setState(() => buttonHeldDown = false); } } - void _animate() { - if (_animationController.isAnimating) return; - final bool wasHeldDown = buttonHeldDown; - final TickerFuture ticker = buttonHeldDown - ? _animationController.animateTo(1.0, duration: kFadeOutDuration) - : _animationController.animateTo(0.0, duration: kFadeInDuration); - ticker.then((void value) { - if (mounted && wasHeldDown != buttonHeldDown) _animate(); - }); - } - - @override - void dispose() { - _animationController.dispose(); - super.dispose(); - } - @visibleForTesting bool buttonHeldDown = false; @@ -356,6 +309,20 @@ class PushButtonState extends State : textLuminance(blendedBackgroundColor).withOpacity(0.25); } + BoxDecoration _getClickEffectBoxDecoration() { + final MacosThemeData theme = MacosTheme.of(context); + final isDark = theme.brightness.isDark; + + final color = isDark + ? const MacosColor.fromRGBO(255, 255, 255, 0.15) + : const MacosColor.fromRGBO(0, 0, 0, 0.06); + + return BoxDecoration( + color: color, + borderRadius: widget.controlSize.borderRadius, + ); + } + @override Widget build(BuildContext context) { assert(debugCheckHasMacosTheme(context)); @@ -375,26 +342,28 @@ class PushButtonState extends State label: widget.semanticLabel, child: ConstrainedBox( constraints: widget.controlSize.constraints, - child: FadeTransition( - opacity: _opacityAnimation, - child: StreamBuilder( - stream: AccentColorListener.instance.onChangedStream, - builder: (context, _) { - return StreamBuilder( - stream: WindowMainStateListener.instance.onChangedStream, - builder: (context, _) { - final Color backgroundColor = _getBackgroundColor(); - - final Color foregroundColor = - _getForegroundColor(backgroundColor); - - final baseStyle = theme.typography.body - .copyWith(color: foregroundColor); - - return DecoratedBox( - decoration: _getBoxDecoration().copyWith( - borderRadius: widget.controlSize.borderRadius, - ), + child: StreamBuilder( + stream: AccentColorListener.instance.onChangedStream, + builder: (context, _) { + return StreamBuilder( + stream: WindowMainStateListener.instance.onChangedStream, + builder: (context, _) { + final Color backgroundColor = _getBackgroundColor(); + + final Color foregroundColor = + _getForegroundColor(backgroundColor); + + final baseStyle = + theme.typography.body.copyWith(color: foregroundColor); + + return DecoratedBox( + decoration: _getBoxDecoration().copyWith( + borderRadius: widget.controlSize.borderRadius, + ), + child: Container( + foregroundDecoration: buttonHeldDown + ? _getClickEffectBoxDecoration() + : const BoxDecoration(), child: Padding( padding: widget.controlSize.padding, child: Align( @@ -407,11 +376,11 @@ class PushButtonState extends State ), ), ), - ); - }, - ); - }, - ), + ), + ); + }, + ); + }, ), ), ), From 07490f0edb586e4eabca04c9418309467dc21916 Mon Sep 17 00:00:00 2001 From: Adrian Samoticha Date: Mon, 7 Aug 2023 14:42:05 +0200 Subject: [PATCH 053/151] =?UTF-8?q?do=20not=20init=20`WindowMainStateListe?= =?UTF-8?q?ner`=20if=20platform=20isn=E2=80=99t=20macOS?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/src/utils.dart | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/src/utils.dart b/lib/src/utils.dart index d71b8cd4..cf18f817 100644 --- a/lib/src/utils.dart +++ b/lib/src/utils.dart @@ -117,6 +117,9 @@ class WindowMainStateListener { /// Initializes the listener. This should only be called once. void _init() { + if (kIsWeb) return; + if (!Platform.isMacOS) return; + final delegate = _WindowMainStateListenerDelegate( onWindowDidBecomeMain: () { _isWindowMain = true; From aa8f90d1a708ea0710e2a27ac42b2bacffede59a Mon Sep 17 00:00:00 2001 From: Adrian Samoticha Date: Mon, 7 Aug 2023 14:42:27 +0200 Subject: [PATCH 054/151] =?UTF-8?q?do=20not=20init=20`AccentColorListener`?= =?UTF-8?q?=20if=20platform=20isn=E2=80=99t=20macOS?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/src/utils.dart | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/src/utils.dart b/lib/src/utils.dart index cf18f817..cc3f765c 100644 --- a/lib/src/utils.dart +++ b/lib/src/utils.dart @@ -202,6 +202,9 @@ class AccentColorListener { /// Initializes this class. void _init() { + if (kIsWeb) return; + if (!Platform.isMacOS) return; + _initCurrentAccentColor(); _initSystemColorObserver(); } From 9b95a4308bd28c393a1d1c823d3e0676e8a7e721 Mon Sep 17 00:00:00 2001 From: Adrian Samoticha Date: Mon, 7 Aug 2023 14:43:46 +0200 Subject: [PATCH 055/151] remove unused imports --- lib/src/buttons/push_button.dart | 3 --- 1 file changed, 3 deletions(-) diff --git a/lib/src/buttons/push_button.dart b/lib/src/buttons/push_button.dart index f25f312f..ad2fc9dc 100644 --- a/lib/src/buttons/push_button.dart +++ b/lib/src/buttons/push_button.dart @@ -1,8 +1,5 @@ // ignore_for_file: prefer_if_null_operators -import 'dart:async'; - -import 'package:appkit_ui_element_colors/appkit_ui_element_colors.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/rendering.dart'; import 'package:gradient_borders/gradient_borders.dart'; From 0f52aaae1a0645a0a4eedf31473f7a683c695248 Mon Sep 17 00:00:00 2001 From: Adrian Samoticha Date: Mon, 7 Aug 2023 14:44:46 +0200 Subject: [PATCH 056/151] remove reference to deprecated API in documentation --- lib/src/buttons/push_button.dart | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/src/buttons/push_button.dart b/lib/src/buttons/push_button.dart index ad2fc9dc..6752957e 100644 --- a/lib/src/buttons/push_button.dart +++ b/lib/src/buttons/push_button.dart @@ -197,8 +197,7 @@ 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 overridden if the [color] attribute is non-null. + /// 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 From 85fe22f658e5d7e515cd5c011a2fdb65891b8eff Mon Sep 17 00:00:00 2001 From: Adrian Samoticha Date: Mon, 7 Aug 2023 14:46:24 +0200 Subject: [PATCH 057/151] deprecate `pressedOpacity` --- lib/src/buttons/push_button.dart | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/src/buttons/push_button.dart b/lib/src/buttons/push_button.dart index 6752957e..4dfbd92e 100644 --- a/lib/src/buttons/push_button.dart +++ b/lib/src/buttons/push_button.dart @@ -171,6 +171,8 @@ class PushButton extends StatefulWidget { /// /// This defaults to 0.4. If null, opacity will not change on pressed if using /// your own custom effects is desired. + @Deprecated("'PushButton' now attempts to mimic macOS’ look and feel, " + "therefore, its opacity no longer changes when it is pressed.") final double? pressedOpacity; /// The radius of the button's corners when it has a background color. From 856bfb07df40adba942072dcdb84df24a5f47d61 Mon Sep 17 00:00:00 2001 From: Adrian Samoticha Date: Mon, 7 Aug 2023 14:47:58 +0200 Subject: [PATCH 058/151] remove deprecated `pressedOpacity` from `debugFillProperties` --- lib/src/buttons/push_button.dart | 1 - test/buttons/push_button_test.dart | 1 - 2 files changed, 2 deletions(-) diff --git a/lib/src/buttons/push_button.dart b/lib/src/buttons/push_button.dart index 4dfbd92e..a295498b 100644 --- a/lib/src/buttons/push_button.dart +++ b/lib/src/buttons/push_button.dart @@ -212,7 +212,6 @@ class PushButton extends StatefulWidget { properties.add(EnumProperty('controlSize', controlSize)); properties.add(ColorProperty('color', color)); properties.add(ColorProperty('disabledColor', disabledColor)); - properties.add(DoubleProperty('pressedOpacity', pressedOpacity)); properties.add(DiagnosticsProperty('alignment', alignment)); properties.add(StringProperty('semanticLabel', semanticLabel)); properties.add(DiagnosticsProperty('borderRadius', borderRadius)); diff --git a/test/buttons/push_button_test.dart b/test/buttons/push_button_test.dart index 8bb294a0..981c21df 100644 --- a/test/buttons/push_button_test.dart +++ b/test/buttons/push_button_test.dart @@ -99,7 +99,6 @@ void main() { 'controlSize: regular', 'color: null', 'disabledColor: null', - 'pressedOpacity: 0.4', 'alignment: Alignment.center', 'semanticLabel: null', 'borderRadius: BorderRadius.circular(4.0)', From 3ca7e8149d5d7895744dbeeca76562ec57c25c21 Mon Sep 17 00:00:00 2001 From: Adrian Samoticha Date: Mon, 7 Aug 2023 14:50:27 +0200 Subject: [PATCH 059/151] document `AccentColor` --- lib/src/enums/accent_color.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/src/enums/accent_color.dart b/lib/src/enums/accent_color.dart index ed21cfe8..2090e6f4 100644 --- a/lib/src/enums/accent_color.dart +++ b/lib/src/enums/accent_color.dart @@ -1,4 +1,5 @@ -// TODO: document this +/// The macOS accent color which can be changed by the user in *System Settings* +/// → *Apperance* → *Accent color*. enum AccentColor { blue, purple, From 3c96252bb53efa19830e4a06762da3f7e74769f4 Mon Sep 17 00:00:00 2001 From: Adrian Samoticha Date: Mon, 7 Aug 2023 14:51:32 +0200 Subject: [PATCH 060/151] document `AccentColor` values --- lib/src/enums/accent_color.dart | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/lib/src/enums/accent_color.dart b/lib/src/enums/accent_color.dart index 2090e6f4..3e5ecc8e 100644 --- a/lib/src/enums/accent_color.dart +++ b/lib/src/enums/accent_color.dart @@ -1,12 +1,27 @@ /// The macOS accent color which can be changed by the user in *System Settings* /// → *Apperance* → *Accent color*. enum AccentColor { + /// The blue accent color. blue, + + /// The purple accent color. purple, + + /// The pink accent color. pink, + + /// The red accent color. red, + + /// The orange accent color. orange, + + /// The yellow accent color. yellow, + + /// The green accent color. green, + + /// The graphite accent color. graphite, } From 3d06510ed1ca7f0361037769f788366696caaebe Mon Sep 17 00:00:00 2001 From: Adrian Samoticha Date: Mon, 7 Aug 2023 15:36:02 +0200 Subject: [PATCH 061/151] increment version number --- example/pubspec.lock | 2 +- pubspec.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/example/pubspec.lock b/example/pubspec.lock index f642de3a..49e48c7f 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -174,7 +174,7 @@ packages: path: ".." relative: true source: path - version: "2.0.0" + version: "2.0.1" macos_window_utils: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 64a10b1e..d0525137 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: 2.0.0 +version: 2.0.1 homepage: "https://macosui.dev" repository: "https://github.com/GroovinChip/macos_ui" From 80bcf9a2f177445069e0f9375ecad21c205e6ac6 Mon Sep 17 00:00:00 2001 From: Adrian Samoticha Date: Mon, 7 Aug 2023 15:36:16 +0200 Subject: [PATCH 062/151] add changelog entry for version 2.0.1 --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0c59bae9..e1c43f00 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## [2.0.1] +* `PushButton` has received a facelift. It now mimics the look of native macOS buttons more closely. + * **Note:** As a result, its `pressedOpacity` property and the `PushButtonTheme` class have been deprecated. + ## [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: From 1c405dc843417156951f127c69b6e29de9951741 Mon Sep 17 00:00:00 2001 From: GroovinChip Date: Mon, 7 Aug 2023 12:43:17 -0400 Subject: [PATCH 063/151] run pub get --- example/pubspec.lock | 46 ++++++++++++++++---------------- pubspec.lock | 62 ++++++++++++++++---------------------------- 2 files changed, 46 insertions(+), 62 deletions(-) diff --git a/example/pubspec.lock b/example/pubspec.lock index 3dbde41a..dc586a2a 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -37,10 +37,10 @@ packages: dependency: transitive description: name: collection - sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a + sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c" url: "https://pub.dev" source: hosted - version: "1.18.0" + version: "1.17.1" crypto: dependency: transitive description: @@ -128,6 +128,14 @@ packages: url: "https://pub.dev" source: hosted version: "4.0.2" + js: + dependency: transitive + description: + name: js + sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 + url: "https://pub.dev" + source: hosted + version: "0.6.7" lints: dependency: transitive description: @@ -155,18 +163,18 @@ packages: dependency: transitive description: name: matcher - sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e" + sha256: "6501fbd55da300384b768785b83e5ce66991266cec21af89ab9ae7f5ce1c4cbb" url: "https://pub.dev" source: hosted - version: "0.12.16" + version: "0.12.15" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41" + sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724 url: "https://pub.dev" source: hosted - version: "0.5.0" + version: "0.2.0" meta: dependency: transitive description: @@ -280,26 +288,26 @@ packages: dependency: transitive description: name: source_span - sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" + sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250 url: "https://pub.dev" source: hosted - version: "1.10.0" + version: "1.9.1" stack_trace: dependency: transitive description: name: stack_trace - sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" + sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5 url: "https://pub.dev" source: hosted - version: "1.11.1" + version: "1.11.0" stream_channel: dependency: transitive description: name: stream_channel - sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 + sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8" url: "https://pub.dev" source: hosted - version: "2.1.2" + version: "2.1.1" string_scanner: dependency: transitive description: @@ -320,10 +328,10 @@ packages: dependency: transitive description: name: test_api - sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b" + sha256: eb6ac1540b26de412b3403a163d919ba86f6a973fe6cc50ae3541b80092fdcfb url: "https://pub.dev" source: hosted - version: "0.6.1" + version: "0.5.1" typed_data: dependency: transitive description: @@ -404,14 +412,6 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.4" - web: - dependency: transitive - description: - name: web - sha256: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10 - url: "https://pub.dev" - source: hosted - version: "0.1.4-beta" win32: dependency: transitive description: @@ -429,5 +429,5 @@ packages: source: hosted version: "1.0.0" sdks: - dart: ">=3.1.0-185.0.dev <4.0.0" + dart: ">=3.0.0 <4.0.0" flutter: ">=3.10.0" diff --git a/pubspec.lock b/pubspec.lock index 708421c4..dfa8a61c 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -5,18 +5,18 @@ packages: dependency: transitive description: name: _fe_analyzer_shared - sha256: "0816708f5fbcacca324d811297153fe3c8e047beb5c6752e12292d2974c17045" + sha256: ae92f5d747aee634b87f89d9946000c2de774be1d6ac3e58268224348cd0101a url: "https://pub.dev" source: hosted - version: "62.0.0" + version: "61.0.0" analyzer: dependency: transitive description: name: analyzer - sha256: "21862995c9932cd082f89d72ae5f5e2c110d1a0204ad06e4ebaee8307b76b834" + sha256: ea3d8652bda62982addfd92fdc2d0214e5f82e43325104990d4f4c4a2a313562 url: "https://pub.dev" source: hosted - version: "6.0.0" + version: "5.13.0" args: dependency: transitive description: @@ -61,10 +61,10 @@ packages: dependency: transitive description: name: collection - sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a + sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c" url: "https://pub.dev" source: hosted - version: "1.18.0" + version: "1.17.1" convert: dependency: transitive description: @@ -89,14 +89,6 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.3" - dart_internal: - dependency: transitive - description: - name: dart_internal - sha256: "689dccc3d5f62affd339534cca548dce12b3a6b32f0f10861569d3025efc0567" - url: "https://pub.dev" - source: hosted - version: "0.2.9" fake_async: dependency: transitive description: @@ -207,18 +199,18 @@ packages: dependency: transitive description: name: matcher - sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e" + sha256: "6501fbd55da300384b768785b83e5ce66991266cec21af89ab9ae7f5ce1c4cbb" url: "https://pub.dev" source: hosted - version: "0.12.16" + version: "0.12.15" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41" + sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724 url: "https://pub.dev" source: hosted - version: "0.5.0" + version: "0.2.0" meta: dependency: transitive description: @@ -340,26 +332,26 @@ packages: dependency: transitive description: name: source_span - sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" + sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250 url: "https://pub.dev" source: hosted - version: "1.10.0" + version: "1.9.1" stack_trace: dependency: transitive description: name: stack_trace - sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" + sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5 url: "https://pub.dev" source: hosted - version: "1.11.1" + version: "1.11.0" stream_channel: dependency: transitive description: name: stream_channel - sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 + sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8" url: "https://pub.dev" source: hosted - version: "2.1.2" + version: "2.1.1" string_scanner: dependency: transitive description: @@ -380,26 +372,26 @@ packages: dependency: transitive description: name: test - sha256: b9a384c4b9c4966dbf7215e7c033a78db1da7e5dcaf8da9232c5f24735f65652 + sha256: "3dac9aecf2c3991d09b9cdde4f98ded7b30804a88a0d7e4e7e1678e78d6b97f4" url: "https://pub.dev" source: hosted - version: "1.24.5" + version: "1.24.1" test_api: dependency: transitive description: name: test_api - sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b" + sha256: eb6ac1540b26de412b3403a163d919ba86f6a973fe6cc50ae3541b80092fdcfb url: "https://pub.dev" source: hosted - version: "0.6.1" + version: "0.5.1" test_core: dependency: transitive description: name: test_core - sha256: c6a536288535efef8526eea8adfa4e25fdd2849fa7f457ecb2a52099998ce8f7 + sha256: "5138dbffb77b2289ecb12b81c11ba46036590b72a64a7a90d6ffb880f1a29e93" url: "https://pub.dev" source: hosted - version: "0.5.5" + version: "0.5.1" typed_data: dependency: transitive description: @@ -432,14 +424,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.0" - web: - dependency: transitive - description: - name: web - sha256: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10 - url: "https://pub.dev" - source: hosted - version: "0.1.4-beta" web_socket_channel: dependency: transitive description: @@ -465,5 +449,5 @@ packages: source: hosted version: "3.1.2" sdks: - dart: ">=3.1.0-185.0.dev <3.3.0" + dart: ">=3.0.0 <4.0.0" flutter: ">=3.10.0" From db6088b6515e9e2bea48b66fdf4e5bac452e0b55 Mon Sep 17 00:00:00 2001 From: Adrian Samoticha Date: Fri, 11 Aug 2023 09:31:40 +0200 Subject: [PATCH 064/151] upgrade to macos_window_utils 1.2.0 --- example/pubspec.lock | 4 ++-- pubspec.lock | 4 ++-- pubspec.yaml | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/example/pubspec.lock b/example/pubspec.lock index 49e48c7f..86bd412e 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -179,10 +179,10 @@ packages: dependency: transitive description: name: macos_window_utils - sha256: b78a210aa70ca7ccad6e7b7b810fb4689c507f4a46e299214900b2a1eb70ea23 + sha256: "43a90473f8786f00f07203e6819dab67e032f8896dafa4a6f85fbc71fba32c0b" url: "https://pub.dev" source: hosted - version: "1.1.3" + version: "1.2.0" matcher: dependency: transitive description: diff --git a/pubspec.lock b/pubspec.lock index 018f3b58..6aa59489 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -215,10 +215,10 @@ packages: dependency: "direct main" description: name: macos_window_utils - sha256: b78a210aa70ca7ccad6e7b7b810fb4689c507f4a46e299214900b2a1eb70ea23 + sha256: "43a90473f8786f00f07203e6819dab67e032f8896dafa4a6f85fbc71fba32c0b" url: "https://pub.dev" source: hosted - version: "1.1.3" + version: "1.2.0" matcher: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index d0525137..8ffa130b 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -11,7 +11,7 @@ environment: dependencies: flutter: sdk: flutter - macos_window_utils: ^1.1.3 + macos_window_utils: ^1.2.0 gradient_borders: ^1.0.0 appkit_ui_element_colors: ^1.0.0 From 2423822ca318b13a779041fb7ed1ee9cd5c543db Mon Sep 17 00:00:00 2001 From: Adrian Samoticha Date: Fri, 11 Aug 2023 09:35:29 +0200 Subject: [PATCH 065/151] initialize `_isWindowMain` properly --- lib/src/utils.dart | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/lib/src/utils.dart b/lib/src/utils.dart index cc3f765c..19a23909 100644 --- a/lib/src/utils.dart +++ b/lib/src/utils.dart @@ -101,9 +101,7 @@ class WindowMainStateListener { NSWindowDelegateHandle? handle; /// Whether the window is currently the main window. - bool _isWindowMain = - true; // TODO: Initialize properly once macos_window_utils supports that, - // see https://github.com/macosui/macos_window_utils.dart/issues/31. + bool _isWindowMain = true; /// Whether the window is currently the main window. bool get isWindowMain => _isWindowMain; @@ -120,6 +118,12 @@ class WindowMainStateListener { if (kIsWeb) return; if (!Platform.isMacOS) return; + _initDelegate(); + _initIsWindowMain(); + } + + /// Initializes the [NSWindowDelegate] to listen for main window changes. + void _initDelegate() { final delegate = _WindowMainStateListenerDelegate( onWindowDidBecomeMain: () { _isWindowMain = true; @@ -133,6 +137,12 @@ class WindowMainStateListener { handle = WindowManipulator.addNSWindowDelegate(delegate); } + /// Initializes the [_isWindowMain] variable. + Future _initIsWindowMain() async { + _isWindowMain = await WindowManipulator.isMainWindow(); + _windowMainStateStreamController.add(_isWindowMain); + } + /// Deinitializes the listener. void deinit() { handle?.removeFromHandler(); From 1ef31a8ba45f2470332d6972375640d11fc16d6e Mon Sep 17 00:00:00 2001 From: Adrian Samoticha Date: Fri, 11 Aug 2023 09:37:18 +0200 Subject: [PATCH 066/151] rename `isWindowMain` to `isMainWindow` --- lib/src/buttons/push_button.dart | 4 ++-- lib/src/utils.dart | 14 +++++++------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/lib/src/buttons/push_button.dart b/lib/src/buttons/push_button.dart index a295498b..678c8510 100644 --- a/lib/src/buttons/push_button.dart +++ b/lib/src/buttons/push_button.dart @@ -261,7 +261,7 @@ class PushButtonState extends State BoxDecoration _getBoxDecoration() { // If the window isn’t currently the main window (that is, it is not in // focus), make the button look as if it was a secondary button. - final isWindowMain = WindowMainStateListener.instance.isWindowMain; + final isWindowMain = WindowMainStateListener.instance.isMainWindow; return _BoxDecorationBuilder.buildBoxDecoration( accentColor: _accentColor, @@ -278,7 +278,7 @@ class PushButtonState extends State // If the window isn’t currently the main window (that is, it is not in // focus), make the button look as if it was a secondary button. - final isWindowMain = WindowMainStateListener.instance.isWindowMain; + final isWindowMain = WindowMainStateListener.instance.isMainWindow; return MacosDynamicColor.resolve( widget.color ?? diff --git a/lib/src/utils.dart b/lib/src/utils.dart index 19a23909..3d9a8693 100644 --- a/lib/src/utils.dart +++ b/lib/src/utils.dart @@ -101,10 +101,10 @@ class WindowMainStateListener { NSWindowDelegateHandle? handle; /// Whether the window is currently the main window. - bool _isWindowMain = true; + bool _isMainWindow = true; /// Whether the window is currently the main window. - bool get isWindowMain => _isWindowMain; + bool get isMainWindow => _isMainWindow; /// Notifies listeners when the window’s main state changes. final _windowMainStateStreamController = StreamController.broadcast(); @@ -126,21 +126,21 @@ class WindowMainStateListener { void _initDelegate() { final delegate = _WindowMainStateListenerDelegate( onWindowDidBecomeMain: () { - _isWindowMain = true; + _isMainWindow = true; _windowMainStateStreamController.add(true); }, onWindowDidResignMain: () { - _isWindowMain = false; + _isMainWindow = false; _windowMainStateStreamController.add(false); }, ); handle = WindowManipulator.addNSWindowDelegate(delegate); } - /// Initializes the [_isWindowMain] variable. + /// Initializes the [_isMainWindow] variable. Future _initIsWindowMain() async { - _isWindowMain = await WindowManipulator.isMainWindow(); - _windowMainStateStreamController.add(_isWindowMain); + _isMainWindow = await WindowManipulator.isMainWindow(); + _windowMainStateStreamController.add(_isMainWindow); } /// Deinitializes the listener. From f064102d70df5d252b3a478246ac4bf0fdfc1efa Mon Sep 17 00:00:00 2001 From: Adrian Samoticha Date: Fri, 11 Aug 2023 09:39:56 +0200 Subject: [PATCH 067/151] replace `print` with `debugPrint` --- lib/src/utils.dart | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/src/utils.dart b/lib/src/utils.dart index 3d9a8693..470b803f 100644 --- a/lib/src/utils.dart +++ b/lib/src/utils.dart @@ -267,8 +267,7 @@ class AccentColorListener { return hueComponentToAccentColor[hueComponent]!; } - // ignore: avoid_print - print( + debugPrint( 'Warning: Falling back on slow accent color resolution. It’s possible ' 'that the accent colors have changed in a recent version of macOS, thus ' 'invalidating macos_ui’s accent colors, which were captured in macOS ' From 9040eb926ae804ed6339765bbaf79219a20f927f Mon Sep 17 00:00:00 2001 From: Adrian Samoticha Date: Fri, 11 Aug 2023 09:50:42 +0200 Subject: [PATCH 068/151] deprecate properties of `PushButtonThemeData` rather than `PushButtonThemeData` itself to avoid breaking changes --- lib/src/theme/macos_theme.dart | 7 ++--- lib/src/theme/push_button_theme.dart | 45 ++++++++++++++++++++++++---- 2 files changed, 42 insertions(+), 10 deletions(-) diff --git a/lib/src/theme/macos_theme.dart b/lib/src/theme/macos_theme.dart index 87405882..0bda4d11 100644 --- a/lib/src/theme/macos_theme.dart +++ b/lib/src/theme/macos_theme.dart @@ -514,8 +514,7 @@ class MacosThemeData with Diagnosticable { typography: MacosTypography.lerp(a.typography, b.typography, t), helpButtonTheme: HelpButtonThemeData.lerp(a.helpButtonTheme, b.helpButtonTheme, t), - pushButtonTheme: - PushButtonThemeData.lerp(a.pushButtonTheme, b.pushButtonTheme, t), + pushButtonTheme: a.pushButtonTheme, tooltipTheme: MacosTooltipThemeData.lerp(a.tooltipTheme, b.tooltipTheme, t), visualDensity: VisualDensity.lerp(a.visualDensity, b.visualDensity, t), @@ -581,7 +580,7 @@ class MacosThemeData with Diagnosticable { canvasColor: canvasColor ?? this.canvasColor, dividerColor: dividerColor ?? this.dividerColor, typography: this.typography.merge(typography), - pushButtonTheme: this.pushButtonTheme.merge(pushButtonTheme), + pushButtonTheme: this.pushButtonTheme, helpButtonTheme: this.helpButtonTheme.merge(helpButtonTheme), tooltipTheme: this.tooltipTheme.merge(tooltipTheme), visualDensity: visualDensity ?? this.visualDensity, @@ -605,7 +604,7 @@ class MacosThemeData with Diagnosticable { canvasColor: other.canvasColor, dividerColor: other.dividerColor, typography: typography.merge(other.typography), - pushButtonTheme: pushButtonTheme.merge(other.pushButtonTheme), + pushButtonTheme: pushButtonTheme, helpButtonTheme: helpButtonTheme.merge(other.helpButtonTheme), tooltipTheme: tooltipTheme.merge(other.tooltipTheme), visualDensity: other.visualDensity, diff --git a/lib/src/theme/push_button_theme.dart b/lib/src/theme/push_button_theme.dart index dc35c2b7..0c69aebd 100644 --- a/lib/src/theme/push_button_theme.dart +++ b/lib/src/theme/push_button_theme.dart @@ -7,12 +7,8 @@ import 'package:macos_ui/src/library.dart'; /// See also: /// /// * [PushButtonThemeData], which is used to configure this theme. -@Deprecated("'PushButton' no longer uses singular colors and therefore cannot " - "be themed using a 'PushButtonTheme'.") class PushButtonTheme extends InheritedTheme { /// Create a [PushButtonTheme]. - /// - /// The [data] parameter must not be null. const PushButtonTheme({ super.key, required this.data, @@ -20,6 +16,9 @@ class PushButtonTheme extends InheritedTheme { }); /// The configuration of this theme. + @Deprecated( + "'PushButton' no longer uses singular colors and therefore cannot " + "be themed using a 'PushButtonTheme'.") final PushButtonThemeData data; /// The closest instance of this class that encloses the given context. @@ -32,6 +31,9 @@ class PushButtonTheme extends InheritedTheme { /// ```dart /// PushButtonTheme theme = PushButtonTheme.of(context); /// ``` + @Deprecated( + "'PushButton' no longer uses singular colors and therefore cannot " + "be themed using a 'PushButtonTheme'.") static PushButtonThemeData of(BuildContext context) { final PushButtonTheme? buttonTheme = context.dependOnInheritedWidgetOfExactType(); @@ -39,11 +41,17 @@ class PushButtonTheme extends InheritedTheme { } @override + @Deprecated( + "'PushButton' no longer uses singular colors and therefore cannot " + "be themed using a 'PushButtonTheme'.") Widget wrap(BuildContext context, Widget child) { return PushButtonTheme(data: data, child: child); } @override + @Deprecated( + "'PushButton' no longer uses singular colors and therefore cannot " + "be themed using a 'PushButtonTheme'.") bool updateShouldNotify(PushButtonTheme oldWidget) => data != oldWidget.data; } @@ -56,8 +64,6 @@ class PushButtonTheme extends InheritedTheme { /// * [PushButtonTheme], the theme which is configured with this class. /// * [MacosThemeData.pushButtonTheme], which can be used to override the default /// style for [PushButton]s below the overall [MacosTheme]. -@Deprecated("'PushButton' no longer uses singular colors and therefore cannot " - "be themed using a 'PushButtonTheme'.") class PushButtonThemeData with Diagnosticable { /// Creates a [PushButtonThemeData]. const PushButtonThemeData({ @@ -67,15 +73,27 @@ class PushButtonThemeData with Diagnosticable { }); /// The default background color for [PushButton] + @Deprecated( + "'PushButton' no longer uses singular colors and therefore cannot " + "be themed using a 'PushButtonTheme'.") final Color? color; /// The default disabled color for [PushButton] + @Deprecated( + "'PushButton' no longer uses singular colors and therefore cannot " + "be themed using a 'PushButtonTheme'.") final Color? disabledColor; /// The default secondary color (e.g. Cancel/Go back buttons) for [PushButton] + @Deprecated( + "'PushButton' no longer uses singular colors and therefore cannot " + "be themed using a 'PushButtonTheme'.") final Color? secondaryColor; /// Copies this [PushButtonThemeData] into another. + @Deprecated( + "'PushButton' no longer uses singular colors and therefore cannot " + "be themed using a 'PushButtonTheme'.") PushButtonThemeData copyWith({ Color? color, Color? disabledColor, @@ -91,6 +109,9 @@ class PushButtonThemeData with Diagnosticable { /// Linearly interpolate between two [PushButtonThemeData]. /// /// All the properties must be non-null. + @Deprecated( + "'PushButton' no longer uses singular colors and therefore cannot " + "be themed using a 'PushButtonTheme'.") static PushButtonThemeData lerp( PushButtonThemeData a, PushButtonThemeData b, @@ -104,6 +125,9 @@ class PushButtonThemeData with Diagnosticable { } @override + @Deprecated( + "'PushButton' no longer uses singular colors and therefore cannot " + "be themed using a 'PushButtonTheme'.") bool operator ==(Object other) => identical(this, other) || other is PushButtonThemeData && @@ -113,9 +137,15 @@ class PushButtonThemeData with Diagnosticable { secondaryColor?.value == other.secondaryColor?.value; @override + @Deprecated( + "'PushButton' no longer uses singular colors and therefore cannot " + "be themed using a 'PushButtonTheme'.") int get hashCode => color.hashCode ^ disabledColor.hashCode; @override + @Deprecated( + "'PushButton' no longer uses singular colors and therefore cannot " + "be themed using a 'PushButtonTheme'.") void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); properties.add(ColorProperty('color', color)); @@ -124,6 +154,9 @@ class PushButtonThemeData with Diagnosticable { } /// Merges this [PushButtonThemeData] with another. + @Deprecated( + "'PushButton' no longer uses singular colors and therefore cannot " + "be themed using a 'PushButtonTheme'.") PushButtonThemeData merge(PushButtonThemeData? other) { if (other == null) return this; return copyWith( From 0df03afc4213f2f41e3f4af7a696e5b9fae1e48d Mon Sep 17 00:00:00 2001 From: Adrian Samoticha Date: Fri, 11 Aug 2023 09:51:36 +0200 Subject: [PATCH 069/151] fix typo in `AccentColor` documentation --- lib/src/enums/accent_color.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/enums/accent_color.dart b/lib/src/enums/accent_color.dart index 3e5ecc8e..95ab8ea2 100644 --- a/lib/src/enums/accent_color.dart +++ b/lib/src/enums/accent_color.dart @@ -1,5 +1,5 @@ /// The macOS accent color which can be changed by the user in *System Settings* -/// → *Apperance* → *Accent color*. +/// → *Appearance* → *Accent color*. enum AccentColor { /// The blue accent color. blue, From bc32402fa0ee00f50a9c27ec5f4db20b5bddc98f Mon Sep 17 00:00:00 2001 From: Adrian Samoticha Date: Fri, 11 Aug 2023 09:52:22 +0200 Subject: [PATCH 070/151] fix typo in `AccentColorListener` docs --- lib/src/utils.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/utils.dart b/lib/src/utils.dart index 470b803f..5ef5a178 100644 --- a/lib/src/utils.dart +++ b/lib/src/utils.dart @@ -207,7 +207,7 @@ class AccentColorListener { /// changes. Stream get onChangedStream => _accentColorStreamController.stream; - /// A stream subcription for the [SystemColorObserver] stream. + /// A stream subscription for the [SystemColorObserver] stream. StreamSubscription? _systemColorObserverStreamSubscription; /// Initializes this class. From ea6a7e571c53d498a73fc2374fe18cc7bdb092e2 Mon Sep 17 00:00:00 2001 From: Adrian Samoticha Date: Fri, 4 Aug 2023 19:37:48 +0200 Subject: [PATCH 071/151] automatic changes --- example/macos/Podfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/example/macos/Podfile.lock b/example/macos/Podfile.lock index 2fa81174..0ee0bdce 100644 --- a/example/macos/Podfile.lock +++ b/example/macos/Podfile.lock @@ -38,4 +38,4 @@ SPEC CHECKSUMS: PODFILE CHECKSUM: ff0a9a3ce75ee73f200ca7e2f47745698c917ef9 -COCOAPODS: 1.11.3 +COCOAPODS: 1.12.1 From f01d422cacba4d1abdd1be86aa505ca8f2d715f7 Mon Sep 17 00:00:00 2001 From: Adrian Samoticha Date: Sun, 6 Aug 2023 21:39:25 +0200 Subject: [PATCH 072/151] =?UTF-8?q?imitate=20macOS=E2=80=99=20push=20butto?= =?UTF-8?q?n=20look?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- example/pubspec.lock | 8 + lib/src/buttons/push_button.dart | 389 +++++++++++++++++++++++++++++-- lib/src/enums/accent_color.dart | 11 + pubspec.lock | 8 + pubspec.yaml | 1 + 5 files changed, 396 insertions(+), 21 deletions(-) create mode 100644 lib/src/enums/accent_color.dart diff --git a/example/pubspec.lock b/example/pubspec.lock index dc586a2a..eab32583 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -112,6 +112,14 @@ packages: url: "https://pub.dev" source: hosted version: "5.1.0" + gradient_borders: + dependency: transitive + description: + name: gradient_borders + sha256: "69eeaff519d145a4c6c213ada1abae386bcc8981a4970d923e478ce7ba19e309" + url: "https://pub.dev" + source: hosted + version: "1.0.0" http: dependency: transitive description: diff --git a/lib/src/buttons/push_button.dart b/lib/src/buttons/push_button.dart index 19180016..d1fbb238 100644 --- a/lib/src/buttons/push_button.dart +++ b/lib/src/buttons/push_button.dart @@ -2,7 +2,9 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/rendering.dart'; +import 'package:gradient_borders/gradient_borders.dart'; import 'package:macos_ui/macos_ui.dart'; +import 'package:macos_ui/src/enums/accent_color.dart'; import 'package:macos_ui/src/library.dart'; const _kMiniButtonSize = Size(26.0, 11.0); @@ -300,29 +302,64 @@ class PushButtonState extends State @visibleForTesting bool buttonHeldDown = false; - @override - Widget build(BuildContext context) { - assert(debugCheckHasMacosTheme(context)); + AccentColor get _accentColor => AccentColor.blue; // TODO: make this dynamic + + BoxDecoration _getBoxDecoration() { + return _BoxDecorationBuilder.buildBoxDecoration( + accentColor: _accentColor, + isEnabled: widget.enabled, + isDarkModeEnabled: MacosTheme.of(context).brightness.isDark, + isSecondary: widget.secondary ?? false, + ); + } + + Color _getBackgroundColor() { final bool enabled = widget.enabled; final bool isSecondary = widget.secondary != null && widget.secondary!; final MacosThemeData theme = MacosTheme.of(context); - final Color backgroundColor = MacosDynamicColor.resolve( + + return MacosDynamicColor.resolve( widget.color ?? - (isSecondary - ? theme.pushButtonTheme.secondaryColor! - : theme.pushButtonTheme.color!), + _BoxDecorationBuilder.getGradientColors( + accentColor: _accentColor, + isEnabled: enabled, + isDarkModeEnabled: theme.brightness.isDark, + isSecondary: isSecondary) + .first, context, ); + } + + Color _getForegroundColor(Color backgroundColor) { + final MacosThemeData theme = MacosTheme.of(context); - final disabledColor = !isSecondary - ? backgroundColor.withOpacity(0.5) - : backgroundColor.withOpacity(0.25); + final blendedBackgroundColor = Color.lerp( + theme.canvasColor, + backgroundColor, + backgroundColor.opacity, + )!; - final Color foregroundColor = widget.enabled - ? textLuminance(backgroundColor) - : theme.brightness.isDark - ? const Color.fromRGBO(255, 255, 255, 0.25) - : const Color.fromRGBO(0, 0, 0, 0.25); + return widget.enabled + ? textLuminance(blendedBackgroundColor) + : textLuminance(blendedBackgroundColor).withOpacity(0.25); + } + + @override + Widget build(BuildContext context) { + assert(debugCheckHasMacosTheme(context)); + final bool enabled = widget.enabled; + final MacosThemeData theme = MacosTheme.of(context); + // TODO: remove this + // final Color backgroundColor = MacosDynamicColor.resolve( + // widget.color ?? + // (isSecondary + // ? theme.pushButtonTheme.secondaryColor! + // : theme.pushButtonTheme.color!), + // context, + // ); + final Color backgroundColor = _getBackgroundColor(); + + final Color foregroundColor = _getForegroundColor(backgroundColor); final baseStyle = theme.typography.body.copyWith(color: foregroundColor); @@ -342,12 +379,8 @@ class PushButtonState extends State child: FadeTransition( opacity: _opacityAnimation, child: DecoratedBox( - decoration: ShapeDecoration( - shape: RoundedRectangleBorder( - borderRadius: widget.controlSize.borderRadius, - ), - // color: !enabled ? disabledColor : backgroundColor, - color: enabled ? backgroundColor : disabledColor, + decoration: _getBoxDecoration().copyWith( + borderRadius: widget.controlSize.borderRadius, ), child: Padding( padding: widget.controlSize.padding, @@ -369,3 +402,317 @@ class PushButtonState extends State ); } } + +class _BoxDecorationBuilder { + /// Gets the colors to use for the [BoxDecoration]’s gradient based on the + /// provided [accentColor], [isEnabled], and [isDarkModeEnabled] properties. + static List getGradientColors({ + required AccentColor accentColor, + required bool isEnabled, + required bool isDarkModeEnabled, + required bool isSecondary, + }) { + final isEnabledFactor = isEnabled ? 1.0 : 0.5; + + if (isSecondary) { + return isDarkModeEnabled + ? [ + MacosColor.fromRGBO(255, 255, 255, 0.251 * isEnabledFactor), + MacosColor.fromRGBO(255, 255, 255, 0.251 * isEnabledFactor), + ] + : [ + MacosColor.fromRGBO(255, 255, 255, 1.0 * isEnabledFactor), + MacosColor.fromRGBO(255, 255, 255, 1.0 * isEnabledFactor), + ]; + } + + if (isDarkModeEnabled) { + switch (accentColor) { + case AccentColor.blue: + return [ + MacosColor.fromRGBO(0, 114, 238, 1.0 * isEnabledFactor), + MacosColor.fromRGBO(0, 94, 211, 1.0 * isEnabledFactor), + ]; + + case AccentColor.purple: + return [ + MacosColor.fromRGBO(135, 65, 131, 1.0 * isEnabledFactor), + MacosColor.fromRGBO(120, 57, 116, 1.0 * isEnabledFactor), + ]; + + case AccentColor.pink: + return [ + MacosColor.fromRGBO(188, 52, 105, 1.0 * isEnabledFactor), + MacosColor.fromRGBO(168, 46, 93, 1.0 * isEnabledFactor), + ]; + + case AccentColor.red: + return [ + MacosColor.fromRGBO(186, 53, 46, 1.0 * isEnabledFactor), + MacosColor.fromRGBO(166, 48, 41, 1.0 * isEnabledFactor), + ]; + + case AccentColor.orange: + return [ + MacosColor.fromRGBO(212, 133, 33, 1.0 * isEnabledFactor), + MacosColor.fromRGBO(189, 118, 30, 1.0 * isEnabledFactor), + ]; + + case AccentColor.yellow: + return [ + MacosColor.fromRGBO(229, 203, 35, 1.0 * isEnabledFactor), + MacosColor.fromRGBO(204, 179, 21, 1.0 * isEnabledFactor), + ]; + + case AccentColor.green: + return [ + MacosColor.fromRGBO(58, 138, 46, 1.0 * isEnabledFactor), + MacosColor.fromRGBO(52, 123, 39, 1.0 * isEnabledFactor), + ]; + + case AccentColor.graphite: + return [ + MacosColor.fromRGBO(64, 64, 64, 1.0 * isEnabledFactor), + MacosColor.fromRGBO(57, 57, 57, 1.0 * isEnabledFactor), + ]; + + default: + throw UnimplementedError(); + } + } else { + switch (accentColor) { + case AccentColor.blue: + return [ + MacosColor.fromRGBO(39, 125, 255, 1.0 * isEnabledFactor), + MacosColor.fromRGBO(1, 101, 255, 1.0 * isEnabledFactor), + ]; + + case AccentColor.purple: + return [ + MacosColor.fromRGBO(148, 73, 143, 1.0 * isEnabledFactor), + MacosColor.fromRGBO(128, 39, 121, 1.0 * isEnabledFactor), + ]; + + case AccentColor.pink: + return [ + MacosColor.fromRGBO(212, 71, 125, 1.0 * isEnabledFactor), + MacosColor.fromRGBO(203, 36, 101, 1.0 * isEnabledFactor), + ]; + + case AccentColor.red: + return [ + MacosColor.fromRGBO(198, 64, 57, 1.0 * isEnabledFactor), + MacosColor.fromRGBO(188, 29, 21, 1.0 * isEnabledFactor), + ]; + + case AccentColor.orange: + return [ + MacosColor.fromRGBO(237, 154, 51, 1.0 * isEnabledFactor), + MacosColor.fromRGBO(234, 136, 13, 1.0 * isEnabledFactor), + ]; + + case AccentColor.yellow: + return [ + MacosColor.fromRGBO(242, 211, 61, 1.0 * isEnabledFactor), + MacosColor.fromRGBO(240, 203, 25, 1.0 * isEnabledFactor), + ]; + + case AccentColor.green: + return [ + MacosColor.fromRGBO(77, 161, 63, 1.0 * isEnabledFactor), + MacosColor.fromRGBO(45, 143, 28, 1.0 * isEnabledFactor), + ]; + + case AccentColor.graphite: + return [ + MacosColor.fromRGBO(86, 86, 86, 1.0 * isEnabledFactor), + MacosColor.fromRGBO(55, 55, 55, 1.0 * isEnabledFactor), + ]; + + default: + throw UnimplementedError(); + } + } + } + + /// Gets the shadow to use for the [BoxDecoration] based on the provided + /// [accentColor], [isEnabled], and [isDarkModeEnabled] properties. + static List _getShadow({ + required AccentColor accentColor, + required bool isEnabled, + required bool isDarkModeEnabled, + required bool isSecondary, + }) { + final isEnabledFactor = isEnabled ? 1.0 : 0.5; + + if (isSecondary) { + return isDarkModeEnabled + ? [ + BoxShadow( + color: MacosColor.fromRGBO(0, 0, 0, 0.4 * isEnabledFactor), + blurRadius: 0.5, + offset: Offset.zero, + spreadRadius: 0.0, + blurStyle: BlurStyle.outer, + ), + ] + : [ + BoxShadow( + color: MacosColor.fromRGBO(0, 0, 0, 0.4 * isEnabledFactor), + blurRadius: 0.5, + offset: isEnabled ? const Offset(0.0, 0.3) : Offset.zero, + spreadRadius: 0.0, + blurStyle: isEnabled ? BlurStyle.normal : BlurStyle.outer, + ), + ]; + } + + if (isDarkModeEnabled) { + return [ + BoxShadow( + color: MacosColor.fromRGBO(0, 0, 0, 0.4 * isEnabledFactor), + blurRadius: 0.5, + offset: isEnabled ? const Offset(0.0, 0.3) : Offset.zero, + spreadRadius: 0.0, + blurStyle: isEnabled ? BlurStyle.normal : BlurStyle.outer, + ), + ]; + } else { + switch (accentColor) { + case AccentColor.blue: + return [ + BoxShadow( + color: MacosColor.fromRGBO(0, 103, 255, 0.21 * isEnabledFactor), + blurRadius: 0.5, + offset: isEnabled ? const Offset(0.0, 0.3) : Offset.zero, + spreadRadius: 0.0, + blurStyle: isEnabled ? BlurStyle.normal : BlurStyle.outer, + ), + ]; + + case AccentColor.purple: + return [ + BoxShadow( + color: MacosColor.fromRGBO(139, 29, 125, 0.21 * isEnabledFactor), + blurRadius: 0.5, + offset: isEnabled ? const Offset(0.0, 0.3) : Offset.zero, + spreadRadius: 0.0, + blurStyle: isEnabled ? BlurStyle.normal : BlurStyle.outer, + ), + ]; + + case AccentColor.pink: + return [ + BoxShadow( + color: MacosColor.fromRGBO(222, 0, 101, 0.21 * isEnabledFactor), + blurRadius: 0.5, + offset: isEnabled ? const Offset(0.0, 0.3) : Offset.zero, + spreadRadius: 0.0, + blurStyle: isEnabled ? BlurStyle.normal : BlurStyle.outer, + ), + ]; + + case AccentColor.red: + return [ + BoxShadow( + color: MacosColor.fromRGBO(188, 29, 21, 0.35 * isEnabledFactor), + blurRadius: 0.5, + offset: isEnabled ? const Offset(0.0, 0.3) : Offset.zero, + spreadRadius: 0.0, + blurStyle: isEnabled ? BlurStyle.normal : BlurStyle.outer, + ), + ]; + + case AccentColor.orange: + return [ + BoxShadow( + color: MacosColor.fromRGBO(234, 136, 13, 0.35 * isEnabledFactor), + blurRadius: 0.5, + offset: isEnabled ? const Offset(0.0, 0.3) : Offset.zero, + spreadRadius: 0.0, + blurStyle: isEnabled ? BlurStyle.normal : BlurStyle.outer, + ), + ]; + + case AccentColor.yellow: + return [ + BoxShadow( + color: MacosColor.fromRGBO(240, 203, 25, 0.35 * isEnabledFactor), + blurRadius: 0.5, + offset: isEnabled ? const Offset(0.0, 0.3) : Offset.zero, + spreadRadius: 0.0, + blurStyle: isEnabled ? BlurStyle.normal : BlurStyle.outer, + ), + ]; + + case AccentColor.green: + return [ + BoxShadow( + color: MacosColor.fromRGBO(45, 143, 28, 0.35 * isEnabledFactor), + blurRadius: 0.5, + offset: isEnabled ? const Offset(0.0, 0.3) : Offset.zero, + spreadRadius: 0.0, + blurStyle: isEnabled ? BlurStyle.normal : BlurStyle.outer, + ), + ]; + + case AccentColor.graphite: + return [ + BoxShadow( + color: MacosColor.fromRGBO(55, 55, 55, 0.35 * isEnabledFactor), + blurRadius: 0.5, + offset: isEnabled ? const Offset(0.0, 0.3) : Offset.zero, + spreadRadius: 0.0, + blurStyle: isEnabled ? BlurStyle.normal : BlurStyle.outer, + ), + ]; + + default: + throw UnimplementedError(); + } + } + } + + /// Builds a [BoxDecoration] for a [MacosPushButton]. + static BoxDecoration buildBoxDecoration({ + required AccentColor accentColor, + required bool isEnabled, + required bool isDarkModeEnabled, + required bool isSecondary, + }) { + final isEnabledFactor = isEnabled ? 1.0 : 0.5; + + return BoxDecoration( + border: isDarkModeEnabled + ? GradientBoxBorder( + gradient: LinearGradient( + colors: [ + MacosColor.fromRGBO(255, 255, 255, 0.43 * isEnabledFactor), + const MacosColor.fromRGBO(255, 255, 255, 0.0), + ], + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + stops: const [0.0, 0.2], + ), + width: 0.7, + ) + : null, + gradient: LinearGradient( + colors: getGradientColors( + accentColor: accentColor, + isEnabled: isEnabled, + isDarkModeEnabled: isDarkModeEnabled, + isSecondary: isSecondary, + ), + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + ), + boxShadow: _getShadow( + accentColor: accentColor, + isEnabled: isEnabled, + isDarkModeEnabled: isDarkModeEnabled, + isSecondary: isSecondary, + ), + ); + } +} diff --git a/lib/src/enums/accent_color.dart b/lib/src/enums/accent_color.dart new file mode 100644 index 00000000..ed21cfe8 --- /dev/null +++ b/lib/src/enums/accent_color.dart @@ -0,0 +1,11 @@ +// TODO: document this +enum AccentColor { + blue, + purple, + pink, + red, + orange, + yellow, + green, + graphite, +} diff --git a/pubspec.lock b/pubspec.lock index dfa8a61c..be900083 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -139,6 +139,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.1" + gradient_borders: + dependency: "direct main" + description: + name: gradient_borders + sha256: "69eeaff519d145a4c6c213ada1abae386bcc8981a4970d923e478ce7ba19e309" + url: "https://pub.dev" + source: hosted + version: "1.0.0" http_multi_server: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 13865b31..7b208ea5 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -12,6 +12,7 @@ dependencies: flutter: sdk: flutter macos_window_utils: ^1.1.3 + gradient_borders: ^1.0.0 dev_dependencies: flutter_test: From d0255e16f648de4004e2b4b3e215a64c2b8ffe8b Mon Sep 17 00:00:00 2001 From: Adrian Samoticha Date: Sun, 6 Aug 2023 22:51:54 +0200 Subject: [PATCH 073/151] make push button appear secondary when window resigns main state --- lib/src/buttons/push_button.dart | 15 ++++++- lib/src/utils.dart | 72 ++++++++++++++++++++++++++++++++ 2 files changed, 86 insertions(+), 1 deletion(-) diff --git a/lib/src/buttons/push_button.dart b/lib/src/buttons/push_button.dart index d1fbb238..3aa9cfde 100644 --- a/lib/src/buttons/push_button.dart +++ b/lib/src/buttons/push_button.dart @@ -1,5 +1,7 @@ // ignore_for_file: prefer_if_null_operators +import 'dart:async'; + import 'package:flutter/foundation.dart'; import 'package:flutter/rendering.dart'; import 'package:gradient_borders/gradient_borders.dart'; @@ -237,6 +239,8 @@ class PushButtonState extends State late AnimationController _animationController; late Animation _opacityAnimation; + late StreamSubscription _onWindowMainStateChangedStreamSubcription; + @override void initState() { super.initState(); @@ -249,6 +253,10 @@ class PushButtonState extends State .drive(CurveTween(curve: Curves.decelerate)) .drive(_opacityTween); _setTween(); + + _onWindowMainStateChangedStreamSubcription = WindowMainStateListener + .instance.onChangedStream + .listen((_) => setState(() {})); } @override @@ -296,6 +304,7 @@ class PushButtonState extends State @override void dispose() { _animationController.dispose(); + _onWindowMainStateChangedStreamSubcription.cancel(); super.dispose(); } @@ -305,11 +314,15 @@ class PushButtonState extends State AccentColor get _accentColor => AccentColor.blue; // TODO: make this dynamic BoxDecoration _getBoxDecoration() { + // If the window isn’t currently the main window (that is, it is not in + // focus), make the button look as if it was a secondary button. + final isWindowMain = WindowMainStateListener.instance.isWindowMain; + return _BoxDecorationBuilder.buildBoxDecoration( accentColor: _accentColor, isEnabled: widget.enabled, isDarkModeEnabled: MacosTheme.of(context).brightness.isDark, - isSecondary: widget.secondary ?? false, + isSecondary: !isWindowMain || (widget.secondary ?? false), ); } diff --git a/lib/src/utils.dart b/lib/src/utils.dart index 1f2d8a1e..dcb8212a 100644 --- a/lib/src/utils.dart +++ b/lib/src/utils.dart @@ -1,3 +1,4 @@ +import 'dart:async'; import 'dart:io'; import 'package:flutter/foundation.dart'; @@ -86,3 +87,74 @@ class MacOSBrightnessOverrideHandler { _lastBrightness = currentBrightness; } } + +/// A class that listens for changes to the application's window being the main +/// window, and notifies listeners. +class WindowMainStateListener { + /// A shared instance of [WindowMainStateListener]. + static final instance = WindowMainStateListener(); + + /// A [NSWindowDelegateHandle], to be used when deiniting the listener. + NSWindowDelegateHandle? handle; + + /// Whether the window is currently the main window. + bool _isWindowMain = + true; // TODO: Initialize properly once macos_window_utils supports that, + // see https://github.com/macosui/macos_window_utils.dart/issues/31. + + /// Whether the window is currently the main window. + bool get isWindowMain => _isWindowMain; + + /// Notifies listeners when the window’s main state changes. + final _windowMainStateStreamController = StreamController.broadcast(); + + /// A stream of the window’s main state. Emits a new value whenever the state + /// changes. + Stream get onChangedStream => _windowMainStateStreamController.stream; + + /// Initializes the listener. This should only be called once. + void _init() { + final delegate = _WindowMainStateListenerDelegate( + onWindowDidBecomeMain: () { + _isWindowMain = true; + _windowMainStateStreamController.add(true); + }, + onWindowDidResignMain: () { + _isWindowMain = false; + _windowMainStateStreamController.add(false); + }, + ); + handle = WindowManipulator.addNSWindowDelegate(delegate); + } + + /// Deinitializes the listener. + void deinit() { + handle?.removeFromHandler(); + } + + /// A class that listens for changes to the application's window being the + /// main window, and notifies listeners. + WindowMainStateListener() { + _init(); + } +} + +/// The [NSWindowDelegate] used by [WindowMainStateListener]. +class _WindowMainStateListenerDelegate extends NSWindowDelegate { + _WindowMainStateListenerDelegate({ + required this.onWindowDidBecomeMain, + required this.onWindowDidResignMain, + }); + + /// Called when the window becomes the main window. + final void Function() onWindowDidBecomeMain; + + /// Called when the window resigns as the main window. + final void Function() onWindowDidResignMain; + + @override + void windowDidBecomeMain() => onWindowDidBecomeMain(); + + @override + void windowDidResignMain() => onWindowDidResignMain(); +} From 5e905194c64b19d73c0a46edf5e252faf47aa5dd Mon Sep 17 00:00:00 2001 From: Adrian Samoticha Date: Sun, 6 Aug 2023 22:58:36 +0200 Subject: [PATCH 074/151] migrate to stream builder --- lib/src/buttons/push_button.dart | 45 +++++++++++++++----------------- 1 file changed, 21 insertions(+), 24 deletions(-) diff --git a/lib/src/buttons/push_button.dart b/lib/src/buttons/push_button.dart index 3aa9cfde..ad7be046 100644 --- a/lib/src/buttons/push_button.dart +++ b/lib/src/buttons/push_button.dart @@ -239,8 +239,6 @@ class PushButtonState extends State late AnimationController _animationController; late Animation _opacityAnimation; - late StreamSubscription _onWindowMainStateChangedStreamSubcription; - @override void initState() { super.initState(); @@ -253,10 +251,6 @@ class PushButtonState extends State .drive(CurveTween(curve: Curves.decelerate)) .drive(_opacityTween); _setTween(); - - _onWindowMainStateChangedStreamSubcription = WindowMainStateListener - .instance.onChangedStream - .listen((_) => setState(() {})); } @override @@ -304,7 +298,6 @@ class PushButtonState extends State @override void dispose() { _animationController.dispose(); - _onWindowMainStateChangedStreamSubcription.cancel(); super.dispose(); } @@ -391,23 +384,27 @@ class PushButtonState extends State constraints: widget.controlSize.constraints, child: FadeTransition( opacity: _opacityAnimation, - child: DecoratedBox( - decoration: _getBoxDecoration().copyWith( - borderRadius: widget.controlSize.borderRadius, - ), - child: Padding( - padding: widget.controlSize.padding, - child: Align( - alignment: widget.alignment, - widthFactor: 1.0, - heightFactor: 1.0, - child: DefaultTextStyle( - style: widget.controlSize.textStyle(baseStyle), - child: widget.child, - ), - ), - ), - ), + child: StreamBuilder( + stream: WindowMainStateListener.instance.onChangedStream, + builder: (context, _) { + return DecoratedBox( + decoration: _getBoxDecoration().copyWith( + borderRadius: widget.controlSize.borderRadius, + ), + child: Padding( + padding: widget.controlSize.padding, + child: Align( + alignment: widget.alignment, + widthFactor: 1.0, + heightFactor: 1.0, + child: DefaultTextStyle( + style: widget.controlSize.textStyle(baseStyle), + child: widget.child, + ), + ), + ), + ); + }), ), ), ), From 08727e099fe85b0bf8d9c022d9d22f2ca8c86a01 Mon Sep 17 00:00:00 2001 From: Adrian Samoticha Date: Sun, 6 Aug 2023 23:05:38 +0200 Subject: [PATCH 075/151] fix incorrect text color in push button when window resigns main state --- lib/src/buttons/push_button.dart | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/lib/src/buttons/push_button.dart b/lib/src/buttons/push_button.dart index ad7be046..1979204b 100644 --- a/lib/src/buttons/push_button.dart +++ b/lib/src/buttons/push_button.dart @@ -324,14 +324,18 @@ class PushButtonState extends State final bool isSecondary = widget.secondary != null && widget.secondary!; final MacosThemeData theme = MacosTheme.of(context); + // If the window isn’t currently the main window (that is, it is not in + // focus), make the button look as if it was a secondary button. + final isWindowMain = WindowMainStateListener.instance.isWindowMain; + return MacosDynamicColor.resolve( widget.color ?? _BoxDecorationBuilder.getGradientColors( - accentColor: _accentColor, - isEnabled: enabled, - isDarkModeEnabled: theme.brightness.isDark, - isSecondary: isSecondary) - .first, + accentColor: _accentColor, + isEnabled: enabled, + isDarkModeEnabled: theme.brightness.isDark, + isSecondary: isSecondary || !isWindowMain, + ).first, context, ); } @@ -363,11 +367,6 @@ class PushButtonState extends State // : theme.pushButtonTheme.color!), // context, // ); - final Color backgroundColor = _getBackgroundColor(); - - final Color foregroundColor = _getForegroundColor(backgroundColor); - - final baseStyle = theme.typography.body.copyWith(color: foregroundColor); return MouseRegion( cursor: widget.mouseCursor!, @@ -387,6 +386,14 @@ class PushButtonState extends State child: StreamBuilder( stream: WindowMainStateListener.instance.onChangedStream, builder: (context, _) { + final Color backgroundColor = _getBackgroundColor(); + + final Color foregroundColor = + _getForegroundColor(backgroundColor); + + final baseStyle = + theme.typography.body.copyWith(color: foregroundColor); + return DecoratedBox( decoration: _getBoxDecoration().copyWith( borderRadius: widget.controlSize.borderRadius, From 0c75ce2fb737f08164a4147dbf42f5e7bff22f24 Mon Sep 17 00:00:00 2001 From: Adrian Samoticha Date: Mon, 7 Aug 2023 12:27:17 +0200 Subject: [PATCH 076/151] add missing comma --- lib/src/buttons/push_button.dart | 53 ++++++++++++++++---------------- 1 file changed, 27 insertions(+), 26 deletions(-) diff --git a/lib/src/buttons/push_button.dart b/lib/src/buttons/push_button.dart index 1979204b..2f714bb6 100644 --- a/lib/src/buttons/push_button.dart +++ b/lib/src/buttons/push_button.dart @@ -384,34 +384,35 @@ class PushButtonState extends State child: FadeTransition( opacity: _opacityAnimation, child: StreamBuilder( - stream: WindowMainStateListener.instance.onChangedStream, - builder: (context, _) { - final Color backgroundColor = _getBackgroundColor(); - - final Color foregroundColor = - _getForegroundColor(backgroundColor); - - final baseStyle = - theme.typography.body.copyWith(color: foregroundColor); - - return DecoratedBox( - decoration: _getBoxDecoration().copyWith( - borderRadius: widget.controlSize.borderRadius, - ), - child: Padding( - padding: widget.controlSize.padding, - child: Align( - alignment: widget.alignment, - widthFactor: 1.0, - heightFactor: 1.0, - child: DefaultTextStyle( - style: widget.controlSize.textStyle(baseStyle), - child: widget.child, - ), + stream: WindowMainStateListener.instance.onChangedStream, + builder: (context, _) { + final Color backgroundColor = _getBackgroundColor(); + + final Color foregroundColor = + _getForegroundColor(backgroundColor); + + final baseStyle = + theme.typography.body.copyWith(color: foregroundColor); + + return DecoratedBox( + decoration: _getBoxDecoration().copyWith( + borderRadius: widget.controlSize.borderRadius, + ), + child: Padding( + padding: widget.controlSize.padding, + child: Align( + alignment: widget.alignment, + widthFactor: 1.0, + heightFactor: 1.0, + child: DefaultTextStyle( + style: widget.controlSize.textStyle(baseStyle), + child: widget.child, ), ), - ); - }), + ), + ); + }, + ), ), ), ), From 7e9e60271df740f91f25491d1903ab76ebd49a09 Mon Sep 17 00:00:00 2001 From: Adrian Samoticha Date: Mon, 7 Aug 2023 14:02:36 +0200 Subject: [PATCH 077/151] implement `AccentColorListener` --- .../Flutter/GeneratedPluginRegistrant.swift | 2 + example/macos/Podfile.lock | 6 + example/pubspec.lock | 16 +++ lib/src/utils.dart | 133 ++++++++++++++++++ pubspec.lock | 24 ++++ pubspec.yaml | 1 + 6 files changed, 182 insertions(+) diff --git a/example/macos/Flutter/GeneratedPluginRegistrant.swift b/example/macos/Flutter/GeneratedPluginRegistrant.swift index 0179c12c..1e64f18d 100644 --- a/example/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/example/macos/Flutter/GeneratedPluginRegistrant.swift @@ -5,12 +5,14 @@ import FlutterMacOS import Foundation +import appkit_ui_element_colors import macos_ui import macos_window_utils import path_provider_foundation import url_launcher_macos func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + AppkitUiElementColorsPlugin.register(with: registry.registrar(forPlugin: "AppkitUiElementColorsPlugin")) MacOSUiPlugin.register(with: registry.registrar(forPlugin: "MacOSUiPlugin")) MacOSWindowUtilsPlugin.register(with: registry.registrar(forPlugin: "MacOSWindowUtilsPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) diff --git a/example/macos/Podfile.lock b/example/macos/Podfile.lock index 0ee0bdce..6aa1d86e 100644 --- a/example/macos/Podfile.lock +++ b/example/macos/Podfile.lock @@ -1,4 +1,6 @@ PODS: + - appkit_ui_element_colors (1.0.0): + - FlutterMacOS - FlutterMacOS (1.0.0) - macos_ui (0.1.0): - FlutterMacOS @@ -11,6 +13,7 @@ PODS: - FlutterMacOS DEPENDENCIES: + - appkit_ui_element_colors (from `Flutter/ephemeral/.symlinks/plugins/appkit_ui_element_colors/macos`) - 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`) @@ -18,6 +21,8 @@ DEPENDENCIES: - url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`) EXTERNAL SOURCES: + appkit_ui_element_colors: + :path: Flutter/ephemeral/.symlinks/plugins/appkit_ui_element_colors/macos FlutterMacOS: :path: Flutter/ephemeral macos_ui: @@ -30,6 +35,7 @@ EXTERNAL SOURCES: :path: Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos SPEC CHECKSUMS: + appkit_ui_element_colors: 39bb2d80be3f19b152ccf4c70d5bbe6cba43d74a FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24 macos_ui: 6229a8922cd97bafb7d9636c8eb8dfb0744183ca macos_window_utils: 933f91f64805e2eb91a5bd057cf97cd097276663 diff --git a/example/pubspec.lock b/example/pubspec.lock index eab32583..f642de3a 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -1,6 +1,14 @@ # Generated by pub # See https://dart.dev/tools/pub/glossary#lockfile packages: + appkit_ui_element_colors: + dependency: transitive + description: + name: appkit_ui_element_colors + sha256: c3e50f900aae314d339de489535736238627071457c4a4a2dbbb1545b4f04f22 + url: "https://pub.dev" + source: hosted + version: "1.0.0" async: dependency: transitive description: @@ -57,6 +65,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.5" + equatable: + dependency: transitive + description: + name: equatable + sha256: c2b87cb7756efdf69892005af546c56c0b5037f54d2a88269b4f347a505e3ca2 + url: "https://pub.dev" + source: hosted + version: "2.0.5" fake_async: dependency: transitive description: diff --git a/lib/src/utils.dart b/lib/src/utils.dart index dcb8212a..d71b8cd4 100644 --- a/lib/src/utils.dart +++ b/lib/src/utils.dart @@ -1,8 +1,11 @@ import 'dart:async'; import 'dart:io'; +import 'dart:math'; +import 'package:appkit_ui_element_colors/appkit_ui_element_colors.dart'; import 'package:flutter/foundation.dart'; import 'package:macos_ui/macos_ui.dart'; +import 'package:macos_ui/src/enums/accent_color.dart'; import 'package:macos_ui/src/library.dart'; /// Asserts that the given context has a [MacosTheme] ancestor. @@ -158,3 +161,133 @@ class _WindowMainStateListenerDelegate extends NSWindowDelegate { @override void windowDidResignMain() => onWindowDidResignMain(); } + +/// A class that listens to accent color changes. +class AccentColorListener { + /// A shared instance of [AccentColorListener]. + static final instance = AccentColorListener(); + + /// A map which maps hue components of the [UiElementColor.controlAccentColor] + /// color captured with the [NSAppearanceName.aqua] appearance in the + /// [NSColorSpace.genericRGB] color space to the corresponding [AccentColor]. + static final hueComponentToAccentColor = { + 0.6085324903200698: AccentColor.blue, + 0.8285987697113538: AccentColor.purple, + 0.9209523937489168: AccentColor.pink, + 0.9861913496946438: AccentColor.red, + 0.06543037411201169: AccentColor.orange, + 0.11813830353929083: AccentColor.yellow, + 0.29428158007138466: AccentColor.green, + 0.0: AccentColor.graphite, + }; + + /// The currently active accent color. + AccentColor? _currentAccentColor; + + /// The currently active accent color. + AccentColor? get currentAccentColor => _currentAccentColor; + + /// Notifies listeners when the accent color changes. + final _accentColorStreamController = StreamController.broadcast(); + + /// An accent color stream. Emits a new value whenever the accent color + /// changes. + Stream get onChangedStream => _accentColorStreamController.stream; + + /// A stream subcription for the [SystemColorObserver] stream. + StreamSubscription? _systemColorObserverStreamSubscription; + + /// Initializes this class. + void _init() { + _initCurrentAccentColor(); + _initSystemColorObserver(); + } + + /// Deinitializes this class. + void deinit() { + _systemColorObserverStreamSubscription?.cancel(); + } + + /// Initializes the current accent color. This method is to be called whenever + /// a change is detected. + Future _initCurrentAccentColor() async { + final hueComponent = await _getHueComponent(); + _currentAccentColor = _resolveAccentColorFromHueComponent(hueComponent); + _accentColorStreamController.add(null); + } + + /// Initializes the current system color observer. This method may only be + /// called once. + void _initSystemColorObserver() { + assert(_systemColorObserverStreamSubscription == null); + + _systemColorObserverStreamSubscription = + AppkitUiElementColors.systemColorObserver.stream.listen((_) { + _initCurrentAccentColor(); + _accentColorStreamController.add(null); + }); + } + + /// Returns the hue component of the currently active accent color on macOS. + Future _getHueComponent() async { + final color = await AppkitUiElementColors.getColorComponents( + uiElementColor: UiElementColor.controlAccentColor, + components: const { + NSColorComponent.hueComponent, + }, + colorSpace: NSColorSpace.genericRGB, + appearance: NSAppearanceName.aqua, + ); + + assert(color.containsKey("hueComponent")); + + return color["hueComponent"]!; + } + + /// Returns the [AccentColor] which corresponds to the provided + /// [hueComponent]. + AccentColor _resolveAccentColorFromHueComponent(double hueComponent) { + if (hueComponentToAccentColor.containsKey(hueComponent)) { + return hueComponentToAccentColor[hueComponent]!; + } + + // ignore: avoid_print + print( + 'Warning: Falling back on slow accent color resolution. It’s possible ' + 'that the accent colors have changed in a recent version of macOS, thus ' + 'invalidating macos_ui’s accent colors, which were captured in macOS ' + 'Ventura. If you see this message, please notify a maintainer of the ' + 'macos_ui package.', + ); + + return _slowlyResolveAccentColorFromHueComponent(hueComponent); + } + + /// This is a fallback method in case the above method fails. + AccentColor _slowlyResolveAccentColorFromHueComponent(double hueComponent) { + final entries = hueComponentToAccentColor.entries; + var lowestDistance = double.maxFinite; + var toBeReturnedAccentColor = AccentColor.values.first; + + for (var entry in entries) { + final distance = _distanceBetweenHueComponents(hueComponent, entry.key); + if (distance < lowestDistance) { + lowestDistance = distance; + toBeReturnedAccentColor = entry.value; + } + } + + return toBeReturnedAccentColor; + } + + /// Returns the distance between two hue components. + double _distanceBetweenHueComponents(double component1, double component2) { + final rawDifference = (component1 - component2).abs(); + return sin(rawDifference * pi); + } + + /// A class that listens to accent color changes. + AccentColorListener() { + _init(); + } +} diff --git a/pubspec.lock b/pubspec.lock index be900083..018f3b58 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -17,6 +17,14 @@ packages: url: "https://pub.dev" source: hosted version: "5.13.0" + appkit_ui_element_colors: + dependency: "direct main" + description: + name: appkit_ui_element_colors + sha256: c3e50f900aae314d339de489535736238627071457c4a4a2dbbb1545b4f04f22 + url: "https://pub.dev" + source: hosted + version: "1.0.0" args: dependency: transitive description: @@ -89,6 +97,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.3" + equatable: + dependency: transitive + description: + name: equatable + sha256: c2b87cb7756efdf69892005af546c56c0b5037f54d2a88269b4f347a505e3ca2 + url: "https://pub.dev" + source: hosted + version: "2.0.5" fake_async: dependency: transitive description: @@ -267,6 +283,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.8.3" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + sha256: "43798d895c929056255600343db8f049921cbec94d31ec87f1dc5c16c01935dd" + url: "https://pub.dev" + source: hosted + version: "2.1.5" pool: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 7b208ea5..64a10b1e 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -13,6 +13,7 @@ dependencies: sdk: flutter macos_window_utils: ^1.1.3 gradient_borders: ^1.0.0 + appkit_ui_element_colors: ^1.0.0 dev_dependencies: flutter_test: From 6a31e1caee028177e60ab65f129aca67669744da Mon Sep 17 00:00:00 2001 From: Adrian Samoticha Date: Mon, 7 Aug 2023 14:02:47 +0200 Subject: [PATCH 078/151] make push button listen to accent color changes --- lib/src/buttons/push_button.dart | 68 +++++++++++++++++--------------- 1 file changed, 37 insertions(+), 31 deletions(-) diff --git a/lib/src/buttons/push_button.dart b/lib/src/buttons/push_button.dart index 2f714bb6..27b7d4d5 100644 --- a/lib/src/buttons/push_button.dart +++ b/lib/src/buttons/push_button.dart @@ -2,6 +2,7 @@ import 'dart:async'; +import 'package:appkit_ui_element_colors/appkit_ui_element_colors.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/rendering.dart'; import 'package:gradient_borders/gradient_borders.dart'; @@ -304,7 +305,8 @@ class PushButtonState extends State @visibleForTesting bool buttonHeldDown = false; - AccentColor get _accentColor => AccentColor.blue; // TODO: make this dynamic + AccentColor get _accentColor => + AccentColorListener.instance.currentAccentColor ?? AccentColor.blue; BoxDecoration _getBoxDecoration() { // If the window isn’t currently the main window (that is, it is not in @@ -383,36 +385,40 @@ class PushButtonState extends State constraints: widget.controlSize.constraints, child: FadeTransition( opacity: _opacityAnimation, - child: StreamBuilder( - stream: WindowMainStateListener.instance.onChangedStream, - builder: (context, _) { - final Color backgroundColor = _getBackgroundColor(); - - final Color foregroundColor = - _getForegroundColor(backgroundColor); - - final baseStyle = - theme.typography.body.copyWith(color: foregroundColor); - - return DecoratedBox( - decoration: _getBoxDecoration().copyWith( - borderRadius: widget.controlSize.borderRadius, - ), - child: Padding( - padding: widget.controlSize.padding, - child: Align( - alignment: widget.alignment, - widthFactor: 1.0, - heightFactor: 1.0, - child: DefaultTextStyle( - style: widget.controlSize.textStyle(baseStyle), - child: widget.child, - ), - ), - ), - ); - }, - ), + child: StreamBuilder( + stream: AccentColorListener.instance.onChangedStream, + builder: (context, _) { + return StreamBuilder( + stream: WindowMainStateListener.instance.onChangedStream, + builder: (context, _) { + final Color backgroundColor = _getBackgroundColor(); + + final Color foregroundColor = + _getForegroundColor(backgroundColor); + + final baseStyle = theme.typography.body + .copyWith(color: foregroundColor); + + return DecoratedBox( + decoration: _getBoxDecoration().copyWith( + borderRadius: widget.controlSize.borderRadius, + ), + child: Padding( + padding: widget.controlSize.padding, + child: Align( + alignment: widget.alignment, + widthFactor: 1.0, + heightFactor: 1.0, + child: DefaultTextStyle( + style: widget.controlSize.textStyle(baseStyle), + child: widget.child, + ), + ), + ), + ); + }, + ); + }), ), ), ), From 3eaab39006c09d59cb1ef4cd260762ed4b4d2aa2 Mon Sep 17 00:00:00 2001 From: Adrian Samoticha Date: Mon, 7 Aug 2023 14:03:03 +0200 Subject: [PATCH 079/151] add missing comma --- lib/src/buttons/push_button.dart | 63 ++++++++++++++++---------------- 1 file changed, 32 insertions(+), 31 deletions(-) diff --git a/lib/src/buttons/push_button.dart b/lib/src/buttons/push_button.dart index 27b7d4d5..6bea8db5 100644 --- a/lib/src/buttons/push_button.dart +++ b/lib/src/buttons/push_button.dart @@ -386,39 +386,40 @@ class PushButtonState extends State child: FadeTransition( opacity: _opacityAnimation, child: StreamBuilder( - stream: AccentColorListener.instance.onChangedStream, - builder: (context, _) { - return StreamBuilder( - stream: WindowMainStateListener.instance.onChangedStream, - builder: (context, _) { - final Color backgroundColor = _getBackgroundColor(); - - final Color foregroundColor = - _getForegroundColor(backgroundColor); - - final baseStyle = theme.typography.body - .copyWith(color: foregroundColor); - - return DecoratedBox( - decoration: _getBoxDecoration().copyWith( - borderRadius: widget.controlSize.borderRadius, - ), - child: Padding( - padding: widget.controlSize.padding, - child: Align( - alignment: widget.alignment, - widthFactor: 1.0, - heightFactor: 1.0, - child: DefaultTextStyle( - style: widget.controlSize.textStyle(baseStyle), - child: widget.child, - ), + stream: AccentColorListener.instance.onChangedStream, + builder: (context, _) { + return StreamBuilder( + stream: WindowMainStateListener.instance.onChangedStream, + builder: (context, _) { + final Color backgroundColor = _getBackgroundColor(); + + final Color foregroundColor = + _getForegroundColor(backgroundColor); + + final baseStyle = theme.typography.body + .copyWith(color: foregroundColor); + + return DecoratedBox( + decoration: _getBoxDecoration().copyWith( + borderRadius: widget.controlSize.borderRadius, + ), + child: Padding( + padding: widget.controlSize.padding, + child: Align( + alignment: widget.alignment, + widthFactor: 1.0, + heightFactor: 1.0, + child: DefaultTextStyle( + style: widget.controlSize.textStyle(baseStyle), + child: widget.child, ), ), - ); - }, - ); - }), + ), + ); + }, + ); + }, + ), ), ), ), From 5d230a58c51f25a280f6a307805abb22f7856b2b Mon Sep 17 00:00:00 2001 From: Adrian Samoticha Date: Mon, 7 Aug 2023 14:09:07 +0200 Subject: [PATCH 080/151] deprecate `PushButtonTheme` --- lib/src/buttons/push_button.dart | 8 -------- lib/src/theme/push_button_theme.dart | 4 ++++ 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/lib/src/buttons/push_button.dart b/lib/src/buttons/push_button.dart index 6bea8db5..e37d8828 100644 --- a/lib/src/buttons/push_button.dart +++ b/lib/src/buttons/push_button.dart @@ -361,14 +361,6 @@ class PushButtonState extends State assert(debugCheckHasMacosTheme(context)); final bool enabled = widget.enabled; final MacosThemeData theme = MacosTheme.of(context); - // TODO: remove this - // final Color backgroundColor = MacosDynamicColor.resolve( - // widget.color ?? - // (isSecondary - // ? theme.pushButtonTheme.secondaryColor! - // : theme.pushButtonTheme.color!), - // context, - // ); return MouseRegion( cursor: widget.mouseCursor!, diff --git a/lib/src/theme/push_button_theme.dart b/lib/src/theme/push_button_theme.dart index c7d27039..dc35c2b7 100644 --- a/lib/src/theme/push_button_theme.dart +++ b/lib/src/theme/push_button_theme.dart @@ -7,6 +7,8 @@ import 'package:macos_ui/src/library.dart'; /// See also: /// /// * [PushButtonThemeData], which is used to configure this theme. +@Deprecated("'PushButton' no longer uses singular colors and therefore cannot " + "be themed using a 'PushButtonTheme'.") class PushButtonTheme extends InheritedTheme { /// Create a [PushButtonTheme]. /// @@ -54,6 +56,8 @@ class PushButtonTheme extends InheritedTheme { /// * [PushButtonTheme], the theme which is configured with this class. /// * [MacosThemeData.pushButtonTheme], which can be used to override the default /// style for [PushButton]s below the overall [MacosTheme]. +@Deprecated("'PushButton' no longer uses singular colors and therefore cannot " + "be themed using a 'PushButtonTheme'.") class PushButtonThemeData with Diagnosticable { /// Creates a [PushButtonThemeData]. const PushButtonThemeData({ From c99b3f00bddda3d3a0946faeb18d5493063841b6 Mon Sep 17 00:00:00 2001 From: Adrian Samoticha Date: Mon, 7 Aug 2023 14:39:58 +0200 Subject: [PATCH 081/151] =?UTF-8?q?mimic=20macOS=E2=80=99=20push=20button?= =?UTF-8?q?=20click=20effect?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/src/buttons/push_button.dart | 119 ++++++++++++------------------- 1 file changed, 44 insertions(+), 75 deletions(-) diff --git a/lib/src/buttons/push_button.dart b/lib/src/buttons/push_button.dart index e37d8828..f25f312f 100644 --- a/lib/src/buttons/push_button.dart +++ b/lib/src/buttons/push_button.dart @@ -232,76 +232,29 @@ class PushButton extends StatefulWidget { class PushButtonState extends State with SingleTickerProviderStateMixin { - // Eyeballed values. Feel free to tweak. - static const Duration kFadeOutDuration = Duration(milliseconds: 10); - static const Duration kFadeInDuration = Duration(milliseconds: 100); - final Tween _opacityTween = Tween(begin: 1.0); - - late AnimationController _animationController; - late Animation _opacityAnimation; - - @override - void initState() { - super.initState(); - _animationController = AnimationController( - duration: const Duration(milliseconds: 200), - value: 0.0, - vsync: this, - ); - _opacityAnimation = _animationController - .drive(CurveTween(curve: Curves.decelerate)) - .drive(_opacityTween); - _setTween(); - } - @override void didUpdateWidget(PushButton oldWidget) { super.didUpdateWidget(oldWidget); - _setTween(); - } - - void _setTween() { - _opacityTween.end = widget.pressedOpacity ?? 1.0; } void _handleTapDown(TapDownDetails event) { if (!buttonHeldDown) { - buttonHeldDown = true; - _animate(); + setState(() => buttonHeldDown = true); } } void _handleTapUp(TapUpDetails event) { if (buttonHeldDown) { - buttonHeldDown = false; - _animate(); + setState(() => buttonHeldDown = false); } } void _handleTapCancel() { if (buttonHeldDown) { - buttonHeldDown = false; - _animate(); + setState(() => buttonHeldDown = false); } } - void _animate() { - if (_animationController.isAnimating) return; - final bool wasHeldDown = buttonHeldDown; - final TickerFuture ticker = buttonHeldDown - ? _animationController.animateTo(1.0, duration: kFadeOutDuration) - : _animationController.animateTo(0.0, duration: kFadeInDuration); - ticker.then((void value) { - if (mounted && wasHeldDown != buttonHeldDown) _animate(); - }); - } - - @override - void dispose() { - _animationController.dispose(); - super.dispose(); - } - @visibleForTesting bool buttonHeldDown = false; @@ -356,6 +309,20 @@ class PushButtonState extends State : textLuminance(blendedBackgroundColor).withOpacity(0.25); } + BoxDecoration _getClickEffectBoxDecoration() { + final MacosThemeData theme = MacosTheme.of(context); + final isDark = theme.brightness.isDark; + + final color = isDark + ? const MacosColor.fromRGBO(255, 255, 255, 0.15) + : const MacosColor.fromRGBO(0, 0, 0, 0.06); + + return BoxDecoration( + color: color, + borderRadius: widget.controlSize.borderRadius, + ); + } + @override Widget build(BuildContext context) { assert(debugCheckHasMacosTheme(context)); @@ -375,26 +342,28 @@ class PushButtonState extends State label: widget.semanticLabel, child: ConstrainedBox( constraints: widget.controlSize.constraints, - child: FadeTransition( - opacity: _opacityAnimation, - child: StreamBuilder( - stream: AccentColorListener.instance.onChangedStream, - builder: (context, _) { - return StreamBuilder( - stream: WindowMainStateListener.instance.onChangedStream, - builder: (context, _) { - final Color backgroundColor = _getBackgroundColor(); - - final Color foregroundColor = - _getForegroundColor(backgroundColor); - - final baseStyle = theme.typography.body - .copyWith(color: foregroundColor); - - return DecoratedBox( - decoration: _getBoxDecoration().copyWith( - borderRadius: widget.controlSize.borderRadius, - ), + child: StreamBuilder( + stream: AccentColorListener.instance.onChangedStream, + builder: (context, _) { + return StreamBuilder( + stream: WindowMainStateListener.instance.onChangedStream, + builder: (context, _) { + final Color backgroundColor = _getBackgroundColor(); + + final Color foregroundColor = + _getForegroundColor(backgroundColor); + + final baseStyle = + theme.typography.body.copyWith(color: foregroundColor); + + return DecoratedBox( + decoration: _getBoxDecoration().copyWith( + borderRadius: widget.controlSize.borderRadius, + ), + child: Container( + foregroundDecoration: buttonHeldDown + ? _getClickEffectBoxDecoration() + : const BoxDecoration(), child: Padding( padding: widget.controlSize.padding, child: Align( @@ -407,11 +376,11 @@ class PushButtonState extends State ), ), ), - ); - }, - ); - }, - ), + ), + ); + }, + ); + }, ), ), ), From 8196267860286f6a030216122acba4683109ef78 Mon Sep 17 00:00:00 2001 From: Adrian Samoticha Date: Mon, 7 Aug 2023 14:42:05 +0200 Subject: [PATCH 082/151] =?UTF-8?q?do=20not=20init=20`WindowMainStateListe?= =?UTF-8?q?ner`=20if=20platform=20isn=E2=80=99t=20macOS?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/src/utils.dart | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/src/utils.dart b/lib/src/utils.dart index d71b8cd4..cf18f817 100644 --- a/lib/src/utils.dart +++ b/lib/src/utils.dart @@ -117,6 +117,9 @@ class WindowMainStateListener { /// Initializes the listener. This should only be called once. void _init() { + if (kIsWeb) return; + if (!Platform.isMacOS) return; + final delegate = _WindowMainStateListenerDelegate( onWindowDidBecomeMain: () { _isWindowMain = true; From 555bae678c34cfcb772fa8b49bc378f0182810a0 Mon Sep 17 00:00:00 2001 From: Adrian Samoticha Date: Mon, 7 Aug 2023 14:42:27 +0200 Subject: [PATCH 083/151] =?UTF-8?q?do=20not=20init=20`AccentColorListener`?= =?UTF-8?q?=20if=20platform=20isn=E2=80=99t=20macOS?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/src/utils.dart | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/src/utils.dart b/lib/src/utils.dart index cf18f817..cc3f765c 100644 --- a/lib/src/utils.dart +++ b/lib/src/utils.dart @@ -202,6 +202,9 @@ class AccentColorListener { /// Initializes this class. void _init() { + if (kIsWeb) return; + if (!Platform.isMacOS) return; + _initCurrentAccentColor(); _initSystemColorObserver(); } From b8b22d7da7d79775f573a7cb4310a90af9e1ee4b Mon Sep 17 00:00:00 2001 From: Adrian Samoticha Date: Mon, 7 Aug 2023 14:43:46 +0200 Subject: [PATCH 084/151] remove unused imports --- lib/src/buttons/push_button.dart | 3 --- 1 file changed, 3 deletions(-) diff --git a/lib/src/buttons/push_button.dart b/lib/src/buttons/push_button.dart index f25f312f..ad2fc9dc 100644 --- a/lib/src/buttons/push_button.dart +++ b/lib/src/buttons/push_button.dart @@ -1,8 +1,5 @@ // ignore_for_file: prefer_if_null_operators -import 'dart:async'; - -import 'package:appkit_ui_element_colors/appkit_ui_element_colors.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/rendering.dart'; import 'package:gradient_borders/gradient_borders.dart'; From 04f2a11838fafb65065d85347bdd5e9eced38080 Mon Sep 17 00:00:00 2001 From: Adrian Samoticha Date: Mon, 7 Aug 2023 14:44:46 +0200 Subject: [PATCH 085/151] remove reference to deprecated API in documentation --- lib/src/buttons/push_button.dart | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/src/buttons/push_button.dart b/lib/src/buttons/push_button.dart index ad2fc9dc..6752957e 100644 --- a/lib/src/buttons/push_button.dart +++ b/lib/src/buttons/push_button.dart @@ -197,8 +197,7 @@ 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 overridden if the [color] attribute is non-null. + /// 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 From 7e0f365435811f164b65da962dbde2779a7e58e4 Mon Sep 17 00:00:00 2001 From: Adrian Samoticha Date: Mon, 7 Aug 2023 14:46:24 +0200 Subject: [PATCH 086/151] deprecate `pressedOpacity` --- lib/src/buttons/push_button.dart | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/src/buttons/push_button.dart b/lib/src/buttons/push_button.dart index 6752957e..4dfbd92e 100644 --- a/lib/src/buttons/push_button.dart +++ b/lib/src/buttons/push_button.dart @@ -171,6 +171,8 @@ class PushButton extends StatefulWidget { /// /// This defaults to 0.4. If null, opacity will not change on pressed if using /// your own custom effects is desired. + @Deprecated("'PushButton' now attempts to mimic macOS’ look and feel, " + "therefore, its opacity no longer changes when it is pressed.") final double? pressedOpacity; /// The radius of the button's corners when it has a background color. From f27803de53b0d8489c20e3fc59a37160ce282197 Mon Sep 17 00:00:00 2001 From: Adrian Samoticha Date: Mon, 7 Aug 2023 14:47:58 +0200 Subject: [PATCH 087/151] remove deprecated `pressedOpacity` from `debugFillProperties` --- lib/src/buttons/push_button.dart | 1 - test/buttons/push_button_test.dart | 1 - 2 files changed, 2 deletions(-) diff --git a/lib/src/buttons/push_button.dart b/lib/src/buttons/push_button.dart index 4dfbd92e..a295498b 100644 --- a/lib/src/buttons/push_button.dart +++ b/lib/src/buttons/push_button.dart @@ -212,7 +212,6 @@ class PushButton extends StatefulWidget { properties.add(EnumProperty('controlSize', controlSize)); properties.add(ColorProperty('color', color)); properties.add(ColorProperty('disabledColor', disabledColor)); - properties.add(DoubleProperty('pressedOpacity', pressedOpacity)); properties.add(DiagnosticsProperty('alignment', alignment)); properties.add(StringProperty('semanticLabel', semanticLabel)); properties.add(DiagnosticsProperty('borderRadius', borderRadius)); diff --git a/test/buttons/push_button_test.dart b/test/buttons/push_button_test.dart index 8bb294a0..981c21df 100644 --- a/test/buttons/push_button_test.dart +++ b/test/buttons/push_button_test.dart @@ -99,7 +99,6 @@ void main() { 'controlSize: regular', 'color: null', 'disabledColor: null', - 'pressedOpacity: 0.4', 'alignment: Alignment.center', 'semanticLabel: null', 'borderRadius: BorderRadius.circular(4.0)', From cfacc99cd6905106fcee3d7fe15f32060a5ae822 Mon Sep 17 00:00:00 2001 From: Adrian Samoticha Date: Mon, 7 Aug 2023 14:50:27 +0200 Subject: [PATCH 088/151] document `AccentColor` --- lib/src/enums/accent_color.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/src/enums/accent_color.dart b/lib/src/enums/accent_color.dart index ed21cfe8..2090e6f4 100644 --- a/lib/src/enums/accent_color.dart +++ b/lib/src/enums/accent_color.dart @@ -1,4 +1,5 @@ -// TODO: document this +/// The macOS accent color which can be changed by the user in *System Settings* +/// → *Apperance* → *Accent color*. enum AccentColor { blue, purple, From 5cf616f430d005c7139a38f24c66e540ab38164a Mon Sep 17 00:00:00 2001 From: Adrian Samoticha Date: Mon, 7 Aug 2023 14:51:32 +0200 Subject: [PATCH 089/151] document `AccentColor` values --- lib/src/enums/accent_color.dart | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/lib/src/enums/accent_color.dart b/lib/src/enums/accent_color.dart index 2090e6f4..3e5ecc8e 100644 --- a/lib/src/enums/accent_color.dart +++ b/lib/src/enums/accent_color.dart @@ -1,12 +1,27 @@ /// The macOS accent color which can be changed by the user in *System Settings* /// → *Apperance* → *Accent color*. enum AccentColor { + /// The blue accent color. blue, + + /// The purple accent color. purple, + + /// The pink accent color. pink, + + /// The red accent color. red, + + /// The orange accent color. orange, + + /// The yellow accent color. yellow, + + /// The green accent color. green, + + /// The graphite accent color. graphite, } From 23ab438e81cf827ddf369a97a126f0d23920066e Mon Sep 17 00:00:00 2001 From: Adrian Samoticha Date: Mon, 7 Aug 2023 15:36:02 +0200 Subject: [PATCH 090/151] increment version number --- example/pubspec.lock | 2 +- pubspec.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/example/pubspec.lock b/example/pubspec.lock index f642de3a..49e48c7f 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -174,7 +174,7 @@ packages: path: ".." relative: true source: path - version: "2.0.0" + version: "2.0.1" macos_window_utils: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 64a10b1e..d0525137 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: 2.0.0 +version: 2.0.1 homepage: "https://macosui.dev" repository: "https://github.com/GroovinChip/macos_ui" From 4aa22965ad0f4855ec310b7c186b75a335b0ff39 Mon Sep 17 00:00:00 2001 From: Adrian Samoticha Date: Mon, 7 Aug 2023 15:36:16 +0200 Subject: [PATCH 091/151] add changelog entry for version 2.0.1 --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0c59bae9..e1c43f00 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## [2.0.1] +* `PushButton` has received a facelift. It now mimics the look of native macOS buttons more closely. + * **Note:** As a result, its `pressedOpacity` property and the `PushButtonTheme` class have been deprecated. + ## [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: From c39ba50dfa292d695d28672836d56db93045c738 Mon Sep 17 00:00:00 2001 From: Adrian Samoticha Date: Fri, 11 Aug 2023 09:31:40 +0200 Subject: [PATCH 092/151] upgrade to macos_window_utils 1.2.0 --- example/pubspec.lock | 4 ++-- pubspec.lock | 4 ++-- pubspec.yaml | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/example/pubspec.lock b/example/pubspec.lock index 49e48c7f..86bd412e 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -179,10 +179,10 @@ packages: dependency: transitive description: name: macos_window_utils - sha256: b78a210aa70ca7ccad6e7b7b810fb4689c507f4a46e299214900b2a1eb70ea23 + sha256: "43a90473f8786f00f07203e6819dab67e032f8896dafa4a6f85fbc71fba32c0b" url: "https://pub.dev" source: hosted - version: "1.1.3" + version: "1.2.0" matcher: dependency: transitive description: diff --git a/pubspec.lock b/pubspec.lock index 018f3b58..6aa59489 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -215,10 +215,10 @@ packages: dependency: "direct main" description: name: macos_window_utils - sha256: b78a210aa70ca7ccad6e7b7b810fb4689c507f4a46e299214900b2a1eb70ea23 + sha256: "43a90473f8786f00f07203e6819dab67e032f8896dafa4a6f85fbc71fba32c0b" url: "https://pub.dev" source: hosted - version: "1.1.3" + version: "1.2.0" matcher: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index d0525137..8ffa130b 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -11,7 +11,7 @@ environment: dependencies: flutter: sdk: flutter - macos_window_utils: ^1.1.3 + macos_window_utils: ^1.2.0 gradient_borders: ^1.0.0 appkit_ui_element_colors: ^1.0.0 From 6a5dbe6a8f92d0601a47fb836e34e17dd2f492bb Mon Sep 17 00:00:00 2001 From: Adrian Samoticha Date: Fri, 11 Aug 2023 09:35:29 +0200 Subject: [PATCH 093/151] initialize `_isWindowMain` properly --- lib/src/utils.dart | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/lib/src/utils.dart b/lib/src/utils.dart index cc3f765c..19a23909 100644 --- a/lib/src/utils.dart +++ b/lib/src/utils.dart @@ -101,9 +101,7 @@ class WindowMainStateListener { NSWindowDelegateHandle? handle; /// Whether the window is currently the main window. - bool _isWindowMain = - true; // TODO: Initialize properly once macos_window_utils supports that, - // see https://github.com/macosui/macos_window_utils.dart/issues/31. + bool _isWindowMain = true; /// Whether the window is currently the main window. bool get isWindowMain => _isWindowMain; @@ -120,6 +118,12 @@ class WindowMainStateListener { if (kIsWeb) return; if (!Platform.isMacOS) return; + _initDelegate(); + _initIsWindowMain(); + } + + /// Initializes the [NSWindowDelegate] to listen for main window changes. + void _initDelegate() { final delegate = _WindowMainStateListenerDelegate( onWindowDidBecomeMain: () { _isWindowMain = true; @@ -133,6 +137,12 @@ class WindowMainStateListener { handle = WindowManipulator.addNSWindowDelegate(delegate); } + /// Initializes the [_isWindowMain] variable. + Future _initIsWindowMain() async { + _isWindowMain = await WindowManipulator.isMainWindow(); + _windowMainStateStreamController.add(_isWindowMain); + } + /// Deinitializes the listener. void deinit() { handle?.removeFromHandler(); From 6aa9d8ac802fc1dc505c8c2b7e3b0920b07652d3 Mon Sep 17 00:00:00 2001 From: Adrian Samoticha Date: Fri, 11 Aug 2023 09:37:18 +0200 Subject: [PATCH 094/151] rename `isWindowMain` to `isMainWindow` --- lib/src/buttons/push_button.dart | 4 ++-- lib/src/utils.dart | 14 +++++++------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/lib/src/buttons/push_button.dart b/lib/src/buttons/push_button.dart index a295498b..678c8510 100644 --- a/lib/src/buttons/push_button.dart +++ b/lib/src/buttons/push_button.dart @@ -261,7 +261,7 @@ class PushButtonState extends State BoxDecoration _getBoxDecoration() { // If the window isn’t currently the main window (that is, it is not in // focus), make the button look as if it was a secondary button. - final isWindowMain = WindowMainStateListener.instance.isWindowMain; + final isWindowMain = WindowMainStateListener.instance.isMainWindow; return _BoxDecorationBuilder.buildBoxDecoration( accentColor: _accentColor, @@ -278,7 +278,7 @@ class PushButtonState extends State // If the window isn’t currently the main window (that is, it is not in // focus), make the button look as if it was a secondary button. - final isWindowMain = WindowMainStateListener.instance.isWindowMain; + final isWindowMain = WindowMainStateListener.instance.isMainWindow; return MacosDynamicColor.resolve( widget.color ?? diff --git a/lib/src/utils.dart b/lib/src/utils.dart index 19a23909..3d9a8693 100644 --- a/lib/src/utils.dart +++ b/lib/src/utils.dart @@ -101,10 +101,10 @@ class WindowMainStateListener { NSWindowDelegateHandle? handle; /// Whether the window is currently the main window. - bool _isWindowMain = true; + bool _isMainWindow = true; /// Whether the window is currently the main window. - bool get isWindowMain => _isWindowMain; + bool get isMainWindow => _isMainWindow; /// Notifies listeners when the window’s main state changes. final _windowMainStateStreamController = StreamController.broadcast(); @@ -126,21 +126,21 @@ class WindowMainStateListener { void _initDelegate() { final delegate = _WindowMainStateListenerDelegate( onWindowDidBecomeMain: () { - _isWindowMain = true; + _isMainWindow = true; _windowMainStateStreamController.add(true); }, onWindowDidResignMain: () { - _isWindowMain = false; + _isMainWindow = false; _windowMainStateStreamController.add(false); }, ); handle = WindowManipulator.addNSWindowDelegate(delegate); } - /// Initializes the [_isWindowMain] variable. + /// Initializes the [_isMainWindow] variable. Future _initIsWindowMain() async { - _isWindowMain = await WindowManipulator.isMainWindow(); - _windowMainStateStreamController.add(_isWindowMain); + _isMainWindow = await WindowManipulator.isMainWindow(); + _windowMainStateStreamController.add(_isMainWindow); } /// Deinitializes the listener. From db655a7c8e774d8d5397b2c89685493681595e7f Mon Sep 17 00:00:00 2001 From: Adrian Samoticha Date: Fri, 11 Aug 2023 09:39:56 +0200 Subject: [PATCH 095/151] replace `print` with `debugPrint` --- lib/src/utils.dart | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/src/utils.dart b/lib/src/utils.dart index 3d9a8693..470b803f 100644 --- a/lib/src/utils.dart +++ b/lib/src/utils.dart @@ -267,8 +267,7 @@ class AccentColorListener { return hueComponentToAccentColor[hueComponent]!; } - // ignore: avoid_print - print( + debugPrint( 'Warning: Falling back on slow accent color resolution. It’s possible ' 'that the accent colors have changed in a recent version of macOS, thus ' 'invalidating macos_ui’s accent colors, which were captured in macOS ' From 2058812c5ec09f09b713e8de8a9cbcc1288db89c Mon Sep 17 00:00:00 2001 From: Adrian Samoticha Date: Fri, 11 Aug 2023 09:50:42 +0200 Subject: [PATCH 096/151] deprecate properties of `PushButtonThemeData` rather than `PushButtonThemeData` itself to avoid breaking changes --- lib/src/theme/macos_theme.dart | 7 ++--- lib/src/theme/push_button_theme.dart | 45 ++++++++++++++++++++++++---- 2 files changed, 42 insertions(+), 10 deletions(-) diff --git a/lib/src/theme/macos_theme.dart b/lib/src/theme/macos_theme.dart index 87405882..0bda4d11 100644 --- a/lib/src/theme/macos_theme.dart +++ b/lib/src/theme/macos_theme.dart @@ -514,8 +514,7 @@ class MacosThemeData with Diagnosticable { typography: MacosTypography.lerp(a.typography, b.typography, t), helpButtonTheme: HelpButtonThemeData.lerp(a.helpButtonTheme, b.helpButtonTheme, t), - pushButtonTheme: - PushButtonThemeData.lerp(a.pushButtonTheme, b.pushButtonTheme, t), + pushButtonTheme: a.pushButtonTheme, tooltipTheme: MacosTooltipThemeData.lerp(a.tooltipTheme, b.tooltipTheme, t), visualDensity: VisualDensity.lerp(a.visualDensity, b.visualDensity, t), @@ -581,7 +580,7 @@ class MacosThemeData with Diagnosticable { canvasColor: canvasColor ?? this.canvasColor, dividerColor: dividerColor ?? this.dividerColor, typography: this.typography.merge(typography), - pushButtonTheme: this.pushButtonTheme.merge(pushButtonTheme), + pushButtonTheme: this.pushButtonTheme, helpButtonTheme: this.helpButtonTheme.merge(helpButtonTheme), tooltipTheme: this.tooltipTheme.merge(tooltipTheme), visualDensity: visualDensity ?? this.visualDensity, @@ -605,7 +604,7 @@ class MacosThemeData with Diagnosticable { canvasColor: other.canvasColor, dividerColor: other.dividerColor, typography: typography.merge(other.typography), - pushButtonTheme: pushButtonTheme.merge(other.pushButtonTheme), + pushButtonTheme: pushButtonTheme, helpButtonTheme: helpButtonTheme.merge(other.helpButtonTheme), tooltipTheme: tooltipTheme.merge(other.tooltipTheme), visualDensity: other.visualDensity, diff --git a/lib/src/theme/push_button_theme.dart b/lib/src/theme/push_button_theme.dart index dc35c2b7..0c69aebd 100644 --- a/lib/src/theme/push_button_theme.dart +++ b/lib/src/theme/push_button_theme.dart @@ -7,12 +7,8 @@ import 'package:macos_ui/src/library.dart'; /// See also: /// /// * [PushButtonThemeData], which is used to configure this theme. -@Deprecated("'PushButton' no longer uses singular colors and therefore cannot " - "be themed using a 'PushButtonTheme'.") class PushButtonTheme extends InheritedTheme { /// Create a [PushButtonTheme]. - /// - /// The [data] parameter must not be null. const PushButtonTheme({ super.key, required this.data, @@ -20,6 +16,9 @@ class PushButtonTheme extends InheritedTheme { }); /// The configuration of this theme. + @Deprecated( + "'PushButton' no longer uses singular colors and therefore cannot " + "be themed using a 'PushButtonTheme'.") final PushButtonThemeData data; /// The closest instance of this class that encloses the given context. @@ -32,6 +31,9 @@ class PushButtonTheme extends InheritedTheme { /// ```dart /// PushButtonTheme theme = PushButtonTheme.of(context); /// ``` + @Deprecated( + "'PushButton' no longer uses singular colors and therefore cannot " + "be themed using a 'PushButtonTheme'.") static PushButtonThemeData of(BuildContext context) { final PushButtonTheme? buttonTheme = context.dependOnInheritedWidgetOfExactType(); @@ -39,11 +41,17 @@ class PushButtonTheme extends InheritedTheme { } @override + @Deprecated( + "'PushButton' no longer uses singular colors and therefore cannot " + "be themed using a 'PushButtonTheme'.") Widget wrap(BuildContext context, Widget child) { return PushButtonTheme(data: data, child: child); } @override + @Deprecated( + "'PushButton' no longer uses singular colors and therefore cannot " + "be themed using a 'PushButtonTheme'.") bool updateShouldNotify(PushButtonTheme oldWidget) => data != oldWidget.data; } @@ -56,8 +64,6 @@ class PushButtonTheme extends InheritedTheme { /// * [PushButtonTheme], the theme which is configured with this class. /// * [MacosThemeData.pushButtonTheme], which can be used to override the default /// style for [PushButton]s below the overall [MacosTheme]. -@Deprecated("'PushButton' no longer uses singular colors and therefore cannot " - "be themed using a 'PushButtonTheme'.") class PushButtonThemeData with Diagnosticable { /// Creates a [PushButtonThemeData]. const PushButtonThemeData({ @@ -67,15 +73,27 @@ class PushButtonThemeData with Diagnosticable { }); /// The default background color for [PushButton] + @Deprecated( + "'PushButton' no longer uses singular colors and therefore cannot " + "be themed using a 'PushButtonTheme'.") final Color? color; /// The default disabled color for [PushButton] + @Deprecated( + "'PushButton' no longer uses singular colors and therefore cannot " + "be themed using a 'PushButtonTheme'.") final Color? disabledColor; /// The default secondary color (e.g. Cancel/Go back buttons) for [PushButton] + @Deprecated( + "'PushButton' no longer uses singular colors and therefore cannot " + "be themed using a 'PushButtonTheme'.") final Color? secondaryColor; /// Copies this [PushButtonThemeData] into another. + @Deprecated( + "'PushButton' no longer uses singular colors and therefore cannot " + "be themed using a 'PushButtonTheme'.") PushButtonThemeData copyWith({ Color? color, Color? disabledColor, @@ -91,6 +109,9 @@ class PushButtonThemeData with Diagnosticable { /// Linearly interpolate between two [PushButtonThemeData]. /// /// All the properties must be non-null. + @Deprecated( + "'PushButton' no longer uses singular colors and therefore cannot " + "be themed using a 'PushButtonTheme'.") static PushButtonThemeData lerp( PushButtonThemeData a, PushButtonThemeData b, @@ -104,6 +125,9 @@ class PushButtonThemeData with Diagnosticable { } @override + @Deprecated( + "'PushButton' no longer uses singular colors and therefore cannot " + "be themed using a 'PushButtonTheme'.") bool operator ==(Object other) => identical(this, other) || other is PushButtonThemeData && @@ -113,9 +137,15 @@ class PushButtonThemeData with Diagnosticable { secondaryColor?.value == other.secondaryColor?.value; @override + @Deprecated( + "'PushButton' no longer uses singular colors and therefore cannot " + "be themed using a 'PushButtonTheme'.") int get hashCode => color.hashCode ^ disabledColor.hashCode; @override + @Deprecated( + "'PushButton' no longer uses singular colors and therefore cannot " + "be themed using a 'PushButtonTheme'.") void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); properties.add(ColorProperty('color', color)); @@ -124,6 +154,9 @@ class PushButtonThemeData with Diagnosticable { } /// Merges this [PushButtonThemeData] with another. + @Deprecated( + "'PushButton' no longer uses singular colors and therefore cannot " + "be themed using a 'PushButtonTheme'.") PushButtonThemeData merge(PushButtonThemeData? other) { if (other == null) return this; return copyWith( From c017df4f1563fe1ef8c9a641b340fca32b72e50c Mon Sep 17 00:00:00 2001 From: Adrian Samoticha Date: Fri, 11 Aug 2023 09:51:36 +0200 Subject: [PATCH 097/151] fix typo in `AccentColor` documentation --- lib/src/enums/accent_color.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/enums/accent_color.dart b/lib/src/enums/accent_color.dart index 3e5ecc8e..95ab8ea2 100644 --- a/lib/src/enums/accent_color.dart +++ b/lib/src/enums/accent_color.dart @@ -1,5 +1,5 @@ /// The macOS accent color which can be changed by the user in *System Settings* -/// → *Apperance* → *Accent color*. +/// → *Appearance* → *Accent color*. enum AccentColor { /// The blue accent color. blue, From 5757632851d3be8da86ff2a257c5be20a5c583b0 Mon Sep 17 00:00:00 2001 From: Adrian Samoticha Date: Fri, 11 Aug 2023 09:52:22 +0200 Subject: [PATCH 098/151] fix typo in `AccentColorListener` docs --- lib/src/utils.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/utils.dart b/lib/src/utils.dart index 470b803f..5ef5a178 100644 --- a/lib/src/utils.dart +++ b/lib/src/utils.dart @@ -207,7 +207,7 @@ class AccentColorListener { /// changes. Stream get onChangedStream => _accentColorStreamController.stream; - /// A stream subcription for the [SystemColorObserver] stream. + /// A stream subscription for the [SystemColorObserver] stream. StreamSubscription? _systemColorObserverStreamSubscription; /// Initializes this class. From c97b9013f15947f84ab2b6080514cb25a4966fd8 Mon Sep 17 00:00:00 2001 From: Adrian Samoticha Date: Fri, 11 Aug 2023 10:10:00 +0200 Subject: [PATCH 099/151] remove push button theme test --- test/theme/push_button_theme_test.dart | 97 -------------------------- 1 file changed, 97 deletions(-) delete mode 100644 test/theme/push_button_theme_test.dart diff --git a/test/theme/push_button_theme_test.dart b/test/theme/push_button_theme_test.dart deleted file mode 100644 index 2ff90243..00000000 --- a/test/theme/push_button_theme_test.dart +++ /dev/null @@ -1,97 +0,0 @@ -import 'package:flutter/foundation.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', () { - test('lerps from light to dark', () { - final actual = - PushButtonThemeData.lerp(_pushButtonTheme, _pushButtonThemeDark, 1); - - expect(actual, _pushButtonThemeDark); - }); - - test('lerps from dark to light', () { - final actual = - PushButtonThemeData.lerp(_pushButtonThemeDark, _pushButtonTheme, 1); - - expect(actual, _pushButtonTheme); - }); - - test('copyWith, hashCode, ==', () { - expect( - const PushButtonThemeData(), - const PushButtonThemeData().copyWith(), - ); - expect( - const PushButtonThemeData().hashCode, - const PushButtonThemeData().copyWith().hashCode, - ); - }); - - testWidgets('debugFillProperties', (tester) async { - final builder = DiagnosticPropertiesBuilder(); - PushButtonThemeData( - color: MacosColors.appleBlue, - disabledColor: MacosColors.systemGrayColor.color, - secondaryColor: MacosColors.controlColor.color, - ).debugFillProperties(builder); - - final description = builder.properties - .where((node) => !node.isFiltered(DiagnosticLevel.info)) - .map((node) => node.toString()) - .toList(); - - expect( - description, - [ - 'color: MacosColor(0xff0433ff)', - 'disabledColor: MacosColor(0xff8e8e93)', - 'secondaryColor: Color(0x19000000)', - ], - ); - }); - - testWidgets('Default values in widget tree', (tester) async { - late BuildContext capturedContext; - await tester.pumpWidget( - MacosApp( - home: MacosWindow( - disableWallpaperTinting: true, - child: MacosScaffold( - children: [ - ContentArea( - builder: (context, _) { - capturedContext = context; - return const PushButton( - controlSize: ControlSize.regular, - child: Text('Push me'), - ); - }, - ), - ], - ), - ), - ), - ); - - final theme = PushButtonTheme.of(capturedContext); - expect(theme.color, const Color(0xff007aff)); - expect(theme.disabledColor, const Color.fromRGBO(244, 245, 245, 1.0)); - expect(theme.secondaryColor, MacosColors.white); - }); - }); -} - -final _pushButtonTheme = PushButtonThemeData( - color: MacosColors.appleRed, - disabledColor: MacosColors.systemGrayColor.color, - secondaryColor: MacosColors.controlColor.color, -); - -final _pushButtonThemeDark = PushButtonThemeData( - color: MacosColors.appleBlue, - disabledColor: MacosColors.systemGrayColor.darkColor, - secondaryColor: MacosColors.controlColor.darkColor, -); From ce558758b10098a1022c51a3ed43fa724352f653 Mon Sep 17 00:00:00 2001 From: Adrian Samoticha <86920182+Adrian-Samoticha@users.noreply.github.com> Date: Sat, 12 Aug 2023 20:07:47 +0200 Subject: [PATCH 100/151] Update CHANGELOG.md Change the wording of the changelog entry for version 2.0.1. Co-authored-by: Reuben Turner --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e1c43f00..95ac5179 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,5 @@ ## [2.0.1] -* `PushButton` has received a facelift. It now mimics the look of native macOS buttons more closely. +* `PushButton` has received a facelift. It now mimics the look and feel of native macOS buttons more closely. * **Note:** As a result, its `pressedOpacity` property and the `PushButtonTheme` class have been deprecated. ## [2.0.0] From 3c1d16f701a1a44647a416de31472593b96971d9 Mon Sep 17 00:00:00 2001 From: Adrian Samoticha <86920182+Adrian-Samoticha@users.noreply.github.com> Date: Sat, 12 Aug 2023 20:09:54 +0200 Subject: [PATCH 101/151] change documentation of `WindowMainStateListener` Co-authored-by: Reuben Turner --- lib/src/utils.dart | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/src/utils.dart b/lib/src/utils.dart index 5ef5a178..30e89f30 100644 --- a/lib/src/utils.dart +++ b/lib/src/utils.dart @@ -91,8 +91,11 @@ class MacOSBrightnessOverrideHandler { } } -/// A class that listens for changes to the application's window being the main -/// window, and notifies listeners. +/// A class that listens for changes to the application's main window. +/// +/// A common use-case for responding to such changes would be to mute the colors of certain +/// primary UI elements when the window is no longer in focus, which is something native +/// macOS applications do out of the box. class WindowMainStateListener { /// A shared instance of [WindowMainStateListener]. static final instance = WindowMainStateListener(); From ad79ce5ca92a59fc38db263d84fabd21c60b5e89 Mon Sep 17 00:00:00 2001 From: Adrian Samoticha <86920182+Adrian-Samoticha@users.noreply.github.com> Date: Sat, 12 Aug 2023 20:16:30 +0200 Subject: [PATCH 102/151] update documentation Co-authored-by: Reuben Turner --- lib/src/utils.dart | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/src/utils.dart b/lib/src/utils.dart index 30e89f30..876504b5 100644 --- a/lib/src/utils.dart +++ b/lib/src/utils.dart @@ -178,7 +178,9 @@ class _WindowMainStateListenerDelegate extends NSWindowDelegate { void windowDidResignMain() => onWindowDidResignMain(); } -/// A class that listens to accent color changes. +/// A class that listens to changes to the user's selected system accent color. +/// +/// Native macOS applications respond to such changes immediately. class AccentColorListener { /// A shared instance of [AccentColorListener]. static final instance = AccentColorListener(); From a90c980f8d9822ebd8a16048ca555d2a2da4c70a Mon Sep 17 00:00:00 2001 From: Adrian Samoticha <86920182+Adrian-Samoticha@users.noreply.github.com> Date: Sat, 12 Aug 2023 20:19:28 +0200 Subject: [PATCH 103/151] update documentation Co-authored-by: Reuben Turner --- lib/src/utils.dart | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/src/utils.dart b/lib/src/utils.dart index 876504b5..1a9ae63a 100644 --- a/lib/src/utils.dart +++ b/lib/src/utils.dart @@ -208,8 +208,9 @@ class AccentColorListener { /// Notifies listeners when the accent color changes. final _accentColorStreamController = StreamController.broadcast(); - /// An accent color stream. Emits a new value whenever the accent color - /// changes. + /// Streams the user's system accent color selection. + /// + /// Emits a new value whenever the system accent color selection changes. Stream get onChangedStream => _accentColorStreamController.stream; /// A stream subscription for the [SystemColorObserver] stream. From d229e15a4200094487e71dee2ed7b7dfacd75788 Mon Sep 17 00:00:00 2001 From: Adrian Samoticha <86920182+Adrian-Samoticha@users.noreply.github.com> Date: Sat, 12 Aug 2023 20:20:02 +0200 Subject: [PATCH 104/151] update documentation Co-authored-by: Reuben Turner --- lib/src/utils.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/utils.dart b/lib/src/utils.dart index 1a9ae63a..1d84d1b0 100644 --- a/lib/src/utils.dart +++ b/lib/src/utils.dart @@ -284,7 +284,7 @@ class AccentColorListener { return _slowlyResolveAccentColorFromHueComponent(hueComponent); } - /// This is a fallback method in case the above method fails. + /// This is a fallback method in case [_resolveAccentColorFromHueComponent] fails. AccentColor _slowlyResolveAccentColorFromHueComponent(double hueComponent) { final entries = hueComponentToAccentColor.entries; var lowestDistance = double.maxFinite; From 7d2ec54e99bd76dbba3b2950ff3ce99d7779106e Mon Sep 17 00:00:00 2001 From: Adrian Samoticha <86920182+Adrian-Samoticha@users.noreply.github.com> Date: Sat, 12 Aug 2023 20:29:25 +0200 Subject: [PATCH 105/151] change `var` to `final` Co-authored-by: Reuben Turner --- lib/src/utils.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/utils.dart b/lib/src/utils.dart index 1d84d1b0..ac935d5f 100644 --- a/lib/src/utils.dart +++ b/lib/src/utils.dart @@ -290,7 +290,7 @@ class AccentColorListener { var lowestDistance = double.maxFinite; var toBeReturnedAccentColor = AccentColor.values.first; - for (var entry in entries) { + for (final entry in entries) { final distance = _distanceBetweenHueComponents(hueComponent, entry.key); if (distance < lowestDistance) { lowestDistance = distance; From a3183b55e99564f00eb900dd530d034427e9c67b Mon Sep 17 00:00:00 2001 From: Adrian Samoticha <86920182+Adrian-Samoticha@users.noreply.github.com> Date: Sat, 12 Aug 2023 20:30:00 +0200 Subject: [PATCH 106/151] update deprecation message Co-authored-by: Reuben Turner --- lib/src/buttons/push_button.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/src/buttons/push_button.dart b/lib/src/buttons/push_button.dart index 678c8510..c49ca98f 100644 --- a/lib/src/buttons/push_button.dart +++ b/lib/src/buttons/push_button.dart @@ -171,8 +171,8 @@ class PushButton extends StatefulWidget { /// /// This defaults to 0.4. If null, opacity will not change on pressed if using /// your own custom effects is desired. - @Deprecated("'PushButton' now attempts to mimic macOS’ look and feel, " - "therefore, its opacity no longer changes when it is pressed.") + @Deprecated("'PushButton' animations now match their native macOS’ counterparts. " + "Therefore, its opacity no longer changes when it is pressed.") final double? pressedOpacity; /// The radius of the button's corners when it has a background color. From 92658d966486d750969ab70d56c212f4e87a17ee Mon Sep 17 00:00:00 2001 From: Adrian Samoticha Date: Sat, 12 Aug 2023 20:41:27 +0200 Subject: [PATCH 107/151] improve formatting --- lib/src/utils.dart | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/src/utils.dart b/lib/src/utils.dart index ac935d5f..b67fec3d 100644 --- a/lib/src/utils.dart +++ b/lib/src/utils.dart @@ -91,7 +91,7 @@ class MacOSBrightnessOverrideHandler { } } -/// A class that listens for changes to the application's main window. +/// A class that listens for changes to the application's main window. /// /// A common use-case for responding to such changes would be to mute the colors of certain /// primary UI elements when the window is no longer in focus, which is something native @@ -208,7 +208,7 @@ class AccentColorListener { /// Notifies listeners when the accent color changes. final _accentColorStreamController = StreamController.broadcast(); - /// Streams the user's system accent color selection. + /// Streams the user's system accent color selection. /// /// Emits a new value whenever the system accent color selection changes. Stream get onChangedStream => _accentColorStreamController.stream; @@ -284,7 +284,8 @@ class AccentColorListener { return _slowlyResolveAccentColorFromHueComponent(hueComponent); } - /// This is a fallback method in case [_resolveAccentColorFromHueComponent] fails. + /// This is a fallback method in case [_resolveAccentColorFromHueComponent] + /// fails. AccentColor _slowlyResolveAccentColorFromHueComponent(double hueComponent) { final entries = hueComponentToAccentColor.entries; var lowestDistance = double.maxFinite; From 7b81123191ec09745d68884b9b3d4cb58ab21cfe Mon Sep 17 00:00:00 2001 From: Adrian Samoticha Date: Sat, 12 Aug 2023 20:42:00 +0200 Subject: [PATCH 108/151] improve formatting --- lib/src/utils.dart | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/src/utils.dart b/lib/src/utils.dart index b67fec3d..18c42a97 100644 --- a/lib/src/utils.dart +++ b/lib/src/utils.dart @@ -93,9 +93,9 @@ class MacOSBrightnessOverrideHandler { /// A class that listens for changes to the application's main window. /// -/// A common use-case for responding to such changes would be to mute the colors of certain -/// primary UI elements when the window is no longer in focus, which is something native -/// macOS applications do out of the box. +/// A common use-case for responding to such changes would be to mute the colors +/// of certain primary UI elements when the window is no longer in focus, which +/// is something native macOS applications do out of the box. class WindowMainStateListener { /// A shared instance of [WindowMainStateListener]. static final instance = WindowMainStateListener(); From fb22efa231a91623df2c44d7532d145c86645d87 Mon Sep 17 00:00:00 2001 From: Adrian Samoticha Date: Sat, 12 Aug 2023 20:47:47 +0200 Subject: [PATCH 109/151] fix grammatical error in debug print message --- lib/src/utils.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/utils.dart b/lib/src/utils.dart index 18c42a97..57e980a7 100644 --- a/lib/src/utils.dart +++ b/lib/src/utils.dart @@ -276,7 +276,7 @@ class AccentColorListener { debugPrint( 'Warning: Falling back on slow accent color resolution. It’s possible ' 'that the accent colors have changed in a recent version of macOS, thus ' - 'invalidating macos_ui’s accent colors, which were captured in macOS ' + 'invalidating macos_ui’s accent colors, which were captured on macOS ' 'Ventura. If you see this message, please notify a maintainer of the ' 'macos_ui package.', ); From 040b4a0ccb31040ba82eb8b390532d07dcce32fe Mon Sep 17 00:00:00 2001 From: Adrian Samoticha Date: Sun, 13 Aug 2023 10:39:32 +0200 Subject: [PATCH 110/151] improve formatting --- lib/src/buttons/push_button.dart | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/src/buttons/push_button.dart b/lib/src/buttons/push_button.dart index c49ca98f..632ea65c 100644 --- a/lib/src/buttons/push_button.dart +++ b/lib/src/buttons/push_button.dart @@ -171,8 +171,9 @@ class PushButton extends StatefulWidget { /// /// This defaults to 0.4. If null, opacity will not change on pressed if using /// your own custom effects is desired. - @Deprecated("'PushButton' animations now match their native macOS’ counterparts. " - "Therefore, its opacity no longer changes when it is pressed.") + @Deprecated("'PushButton' animations now match their native macOS’ " + "counterparts. Therefore, its opacity no longer changes when it is " + "pressed.") final double? pressedOpacity; /// The radius of the button's corners when it has a background color. From 3a8aecb05a8901d8719f35484101b98659394124 Mon Sep 17 00:00:00 2001 From: Adrian Samoticha Date: Tue, 15 Aug 2023 18:58:21 +0200 Subject: [PATCH 111/151] rename `deinit` methods to `dispose` --- lib/src/utils.dart | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/src/utils.dart b/lib/src/utils.dart index 57e980a7..e90be74d 100644 --- a/lib/src/utils.dart +++ b/lib/src/utils.dart @@ -100,7 +100,7 @@ class WindowMainStateListener { /// A shared instance of [WindowMainStateListener]. static final instance = WindowMainStateListener(); - /// A [NSWindowDelegateHandle], to be used when deiniting the listener. + /// A [NSWindowDelegateHandle], to be used when disposing the listener. NSWindowDelegateHandle? handle; /// Whether the window is currently the main window. @@ -146,8 +146,8 @@ class WindowMainStateListener { _windowMainStateStreamController.add(_isMainWindow); } - /// Deinitializes the listener. - void deinit() { + /// Disposes this listener. + void dispose() { handle?.removeFromHandler(); } @@ -225,8 +225,8 @@ class AccentColorListener { _initSystemColorObserver(); } - /// Deinitializes this class. - void deinit() { + /// Disposes this listener. + void dispose() { _systemColorObserverStreamSubscription?.cancel(); } From 48c2c26a2a6774b34fcf55ad770decebeaf01be6 Mon Sep 17 00:00:00 2001 From: Adrian Samoticha Date: Tue, 15 Aug 2023 19:00:25 +0200 Subject: [PATCH 112/151] move `utils.dart` into `utils` directory --- lib/src/library.dart | 2 +- lib/src/{ => utils}/utils.dart | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename lib/src/{ => utils}/utils.dart (100%) diff --git a/lib/src/library.dart b/lib/src/library.dart index 37fb1228..2273d447 100644 --- a/lib/src/library.dart +++ b/lib/src/library.dart @@ -32,4 +32,4 @@ export 'package:flutter/material.dart' MaterialState; export 'package:flutter/widgets.dart'; -export 'utils.dart'; +export 'utils/utils.dart'; diff --git a/lib/src/utils.dart b/lib/src/utils/utils.dart similarity index 100% rename from lib/src/utils.dart rename to lib/src/utils/utils.dart From ce94aa915ebf6289ed228f312f751674699e4ee3 Mon Sep 17 00:00:00 2001 From: Adrian Samoticha Date: Tue, 15 Aug 2023 19:03:08 +0200 Subject: [PATCH 113/151] move `WindowMainStateListener` to separate file --- lib/src/utils/utils.dart | 89 +----------------- lib/src/utils/window_main_state_listener.dart | 92 +++++++++++++++++++ 2 files changed, 94 insertions(+), 87 deletions(-) create mode 100644 lib/src/utils/window_main_state_listener.dart diff --git a/lib/src/utils/utils.dart b/lib/src/utils/utils.dart index e90be74d..0bfd8c2d 100644 --- a/lib/src/utils/utils.dart +++ b/lib/src/utils/utils.dart @@ -8,6 +8,8 @@ import 'package:macos_ui/macos_ui.dart'; import 'package:macos_ui/src/enums/accent_color.dart'; import 'package:macos_ui/src/library.dart'; +export 'window_main_state_listener.dart'; + /// Asserts that the given context has a [MacosTheme] ancestor. /// /// To call this function, use the following pattern, typically in the @@ -91,93 +93,6 @@ class MacOSBrightnessOverrideHandler { } } -/// A class that listens for changes to the application's main window. -/// -/// A common use-case for responding to such changes would be to mute the colors -/// of certain primary UI elements when the window is no longer in focus, which -/// is something native macOS applications do out of the box. -class WindowMainStateListener { - /// A shared instance of [WindowMainStateListener]. - static final instance = WindowMainStateListener(); - - /// A [NSWindowDelegateHandle], to be used when disposing the listener. - NSWindowDelegateHandle? handle; - - /// Whether the window is currently the main window. - bool _isMainWindow = true; - - /// Whether the window is currently the main window. - bool get isMainWindow => _isMainWindow; - - /// Notifies listeners when the window’s main state changes. - final _windowMainStateStreamController = StreamController.broadcast(); - - /// A stream of the window’s main state. Emits a new value whenever the state - /// changes. - Stream get onChangedStream => _windowMainStateStreamController.stream; - - /// Initializes the listener. This should only be called once. - void _init() { - if (kIsWeb) return; - if (!Platform.isMacOS) return; - - _initDelegate(); - _initIsWindowMain(); - } - - /// Initializes the [NSWindowDelegate] to listen for main window changes. - void _initDelegate() { - final delegate = _WindowMainStateListenerDelegate( - onWindowDidBecomeMain: () { - _isMainWindow = true; - _windowMainStateStreamController.add(true); - }, - onWindowDidResignMain: () { - _isMainWindow = false; - _windowMainStateStreamController.add(false); - }, - ); - handle = WindowManipulator.addNSWindowDelegate(delegate); - } - - /// Initializes the [_isMainWindow] variable. - Future _initIsWindowMain() async { - _isMainWindow = await WindowManipulator.isMainWindow(); - _windowMainStateStreamController.add(_isMainWindow); - } - - /// Disposes this listener. - void dispose() { - handle?.removeFromHandler(); - } - - /// A class that listens for changes to the application's window being the - /// main window, and notifies listeners. - WindowMainStateListener() { - _init(); - } -} - -/// The [NSWindowDelegate] used by [WindowMainStateListener]. -class _WindowMainStateListenerDelegate extends NSWindowDelegate { - _WindowMainStateListenerDelegate({ - required this.onWindowDidBecomeMain, - required this.onWindowDidResignMain, - }); - - /// Called when the window becomes the main window. - final void Function() onWindowDidBecomeMain; - - /// Called when the window resigns as the main window. - final void Function() onWindowDidResignMain; - - @override - void windowDidBecomeMain() => onWindowDidBecomeMain(); - - @override - void windowDidResignMain() => onWindowDidResignMain(); -} - /// A class that listens to changes to the user's selected system accent color. /// /// Native macOS applications respond to such changes immediately. diff --git a/lib/src/utils/window_main_state_listener.dart b/lib/src/utils/window_main_state_listener.dart new file mode 100644 index 00000000..932dcb3d --- /dev/null +++ b/lib/src/utils/window_main_state_listener.dart @@ -0,0 +1,92 @@ +import 'dart:async'; +import 'dart:io'; + +import 'package:flutter/foundation.dart'; +import 'package:macos_ui/macos_ui.dart'; + +/// A class that listens for changes to the application’s main window. +/// +/// A common use-case for responding to such changes would be to mute the colors +/// of certain primary UI elements when the window is no longer in focus, which +/// is something native macOS applications do out of the box. +class WindowMainStateListener { + /// A shared instance of [WindowMainStateListener]. + static final instance = WindowMainStateListener(); + + /// A [NSWindowDelegateHandle], to be used when disposing the listener. + NSWindowDelegateHandle? handle; + + /// Whether the window is currently the main window. + bool _isMainWindow = true; + + /// Whether the window is currently the main window. + bool get isMainWindow => _isMainWindow; + + /// Notifies listeners when the window’s main state changes. + final _windowMainStateStreamController = StreamController.broadcast(); + + /// A stream of the window’s main state. Emits a new value whenever the state + /// changes. + Stream get onChangedStream => _windowMainStateStreamController.stream; + + /// Initializes the listener. This should only be called once. + void _init() { + if (kIsWeb) return; + if (!Platform.isMacOS) return; + + _initDelegate(); + _initIsWindowMain(); + } + + /// Initializes the [NSWindowDelegate] to listen for main window changes. + void _initDelegate() { + final delegate = _WindowMainStateListenerDelegate( + onWindowDidBecomeMain: () { + _isMainWindow = true; + _windowMainStateStreamController.add(true); + }, + onWindowDidResignMain: () { + _isMainWindow = false; + _windowMainStateStreamController.add(false); + }, + ); + handle = WindowManipulator.addNSWindowDelegate(delegate); + } + + /// Initializes the [_isMainWindow] variable. + Future _initIsWindowMain() async { + _isMainWindow = await WindowManipulator.isMainWindow(); + _windowMainStateStreamController.add(_isMainWindow); + } + + /// Disposes this listener. + void dispose() { + handle?.removeFromHandler(); + } + + /// A class that listens for changes to the application’s window being the + /// main window, and notifies listeners. + WindowMainStateListener() { + _init(); + } +} + +/// The [NSWindowDelegate] used by [WindowMainStateListener]. +class _WindowMainStateListenerDelegate extends NSWindowDelegate { + _WindowMainStateListenerDelegate({ + required this.onWindowDidBecomeMain, + required this.onWindowDidResignMain, + }); + + /// Called when the window becomes the main window. + final void Function() onWindowDidBecomeMain; + + /// Called when the window resigns as the main window. + final void Function() onWindowDidResignMain; + + @override + void windowDidBecomeMain() => onWindowDidBecomeMain(); + + @override + void windowDidResignMain() => onWindowDidResignMain(); +} From ef8c156b62bc18058f8133916206c2db44bef8d9 Mon Sep 17 00:00:00 2001 From: Adrian Samoticha Date: Tue, 15 Aug 2023 19:05:10 +0200 Subject: [PATCH 114/151] move `AccentColorListener` to separate file --- lib/src/utils/accent_color_listener.dart | 143 +++++++++++++++++++++++ lib/src/utils/utils.dart | 141 +--------------------- 2 files changed, 144 insertions(+), 140 deletions(-) create mode 100644 lib/src/utils/accent_color_listener.dart diff --git a/lib/src/utils/accent_color_listener.dart b/lib/src/utils/accent_color_listener.dart new file mode 100644 index 00000000..4ba4a491 --- /dev/null +++ b/lib/src/utils/accent_color_listener.dart @@ -0,0 +1,143 @@ +import 'dart:async'; +import 'dart:io'; +import 'dart:math'; + +import 'package:appkit_ui_element_colors/appkit_ui_element_colors.dart'; +import 'package:flutter/foundation.dart'; +import 'package:macos_ui/src/enums/accent_color.dart'; + +/// A class that listens to changes to the user’s selected system accent color. +/// +/// Native macOS applications respond to such changes immediately. +class AccentColorListener { + /// A shared instance of [AccentColorListener]. + static final instance = AccentColorListener(); + + /// A map which maps hue components of the [UiElementColor.controlAccentColor] + /// color captured with the [NSAppearanceName.aqua] appearance in the + /// [NSColorSpace.genericRGB] color space to the corresponding [AccentColor]. + static final hueComponentToAccentColor = { + 0.6085324903200698: AccentColor.blue, + 0.8285987697113538: AccentColor.purple, + 0.9209523937489168: AccentColor.pink, + 0.9861913496946438: AccentColor.red, + 0.06543037411201169: AccentColor.orange, + 0.11813830353929083: AccentColor.yellow, + 0.29428158007138466: AccentColor.green, + 0.0: AccentColor.graphite, + }; + + /// The currently active accent color. + AccentColor? _currentAccentColor; + + /// The currently active accent color. + AccentColor? get currentAccentColor => _currentAccentColor; + + /// Notifies listeners when the accent color changes. + final _accentColorStreamController = StreamController.broadcast(); + + /// Streams the user’s system accent color selection. + /// + /// Emits a new value whenever the system accent color selection changes. + Stream get onChangedStream => _accentColorStreamController.stream; + + /// A stream subscription for the [SystemColorObserver] stream. + StreamSubscription? _systemColorObserverStreamSubscription; + + /// Initializes this class. + void _init() { + if (kIsWeb) return; + if (!Platform.isMacOS) return; + + _initCurrentAccentColor(); + _initSystemColorObserver(); + } + + /// Disposes this listener. + void dispose() { + _systemColorObserverStreamSubscription?.cancel(); + } + + /// Initializes the current accent color. This method is to be called whenever + /// a change is detected. + Future _initCurrentAccentColor() async { + final hueComponent = await _getHueComponent(); + _currentAccentColor = _resolveAccentColorFromHueComponent(hueComponent); + _accentColorStreamController.add(null); + } + + /// Initializes the current system color observer. This method may only be + /// called once. + void _initSystemColorObserver() { + assert(_systemColorObserverStreamSubscription == null); + + _systemColorObserverStreamSubscription = + AppkitUiElementColors.systemColorObserver.stream.listen((_) { + _initCurrentAccentColor(); + _accentColorStreamController.add(null); + }); + } + + /// Returns the hue component of the currently active accent color on macOS. + Future _getHueComponent() async { + final color = await AppkitUiElementColors.getColorComponents( + uiElementColor: UiElementColor.controlAccentColor, + components: const { + NSColorComponent.hueComponent, + }, + colorSpace: NSColorSpace.genericRGB, + appearance: NSAppearanceName.aqua, + ); + + assert(color.containsKey("hueComponent")); + + return color["hueComponent"]!; + } + + /// Returns the [AccentColor] which corresponds to the provided + /// [hueComponent]. + AccentColor _resolveAccentColorFromHueComponent(double hueComponent) { + if (hueComponentToAccentColor.containsKey(hueComponent)) { + return hueComponentToAccentColor[hueComponent]!; + } + + debugPrint( + 'Warning: Falling back on slow accent color resolution. It’s possible ' + 'that the accent colors have changed in a recent version of macOS, thus ' + 'invalidating macos_ui’s accent colors, which were captured on macOS ' + 'Ventura. If you see this message, please notify a maintainer of the ' + 'macos_ui package.', + ); + + return _slowlyResolveAccentColorFromHueComponent(hueComponent); + } + + /// This is a fallback method in case [_resolveAccentColorFromHueComponent] + /// fails. + AccentColor _slowlyResolveAccentColorFromHueComponent(double hueComponent) { + final entries = hueComponentToAccentColor.entries; + var lowestDistance = double.maxFinite; + var toBeReturnedAccentColor = AccentColor.values.first; + + for (final entry in entries) { + final distance = _distanceBetweenHueComponents(hueComponent, entry.key); + if (distance < lowestDistance) { + lowestDistance = distance; + toBeReturnedAccentColor = entry.value; + } + } + + return toBeReturnedAccentColor; + } + + /// Returns the distance between two hue components. + double _distanceBetweenHueComponents(double component1, double component2) { + final rawDifference = (component1 - component2).abs(); + return sin(rawDifference * pi); + } + + /// A class that listens to accent color changes. + AccentColorListener() { + _init(); + } +} diff --git a/lib/src/utils/utils.dart b/lib/src/utils/utils.dart index 0bfd8c2d..52930e52 100644 --- a/lib/src/utils/utils.dart +++ b/lib/src/utils/utils.dart @@ -1,14 +1,11 @@ -import 'dart:async'; import 'dart:io'; -import 'dart:math'; -import 'package:appkit_ui_element_colors/appkit_ui_element_colors.dart'; import 'package:flutter/foundation.dart'; import 'package:macos_ui/macos_ui.dart'; -import 'package:macos_ui/src/enums/accent_color.dart'; import 'package:macos_ui/src/library.dart'; export 'window_main_state_listener.dart'; +export 'accent_color_listener.dart'; /// Asserts that the given context has a [MacosTheme] ancestor. /// @@ -92,139 +89,3 @@ class MacOSBrightnessOverrideHandler { _lastBrightness = currentBrightness; } } - -/// A class that listens to changes to the user's selected system accent color. -/// -/// Native macOS applications respond to such changes immediately. -class AccentColorListener { - /// A shared instance of [AccentColorListener]. - static final instance = AccentColorListener(); - - /// A map which maps hue components of the [UiElementColor.controlAccentColor] - /// color captured with the [NSAppearanceName.aqua] appearance in the - /// [NSColorSpace.genericRGB] color space to the corresponding [AccentColor]. - static final hueComponentToAccentColor = { - 0.6085324903200698: AccentColor.blue, - 0.8285987697113538: AccentColor.purple, - 0.9209523937489168: AccentColor.pink, - 0.9861913496946438: AccentColor.red, - 0.06543037411201169: AccentColor.orange, - 0.11813830353929083: AccentColor.yellow, - 0.29428158007138466: AccentColor.green, - 0.0: AccentColor.graphite, - }; - - /// The currently active accent color. - AccentColor? _currentAccentColor; - - /// The currently active accent color. - AccentColor? get currentAccentColor => _currentAccentColor; - - /// Notifies listeners when the accent color changes. - final _accentColorStreamController = StreamController.broadcast(); - - /// Streams the user's system accent color selection. - /// - /// Emits a new value whenever the system accent color selection changes. - Stream get onChangedStream => _accentColorStreamController.stream; - - /// A stream subscription for the [SystemColorObserver] stream. - StreamSubscription? _systemColorObserverStreamSubscription; - - /// Initializes this class. - void _init() { - if (kIsWeb) return; - if (!Platform.isMacOS) return; - - _initCurrentAccentColor(); - _initSystemColorObserver(); - } - - /// Disposes this listener. - void dispose() { - _systemColorObserverStreamSubscription?.cancel(); - } - - /// Initializes the current accent color. This method is to be called whenever - /// a change is detected. - Future _initCurrentAccentColor() async { - final hueComponent = await _getHueComponent(); - _currentAccentColor = _resolveAccentColorFromHueComponent(hueComponent); - _accentColorStreamController.add(null); - } - - /// Initializes the current system color observer. This method may only be - /// called once. - void _initSystemColorObserver() { - assert(_systemColorObserverStreamSubscription == null); - - _systemColorObserverStreamSubscription = - AppkitUiElementColors.systemColorObserver.stream.listen((_) { - _initCurrentAccentColor(); - _accentColorStreamController.add(null); - }); - } - - /// Returns the hue component of the currently active accent color on macOS. - Future _getHueComponent() async { - final color = await AppkitUiElementColors.getColorComponents( - uiElementColor: UiElementColor.controlAccentColor, - components: const { - NSColorComponent.hueComponent, - }, - colorSpace: NSColorSpace.genericRGB, - appearance: NSAppearanceName.aqua, - ); - - assert(color.containsKey("hueComponent")); - - return color["hueComponent"]!; - } - - /// Returns the [AccentColor] which corresponds to the provided - /// [hueComponent]. - AccentColor _resolveAccentColorFromHueComponent(double hueComponent) { - if (hueComponentToAccentColor.containsKey(hueComponent)) { - return hueComponentToAccentColor[hueComponent]!; - } - - debugPrint( - 'Warning: Falling back on slow accent color resolution. It’s possible ' - 'that the accent colors have changed in a recent version of macOS, thus ' - 'invalidating macos_ui’s accent colors, which were captured on macOS ' - 'Ventura. If you see this message, please notify a maintainer of the ' - 'macos_ui package.', - ); - - return _slowlyResolveAccentColorFromHueComponent(hueComponent); - } - - /// This is a fallback method in case [_resolveAccentColorFromHueComponent] - /// fails. - AccentColor _slowlyResolveAccentColorFromHueComponent(double hueComponent) { - final entries = hueComponentToAccentColor.entries; - var lowestDistance = double.maxFinite; - var toBeReturnedAccentColor = AccentColor.values.first; - - for (final entry in entries) { - final distance = _distanceBetweenHueComponents(hueComponent, entry.key); - if (distance < lowestDistance) { - lowestDistance = distance; - toBeReturnedAccentColor = entry.value; - } - } - - return toBeReturnedAccentColor; - } - - /// Returns the distance between two hue components. - double _distanceBetweenHueComponents(double component1, double component2) { - final rawDifference = (component1 - component2).abs(); - return sin(rawDifference * pi); - } - - /// A class that listens to accent color changes. - AccentColorListener() { - _init(); - } -} From 7fa0f23692042bcbcefb30c46b8020a18810c02e Mon Sep 17 00:00:00 2001 From: Adrian Samoticha Date: Tue, 15 Aug 2023 19:08:11 +0200 Subject: [PATCH 115/151] move `MacOSBrightnessOverrideHandler` to separate file --- .../macos_brightness_override_handler.dart | 25 +++++++++++++++++++ lib/src/utils/utils.dart | 25 +------------------ 2 files changed, 26 insertions(+), 24 deletions(-) create mode 100644 lib/src/utils/macos_brightness_override_handler.dart diff --git a/lib/src/utils/macos_brightness_override_handler.dart b/lib/src/utils/macos_brightness_override_handler.dart new file mode 100644 index 00000000..3fad912b --- /dev/null +++ b/lib/src/utils/macos_brightness_override_handler.dart @@ -0,0 +1,25 @@ +import 'dart:io'; + +import 'package:flutter/foundation.dart'; +import 'package:macos_ui/macos_ui.dart'; + +/// 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/lib/src/utils/utils.dart b/lib/src/utils/utils.dart index 52930e52..3d63855c 100644 --- a/lib/src/utils/utils.dart +++ b/lib/src/utils/utils.dart @@ -1,11 +1,9 @@ -import 'dart:io'; - -import 'package:flutter/foundation.dart'; import 'package:macos_ui/macos_ui.dart'; import 'package:macos_ui/src/library.dart'; export 'window_main_state_listener.dart'; export 'accent_color_listener.dart'; +export 'macos_brightness_override_handler.dart'; /// Asserts that the given context has a [MacosTheme] ancestor. /// @@ -68,24 +66,3 @@ class Unsupported { 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; - } -} From 3e50ae4034afcb76862b7a0e7728900cdec77b75 Mon Sep 17 00:00:00 2001 From: Adrian Samoticha Date: Tue, 15 Aug 2023 19:54:08 +0200 Subject: [PATCH 116/151] add example to `AccentColorListener` --- lib/src/utils/accent_color_listener.dart | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/lib/src/utils/accent_color_listener.dart b/lib/src/utils/accent_color_listener.dart index 4ba4a491..4dd85a75 100644 --- a/lib/src/utils/accent_color_listener.dart +++ b/lib/src/utils/accent_color_listener.dart @@ -9,6 +9,23 @@ import 'package:macos_ui/src/enums/accent_color.dart'; /// A class that listens to changes to the user’s selected system accent color. /// /// Native macOS applications respond to such changes immediately. +/// +/// Example using [StreamBuilder]: +/// +/// ```dart +/// StreamBuilder( +/// stream: AccentColorListener.instance.onChangedStream, +/// builder: (context, _) { +/// final AccentColor? accentColor = +/// AccentColorListener.instance.currentAccentColor; +/// +/// return SomeWidget( +/// accentColor: accentColor, +/// child: ... +/// ); +/// }, +/// ); +/// ``` class AccentColorListener { /// A shared instance of [AccentColorListener]. static final instance = AccentColorListener(); From 47e69740a9324da56fa8bd3ba5001c5e29091009 Mon Sep 17 00:00:00 2001 From: Adrian Samoticha Date: Tue, 15 Aug 2023 19:56:51 +0200 Subject: [PATCH 117/151] rename `isWindowMain` to `isMainWindow` --- lib/src/buttons/push_button.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/src/buttons/push_button.dart b/lib/src/buttons/push_button.dart index 632ea65c..b94e1822 100644 --- a/lib/src/buttons/push_button.dart +++ b/lib/src/buttons/push_button.dart @@ -262,13 +262,13 @@ class PushButtonState extends State BoxDecoration _getBoxDecoration() { // If the window isn’t currently the main window (that is, it is not in // focus), make the button look as if it was a secondary button. - final isWindowMain = WindowMainStateListener.instance.isMainWindow; + final isMainWindow = WindowMainStateListener.instance.isMainWindow; return _BoxDecorationBuilder.buildBoxDecoration( accentColor: _accentColor, isEnabled: widget.enabled, isDarkModeEnabled: MacosTheme.of(context).brightness.isDark, - isSecondary: !isWindowMain || (widget.secondary ?? false), + isSecondary: !isMainWindow || (widget.secondary ?? false), ); } From 7b9b1b6fd9ebddfe7e55e63d3ec59683045e9022 Mon Sep 17 00:00:00 2001 From: Adrian Samoticha Date: Tue, 15 Aug 2023 19:57:55 +0200 Subject: [PATCH 118/151] add example to `WindowMainStateListener` --- lib/src/utils/window_main_state_listener.dart | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/lib/src/utils/window_main_state_listener.dart b/lib/src/utils/window_main_state_listener.dart index 932dcb3d..43b5eafd 100644 --- a/lib/src/utils/window_main_state_listener.dart +++ b/lib/src/utils/window_main_state_listener.dart @@ -9,6 +9,23 @@ import 'package:macos_ui/macos_ui.dart'; /// A common use-case for responding to such changes would be to mute the colors /// of certain primary UI elements when the window is no longer in focus, which /// is something native macOS applications do out of the box. +/// +/// Example using [StreamBuilder]: +/// +/// ```dart +/// StreamBuilder( +/// stream: WindowMainStateListener.instance.onChangedStream, +/// builder: (context, _) { +/// final bool isMainWindow +/// = WindowMainStateListener.instance.isMainWindow; +/// +/// return SomeWidget( +/// isMainWindow: isMainWindow, +/// child: ... +/// ); +/// }, +/// ); +/// ``` class WindowMainStateListener { /// A shared instance of [WindowMainStateListener]. static final instance = WindowMainStateListener(); From a4b6c45133271512a81f544a5692cb7c1c47c289 Mon Sep 17 00:00:00 2001 From: Adrian Samoticha Date: Sat, 19 Aug 2023 15:28:45 +0200 Subject: [PATCH 119/151] =?UTF-8?q?replace=20`child:=20...`=20with=20`chil?= =?UTF-8?q?d:=20=E2=80=A6`=20to=20avoid=20confusing=20the=20dots=20with=20?= =?UTF-8?q?the=20spread=20operator?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/src/utils/accent_color_listener.dart | 2 +- lib/src/utils/window_main_state_listener.dart | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/src/utils/accent_color_listener.dart b/lib/src/utils/accent_color_listener.dart index 4dd85a75..fe279cd3 100644 --- a/lib/src/utils/accent_color_listener.dart +++ b/lib/src/utils/accent_color_listener.dart @@ -21,7 +21,7 @@ import 'package:macos_ui/src/enums/accent_color.dart'; /// /// return SomeWidget( /// accentColor: accentColor, -/// child: ... +/// child: … /// ); /// }, /// ); diff --git a/lib/src/utils/window_main_state_listener.dart b/lib/src/utils/window_main_state_listener.dart index 43b5eafd..74db47ef 100644 --- a/lib/src/utils/window_main_state_listener.dart +++ b/lib/src/utils/window_main_state_listener.dart @@ -21,7 +21,7 @@ import 'package:macos_ui/macos_ui.dart'; /// /// return SomeWidget( /// isMainWindow: isMainWindow, -/// child: ... +/// child: … /// ); /// }, /// ); From 4bd7d336d85a6cfa41b7ac1b8f3dd4bd4c8a08a8 Mon Sep 17 00:00:00 2001 From: Adrian Samoticha <86920182+Adrian-Samoticha@users.noreply.github.com> Date: Sat, 19 Aug 2023 16:51:50 +0200 Subject: [PATCH 120/151] Update lib/src/utils/accent_color_listener.dart Co-authored-by: Reuben Turner --- lib/src/utils/accent_color_listener.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/utils/accent_color_listener.dart b/lib/src/utils/accent_color_listener.dart index fe279cd3..c1ccab12 100644 --- a/lib/src/utils/accent_color_listener.dart +++ b/lib/src/utils/accent_color_listener.dart @@ -6,7 +6,7 @@ import 'package:appkit_ui_element_colors/appkit_ui_element_colors.dart'; import 'package:flutter/foundation.dart'; import 'package:macos_ui/src/enums/accent_color.dart'; -/// A class that listens to changes to the user’s selected system accent color. +/// A class that listens for changes to the user’s selected system accent color. /// /// Native macOS applications respond to such changes immediately. /// From 21ebe83bf83699af26ca41b12b87f6f9a03012f7 Mon Sep 17 00:00:00 2001 From: Adrian Samoticha <86920182+Adrian-Samoticha@users.noreply.github.com> Date: Sat, 19 Aug 2023 16:57:44 +0200 Subject: [PATCH 121/151] Update lib/src/utils/accent_color_listener.dart Co-authored-by: Reuben Turner --- lib/src/utils/accent_color_listener.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/utils/accent_color_listener.dart b/lib/src/utils/accent_color_listener.dart index c1ccab12..7b80784a 100644 --- a/lib/src/utils/accent_color_listener.dart +++ b/lib/src/utils/accent_color_listener.dart @@ -95,7 +95,7 @@ class AccentColorListener { }); } - /// Returns the hue component of the currently active accent color on macOS. + /// Returns the hue component of the active accent color selection on macOS. Future _getHueComponent() async { final color = await AppkitUiElementColors.getColorComponents( uiElementColor: UiElementColor.controlAccentColor, From 053befb6b527bdcc80fd1d14c3b4236087a1bfa8 Mon Sep 17 00:00:00 2001 From: Adrian Samoticha <86920182+Adrian-Samoticha@users.noreply.github.com> Date: Sat, 19 Aug 2023 16:58:17 +0200 Subject: [PATCH 122/151] Update lib/src/utils/accent_color_listener.dart Co-authored-by: Reuben Turner --- lib/src/utils/accent_color_listener.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/utils/accent_color_listener.dart b/lib/src/utils/accent_color_listener.dart index 7b80784a..08104c68 100644 --- a/lib/src/utils/accent_color_listener.dart +++ b/lib/src/utils/accent_color_listener.dart @@ -44,7 +44,7 @@ class AccentColorListener { 0.0: AccentColor.graphite, }; - /// The currently active accent color. + /// The active accent color selection. AccentColor? _currentAccentColor; /// The currently active accent color. From 9394422547e799b49e4e4f18f12c29bf353e0f80 Mon Sep 17 00:00:00 2001 From: Adrian Samoticha Date: Sat, 19 Aug 2023 17:16:32 +0200 Subject: [PATCH 123/151] rename `onChangedStream` to `onChanged` --- lib/src/buttons/push_button.dart | 4 ++-- lib/src/utils/accent_color_listener.dart | 4 ++-- lib/src/utils/window_main_state_listener.dart | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/src/buttons/push_button.dart b/lib/src/buttons/push_button.dart index b94e1822..df0cf4fe 100644 --- a/lib/src/buttons/push_button.dart +++ b/lib/src/buttons/push_button.dart @@ -341,10 +341,10 @@ class PushButtonState extends State child: ConstrainedBox( constraints: widget.controlSize.constraints, child: StreamBuilder( - stream: AccentColorListener.instance.onChangedStream, + stream: AccentColorListener.instance.onChanged, builder: (context, _) { return StreamBuilder( - stream: WindowMainStateListener.instance.onChangedStream, + stream: WindowMainStateListener.instance.onChanged, builder: (context, _) { final Color backgroundColor = _getBackgroundColor(); diff --git a/lib/src/utils/accent_color_listener.dart b/lib/src/utils/accent_color_listener.dart index 08104c68..1a21c64b 100644 --- a/lib/src/utils/accent_color_listener.dart +++ b/lib/src/utils/accent_color_listener.dart @@ -14,7 +14,7 @@ import 'package:macos_ui/src/enums/accent_color.dart'; /// /// ```dart /// StreamBuilder( -/// stream: AccentColorListener.instance.onChangedStream, +/// stream: AccentColorListener.instance.onChanged, /// builder: (context, _) { /// final AccentColor? accentColor = /// AccentColorListener.instance.currentAccentColor; @@ -56,7 +56,7 @@ class AccentColorListener { /// Streams the user’s system accent color selection. /// /// Emits a new value whenever the system accent color selection changes. - Stream get onChangedStream => _accentColorStreamController.stream; + Stream get onChanged => _accentColorStreamController.stream; /// A stream subscription for the [SystemColorObserver] stream. StreamSubscription? _systemColorObserverStreamSubscription; diff --git a/lib/src/utils/window_main_state_listener.dart b/lib/src/utils/window_main_state_listener.dart index 74db47ef..ece89646 100644 --- a/lib/src/utils/window_main_state_listener.dart +++ b/lib/src/utils/window_main_state_listener.dart @@ -14,7 +14,7 @@ import 'package:macos_ui/macos_ui.dart'; /// /// ```dart /// StreamBuilder( -/// stream: WindowMainStateListener.instance.onChangedStream, +/// stream: WindowMainStateListener.instance.onChanged, /// builder: (context, _) { /// final bool isMainWindow /// = WindowMainStateListener.instance.isMainWindow; @@ -44,7 +44,7 @@ class WindowMainStateListener { /// A stream of the window’s main state. Emits a new value whenever the state /// changes. - Stream get onChangedStream => _windowMainStateStreamController.stream; + Stream get onChanged => _windowMainStateStreamController.stream; /// Initializes the listener. This should only be called once. void _init() { From 76caa07b0c98bec7a796839935ad4380406ae3fb Mon Sep 17 00:00:00 2001 From: Adrian Samoticha Date: Sat, 19 Aug 2023 17:17:43 +0200 Subject: [PATCH 124/151] move `AccentColorListener` constructor to top of class --- lib/src/utils/accent_color_listener.dart | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/src/utils/accent_color_listener.dart b/lib/src/utils/accent_color_listener.dart index 1a21c64b..5965e3ab 100644 --- a/lib/src/utils/accent_color_listener.dart +++ b/lib/src/utils/accent_color_listener.dart @@ -27,6 +27,11 @@ import 'package:macos_ui/src/enums/accent_color.dart'; /// ); /// ``` class AccentColorListener { + /// A class that listens to accent color changes. + AccentColorListener() { + _init(); + } + /// A shared instance of [AccentColorListener]. static final instance = AccentColorListener(); @@ -152,9 +157,4 @@ class AccentColorListener { final rawDifference = (component1 - component2).abs(); return sin(rawDifference * pi); } - - /// A class that listens to accent color changes. - AccentColorListener() { - _init(); - } } From e8190d6c3505c4513c68f4a5dd5777960675d2bb Mon Sep 17 00:00:00 2001 From: Adrian Samoticha Date: Sat, 19 Aug 2023 17:18:16 +0200 Subject: [PATCH 125/151] move `WindowMainStateListener` to top of class --- lib/src/utils/window_main_state_listener.dart | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/src/utils/window_main_state_listener.dart b/lib/src/utils/window_main_state_listener.dart index ece89646..c926f2b3 100644 --- a/lib/src/utils/window_main_state_listener.dart +++ b/lib/src/utils/window_main_state_listener.dart @@ -27,6 +27,12 @@ import 'package:macos_ui/macos_ui.dart'; /// ); /// ``` class WindowMainStateListener { + /// A class that listens for changes to the application’s window being the + /// main window, and notifies listeners. + WindowMainStateListener() { + _init(); + } + /// A shared instance of [WindowMainStateListener]. static final instance = WindowMainStateListener(); @@ -80,12 +86,6 @@ class WindowMainStateListener { void dispose() { handle?.removeFromHandler(); } - - /// A class that listens for changes to the application’s window being the - /// main window, and notifies listeners. - WindowMainStateListener() { - _init(); - } } /// The [NSWindowDelegate] used by [WindowMainStateListener]. From 673c7209bd71b9ac035f5b2f24d0c483451e86a8 Mon Sep 17 00:00:00 2001 From: Adrian Samoticha Date: Sat, 19 Aug 2023 17:19:36 +0200 Subject: [PATCH 126/151] =?UTF-8?q?add=20=E2=80=9CUpdated=E2=80=9D=20headi?= =?UTF-8?q?ng=20to=20changelog=20entry=20for=20version=202.0.1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 95ac5179..f27d69cf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,5 @@ ## [2.0.1] +### 🔄 Updated 🔄 * `PushButton` has received a facelift. It now mimics the look and feel of native macOS buttons more closely. * **Note:** As a result, its `pressedOpacity` property and the `PushButtonTheme` class have been deprecated. From e78cd55eeed26a2073dbb1da70eb047cff60ddf8 Mon Sep 17 00:00:00 2001 From: Adrian Samoticha Date: Sat, 19 Aug 2023 17:29:28 +0200 Subject: [PATCH 127/151] migrate to `ExcludeSemantics` in popup button (`ignoringSemantics` is deprecated) --- example/pubspec.lock | 38 +++++++++++++++---------------- lib/src/buttons/popup_button.dart | 11 +++++---- pubspec.lock | 38 +++++++++++++++++++------------ 3 files changed, 49 insertions(+), 38 deletions(-) diff --git a/example/pubspec.lock b/example/pubspec.lock index 86bd412e..f61168d0 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -45,10 +45,10 @@ packages: dependency: transitive description: name: collection - sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c" + sha256: f092b211a4319e98e5ff58223576de6c2803db36221657b46c82574721240687 url: "https://pub.dev" source: hosted - version: "1.17.1" + version: "1.17.2" crypto: dependency: transitive description: @@ -152,14 +152,6 @@ packages: url: "https://pub.dev" source: hosted version: "4.0.2" - js: - dependency: transitive - description: - name: js - sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 - url: "https://pub.dev" - source: hosted - version: "0.6.7" lints: dependency: transitive description: @@ -187,18 +179,18 @@ packages: dependency: transitive description: name: matcher - sha256: "6501fbd55da300384b768785b83e5ce66991266cec21af89ab9ae7f5ce1c4cbb" + sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e" url: "https://pub.dev" source: hosted - version: "0.12.15" + version: "0.12.16" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724 + sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41" url: "https://pub.dev" source: hosted - version: "0.2.0" + version: "0.5.0" meta: dependency: transitive description: @@ -312,10 +304,10 @@ packages: dependency: transitive description: name: source_span - sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250 + sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" url: "https://pub.dev" source: hosted - version: "1.9.1" + version: "1.10.0" stack_trace: dependency: transitive description: @@ -352,10 +344,10 @@ packages: dependency: transitive description: name: test_api - sha256: eb6ac1540b26de412b3403a163d919ba86f6a973fe6cc50ae3541b80092fdcfb + sha256: "75760ffd7786fffdfb9597c35c5b27eaeec82be8edfb6d71d32651128ed7aab8" url: "https://pub.dev" source: hosted - version: "0.5.1" + version: "0.6.0" typed_data: dependency: transitive description: @@ -436,6 +428,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.4" + web: + dependency: transitive + description: + name: web + sha256: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10 + url: "https://pub.dev" + source: hosted + version: "0.1.4-beta" win32: dependency: transitive description: @@ -453,5 +453,5 @@ packages: source: hosted version: "1.0.0" sdks: - dart: ">=3.0.0 <4.0.0" + dart: ">=3.1.0-185.0.dev <4.0.0" flutter: ">=3.10.0" diff --git a/lib/src/buttons/popup_button.dart b/lib/src/buttons/popup_button.dart index 8158e63c..37c6559b 100644 --- a/lib/src/buttons/popup_button.dart +++ b/lib/src/buttons/popup_button.dart @@ -1194,10 +1194,13 @@ class _MacosPopupButtonState extends State> } hintIndex = items.length; - items.add(IgnorePointer( - ignoringSemantics: false, - child: displayedHint, - )); + items.add( + ExcludeSemantics( + child: IgnorePointer( + child: displayedHint, + ), + ), + ); } // If value is null (then _selectedIndex is null) then we diff --git a/pubspec.lock b/pubspec.lock index 6aa59489..62a3f534 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -69,10 +69,10 @@ packages: dependency: transitive description: name: collection - sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c" + sha256: f092b211a4319e98e5ff58223576de6c2803db36221657b46c82574721240687 url: "https://pub.dev" source: hosted - version: "1.17.1" + version: "1.17.2" convert: dependency: transitive description: @@ -223,18 +223,18 @@ packages: dependency: transitive description: name: matcher - sha256: "6501fbd55da300384b768785b83e5ce66991266cec21af89ab9ae7f5ce1c4cbb" + sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e" url: "https://pub.dev" source: hosted - version: "0.12.15" + version: "0.12.16" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724 + sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41" url: "https://pub.dev" source: hosted - version: "0.2.0" + version: "0.5.0" meta: dependency: transitive description: @@ -364,10 +364,10 @@ packages: dependency: transitive description: name: source_span - sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250 + sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" url: "https://pub.dev" source: hosted - version: "1.9.1" + version: "1.10.0" stack_trace: dependency: transitive description: @@ -404,26 +404,26 @@ packages: dependency: transitive description: name: test - sha256: "3dac9aecf2c3991d09b9cdde4f98ded7b30804a88a0d7e4e7e1678e78d6b97f4" + sha256: "13b41f318e2a5751c3169137103b60c584297353d4b1761b66029bae6411fe46" url: "https://pub.dev" source: hosted - version: "1.24.1" + version: "1.24.3" test_api: dependency: transitive description: name: test_api - sha256: eb6ac1540b26de412b3403a163d919ba86f6a973fe6cc50ae3541b80092fdcfb + sha256: "75760ffd7786fffdfb9597c35c5b27eaeec82be8edfb6d71d32651128ed7aab8" url: "https://pub.dev" source: hosted - version: "0.5.1" + version: "0.6.0" test_core: dependency: transitive description: name: test_core - sha256: "5138dbffb77b2289ecb12b81c11ba46036590b72a64a7a90d6ffb880f1a29e93" + sha256: "99806e9e6d95c7b059b7a0fc08f07fc53fabe54a829497f0d9676299f1e8637e" url: "https://pub.dev" source: hosted - version: "0.5.1" + version: "0.5.3" typed_data: dependency: transitive description: @@ -456,6 +456,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.0" + web: + dependency: transitive + description: + name: web + sha256: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10 + url: "https://pub.dev" + source: hosted + version: "0.1.4-beta" web_socket_channel: dependency: transitive description: @@ -481,5 +489,5 @@ packages: source: hosted version: "3.1.2" sdks: - dart: ">=3.0.0 <4.0.0" + dart: ">=3.1.0-185.0.dev <4.0.0" flutter: ">=3.10.0" From 9f006f2c2e06af348c8d8308fa6531858db50b49 Mon Sep 17 00:00:00 2001 From: Adrian Samoticha <86920182+Adrian-Samoticha@users.noreply.github.com> Date: Sat, 19 Aug 2023 19:55:15 +0200 Subject: [PATCH 128/151] Resolve #445 (#471) --- CHANGELOG.md | 5 + .../Flutter/GeneratedPluginRegistrant.swift | 2 + example/macos/Podfile.lock | 8 +- example/pubspec.lock | 68 ++- lib/src/buttons/popup_button.dart | 11 +- lib/src/buttons/push_button.dart | 515 ++++++++++++++---- lib/src/enums/accent_color.dart | 27 + lib/src/library.dart | 2 +- lib/src/theme/macos_theme.dart | 7 +- lib/src/theme/push_button_theme.dart | 41 +- lib/src/utils/accent_color_listener.dart | 160 ++++++ .../macos_brightness_override_handler.dart | 25 + lib/src/{ => utils}/utils.dart | 28 +- lib/src/utils/window_main_state_listener.dart | 109 ++++ pubspec.lock | 74 ++- pubspec.yaml | 6 +- test/buttons/push_button_test.dart | 1 - test/theme/push_button_theme_test.dart | 97 ---- 18 files changed, 919 insertions(+), 267 deletions(-) create mode 100644 lib/src/enums/accent_color.dart create mode 100644 lib/src/utils/accent_color_listener.dart create mode 100644 lib/src/utils/macos_brightness_override_handler.dart rename lib/src/{ => utils}/utils.dart (67%) create mode 100644 lib/src/utils/window_main_state_listener.dart delete mode 100644 test/theme/push_button_theme_test.dart diff --git a/CHANGELOG.md b/CHANGELOG.md index 0c59bae9..f27d69cf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +## [2.0.1] +### 🔄 Updated 🔄 +* `PushButton` has received a facelift. It now mimics the look and feel of native macOS buttons more closely. + * **Note:** As a result, its `pressedOpacity` property and the `PushButtonTheme` class have been deprecated. + ## [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: diff --git a/example/macos/Flutter/GeneratedPluginRegistrant.swift b/example/macos/Flutter/GeneratedPluginRegistrant.swift index 0179c12c..1e64f18d 100644 --- a/example/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/example/macos/Flutter/GeneratedPluginRegistrant.swift @@ -5,12 +5,14 @@ import FlutterMacOS import Foundation +import appkit_ui_element_colors import macos_ui import macos_window_utils import path_provider_foundation import url_launcher_macos func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + AppkitUiElementColorsPlugin.register(with: registry.registrar(forPlugin: "AppkitUiElementColorsPlugin")) MacOSUiPlugin.register(with: registry.registrar(forPlugin: "MacOSUiPlugin")) MacOSWindowUtilsPlugin.register(with: registry.registrar(forPlugin: "MacOSWindowUtilsPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) diff --git a/example/macos/Podfile.lock b/example/macos/Podfile.lock index 2fa81174..6aa1d86e 100644 --- a/example/macos/Podfile.lock +++ b/example/macos/Podfile.lock @@ -1,4 +1,6 @@ PODS: + - appkit_ui_element_colors (1.0.0): + - FlutterMacOS - FlutterMacOS (1.0.0) - macos_ui (0.1.0): - FlutterMacOS @@ -11,6 +13,7 @@ PODS: - FlutterMacOS DEPENDENCIES: + - appkit_ui_element_colors (from `Flutter/ephemeral/.symlinks/plugins/appkit_ui_element_colors/macos`) - 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`) @@ -18,6 +21,8 @@ DEPENDENCIES: - url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`) EXTERNAL SOURCES: + appkit_ui_element_colors: + :path: Flutter/ephemeral/.symlinks/plugins/appkit_ui_element_colors/macos FlutterMacOS: :path: Flutter/ephemeral macos_ui: @@ -30,6 +35,7 @@ EXTERNAL SOURCES: :path: Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos SPEC CHECKSUMS: + appkit_ui_element_colors: 39bb2d80be3f19b152ccf4c70d5bbe6cba43d74a FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24 macos_ui: 6229a8922cd97bafb7d9636c8eb8dfb0744183ca macos_window_utils: 933f91f64805e2eb91a5bd057cf97cd097276663 @@ -38,4 +44,4 @@ SPEC CHECKSUMS: PODFILE CHECKSUM: ff0a9a3ce75ee73f200ca7e2f47745698c917ef9 -COCOAPODS: 1.11.3 +COCOAPODS: 1.12.1 diff --git a/example/pubspec.lock b/example/pubspec.lock index dc586a2a..f61168d0 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -1,6 +1,14 @@ # Generated by pub # See https://dart.dev/tools/pub/glossary#lockfile packages: + appkit_ui_element_colors: + dependency: transitive + description: + name: appkit_ui_element_colors + sha256: c3e50f900aae314d339de489535736238627071457c4a4a2dbbb1545b4f04f22 + url: "https://pub.dev" + source: hosted + version: "1.0.0" async: dependency: transitive description: @@ -37,10 +45,10 @@ packages: dependency: transitive description: name: collection - sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c" + sha256: f092b211a4319e98e5ff58223576de6c2803db36221657b46c82574721240687 url: "https://pub.dev" source: hosted - version: "1.17.1" + version: "1.17.2" crypto: dependency: transitive description: @@ -57,6 +65,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.5" + equatable: + dependency: transitive + description: + name: equatable + sha256: c2b87cb7756efdf69892005af546c56c0b5037f54d2a88269b4f347a505e3ca2 + url: "https://pub.dev" + source: hosted + version: "2.0.5" fake_async: dependency: transitive description: @@ -112,6 +128,14 @@ packages: url: "https://pub.dev" source: hosted version: "5.1.0" + gradient_borders: + dependency: transitive + description: + name: gradient_borders + sha256: "69eeaff519d145a4c6c213ada1abae386bcc8981a4970d923e478ce7ba19e309" + url: "https://pub.dev" + source: hosted + version: "1.0.0" http: dependency: transitive description: @@ -128,14 +152,6 @@ packages: url: "https://pub.dev" source: hosted version: "4.0.2" - js: - dependency: transitive - description: - name: js - sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 - url: "https://pub.dev" - source: hosted - version: "0.6.7" lints: dependency: transitive description: @@ -150,31 +166,31 @@ packages: path: ".." relative: true source: path - version: "2.0.0" + version: "2.0.1" macos_window_utils: dependency: transitive description: name: macos_window_utils - sha256: b78a210aa70ca7ccad6e7b7b810fb4689c507f4a46e299214900b2a1eb70ea23 + sha256: "43a90473f8786f00f07203e6819dab67e032f8896dafa4a6f85fbc71fba32c0b" url: "https://pub.dev" source: hosted - version: "1.1.3" + version: "1.2.0" matcher: dependency: transitive description: name: matcher - sha256: "6501fbd55da300384b768785b83e5ce66991266cec21af89ab9ae7f5ce1c4cbb" + sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e" url: "https://pub.dev" source: hosted - version: "0.12.15" + version: "0.12.16" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724 + sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41" url: "https://pub.dev" source: hosted - version: "0.2.0" + version: "0.5.0" meta: dependency: transitive description: @@ -288,10 +304,10 @@ packages: dependency: transitive description: name: source_span - sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250 + sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" url: "https://pub.dev" source: hosted - version: "1.9.1" + version: "1.10.0" stack_trace: dependency: transitive description: @@ -328,10 +344,10 @@ packages: dependency: transitive description: name: test_api - sha256: eb6ac1540b26de412b3403a163d919ba86f6a973fe6cc50ae3541b80092fdcfb + sha256: "75760ffd7786fffdfb9597c35c5b27eaeec82be8edfb6d71d32651128ed7aab8" url: "https://pub.dev" source: hosted - version: "0.5.1" + version: "0.6.0" typed_data: dependency: transitive description: @@ -412,6 +428,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.4" + web: + dependency: transitive + description: + name: web + sha256: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10 + url: "https://pub.dev" + source: hosted + version: "0.1.4-beta" win32: dependency: transitive description: @@ -429,5 +453,5 @@ packages: source: hosted version: "1.0.0" sdks: - dart: ">=3.0.0 <4.0.0" + dart: ">=3.1.0-185.0.dev <4.0.0" flutter: ">=3.10.0" diff --git a/lib/src/buttons/popup_button.dart b/lib/src/buttons/popup_button.dart index 8158e63c..37c6559b 100644 --- a/lib/src/buttons/popup_button.dart +++ b/lib/src/buttons/popup_button.dart @@ -1194,10 +1194,13 @@ class _MacosPopupButtonState extends State> } hintIndex = items.length; - items.add(IgnorePointer( - ignoringSemantics: false, - child: displayedHint, - )); + items.add( + ExcludeSemantics( + child: IgnorePointer( + child: displayedHint, + ), + ), + ); } // If value is null (then _selectedIndex is null) then we diff --git a/lib/src/buttons/push_button.dart b/lib/src/buttons/push_button.dart index 19180016..df0cf4fe 100644 --- a/lib/src/buttons/push_button.dart +++ b/lib/src/buttons/push_button.dart @@ -2,7 +2,9 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/rendering.dart'; +import 'package:gradient_borders/gradient_borders.dart'; import 'package:macos_ui/macos_ui.dart'; +import 'package:macos_ui/src/enums/accent_color.dart'; import 'package:macos_ui/src/library.dart'; const _kMiniButtonSize = Size(26.0, 11.0); @@ -169,6 +171,9 @@ class PushButton extends StatefulWidget { /// /// This defaults to 0.4. If null, opacity will not change on pressed if using /// your own custom effects is desired. + @Deprecated("'PushButton' animations now match their native macOS’ " + "counterparts. Therefore, its opacity no longer changes when it is " + "pressed.") final double? pressedOpacity; /// The radius of the button's corners when it has a background color. @@ -195,8 +200,7 @@ 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 overridden if the [color] attribute is non-null. + /// 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 @@ -209,7 +213,6 @@ class PushButton extends StatefulWidget { properties.add(EnumProperty('controlSize', controlSize)); properties.add(ColorProperty('color', color)); properties.add(ColorProperty('disabledColor', disabledColor)); - properties.add(DoubleProperty('pressedOpacity', pressedOpacity)); properties.add(DiagnosticsProperty('alignment', alignment)); properties.add(StringProperty('semanticLabel', semanticLabel)); properties.add(DiagnosticsProperty('borderRadius', borderRadius)); @@ -227,104 +230,102 @@ class PushButton extends StatefulWidget { class PushButtonState extends State with SingleTickerProviderStateMixin { - // Eyeballed values. Feel free to tweak. - static const Duration kFadeOutDuration = Duration(milliseconds: 10); - static const Duration kFadeInDuration = Duration(milliseconds: 100); - final Tween _opacityTween = Tween(begin: 1.0); - - late AnimationController _animationController; - late Animation _opacityAnimation; - - @override - void initState() { - super.initState(); - _animationController = AnimationController( - duration: const Duration(milliseconds: 200), - value: 0.0, - vsync: this, - ); - _opacityAnimation = _animationController - .drive(CurveTween(curve: Curves.decelerate)) - .drive(_opacityTween); - _setTween(); - } - @override void didUpdateWidget(PushButton oldWidget) { super.didUpdateWidget(oldWidget); - _setTween(); - } - - void _setTween() { - _opacityTween.end = widget.pressedOpacity ?? 1.0; } void _handleTapDown(TapDownDetails event) { if (!buttonHeldDown) { - buttonHeldDown = true; - _animate(); + setState(() => buttonHeldDown = true); } } void _handleTapUp(TapUpDetails event) { if (buttonHeldDown) { - buttonHeldDown = false; - _animate(); + setState(() => buttonHeldDown = false); } } void _handleTapCancel() { if (buttonHeldDown) { - buttonHeldDown = false; - _animate(); + setState(() => buttonHeldDown = false); } } - void _animate() { - if (_animationController.isAnimating) return; - final bool wasHeldDown = buttonHeldDown; - final TickerFuture ticker = buttonHeldDown - ? _animationController.animateTo(1.0, duration: kFadeOutDuration) - : _animationController.animateTo(0.0, duration: kFadeInDuration); - ticker.then((void value) { - if (mounted && wasHeldDown != buttonHeldDown) _animate(); - }); - } - - @override - void dispose() { - _animationController.dispose(); - super.dispose(); - } - @visibleForTesting bool buttonHeldDown = false; - @override - Widget build(BuildContext context) { - assert(debugCheckHasMacosTheme(context)); + AccentColor get _accentColor => + AccentColorListener.instance.currentAccentColor ?? AccentColor.blue; + + BoxDecoration _getBoxDecoration() { + // If the window isn’t currently the main window (that is, it is not in + // focus), make the button look as if it was a secondary button. + final isMainWindow = WindowMainStateListener.instance.isMainWindow; + + return _BoxDecorationBuilder.buildBoxDecoration( + accentColor: _accentColor, + isEnabled: widget.enabled, + isDarkModeEnabled: MacosTheme.of(context).brightness.isDark, + isSecondary: !isMainWindow || (widget.secondary ?? false), + ); + } + + Color _getBackgroundColor() { final bool enabled = widget.enabled; final bool isSecondary = widget.secondary != null && widget.secondary!; final MacosThemeData theme = MacosTheme.of(context); - final Color backgroundColor = MacosDynamicColor.resolve( + + // If the window isn’t currently the main window (that is, it is not in + // focus), make the button look as if it was a secondary button. + final isWindowMain = WindowMainStateListener.instance.isMainWindow; + + return MacosDynamicColor.resolve( widget.color ?? - (isSecondary - ? theme.pushButtonTheme.secondaryColor! - : theme.pushButtonTheme.color!), + _BoxDecorationBuilder.getGradientColors( + accentColor: _accentColor, + isEnabled: enabled, + isDarkModeEnabled: theme.brightness.isDark, + isSecondary: isSecondary || !isWindowMain, + ).first, context, ); + } - final disabledColor = !isSecondary - ? backgroundColor.withOpacity(0.5) - : backgroundColor.withOpacity(0.25); + Color _getForegroundColor(Color backgroundColor) { + final MacosThemeData theme = MacosTheme.of(context); - final Color foregroundColor = widget.enabled - ? textLuminance(backgroundColor) - : theme.brightness.isDark - ? const Color.fromRGBO(255, 255, 255, 0.25) - : const Color.fromRGBO(0, 0, 0, 0.25); + final blendedBackgroundColor = Color.lerp( + theme.canvasColor, + backgroundColor, + backgroundColor.opacity, + )!; - final baseStyle = theme.typography.body.copyWith(color: foregroundColor); + return widget.enabled + ? textLuminance(blendedBackgroundColor) + : textLuminance(blendedBackgroundColor).withOpacity(0.25); + } + + BoxDecoration _getClickEffectBoxDecoration() { + final MacosThemeData theme = MacosTheme.of(context); + final isDark = theme.brightness.isDark; + + final color = isDark + ? const MacosColor.fromRGBO(255, 255, 255, 0.15) + : const MacosColor.fromRGBO(0, 0, 0, 0.06); + + return BoxDecoration( + color: color, + borderRadius: widget.controlSize.borderRadius, + ); + } + + @override + Widget build(BuildContext context) { + assert(debugCheckHasMacosTheme(context)); + final bool enabled = widget.enabled; + final MacosThemeData theme = MacosTheme.of(context); return MouseRegion( cursor: widget.mouseCursor!, @@ -339,29 +340,45 @@ class PushButtonState extends State label: widget.semanticLabel, child: ConstrainedBox( constraints: widget.controlSize.constraints, - child: FadeTransition( - opacity: _opacityAnimation, - child: DecoratedBox( - decoration: ShapeDecoration( - shape: RoundedRectangleBorder( - borderRadius: widget.controlSize.borderRadius, - ), - // color: !enabled ? disabledColor : backgroundColor, - color: enabled ? backgroundColor : disabledColor, - ), - child: Padding( - padding: widget.controlSize.padding, - child: Align( - alignment: widget.alignment, - widthFactor: 1.0, - heightFactor: 1.0, - child: DefaultTextStyle( - style: widget.controlSize.textStyle(baseStyle), - child: widget.child, - ), - ), - ), - ), + child: StreamBuilder( + stream: AccentColorListener.instance.onChanged, + builder: (context, _) { + return StreamBuilder( + stream: WindowMainStateListener.instance.onChanged, + builder: (context, _) { + final Color backgroundColor = _getBackgroundColor(); + + final Color foregroundColor = + _getForegroundColor(backgroundColor); + + final baseStyle = + theme.typography.body.copyWith(color: foregroundColor); + + return DecoratedBox( + decoration: _getBoxDecoration().copyWith( + borderRadius: widget.controlSize.borderRadius, + ), + child: Container( + foregroundDecoration: buttonHeldDown + ? _getClickEffectBoxDecoration() + : const BoxDecoration(), + child: Padding( + padding: widget.controlSize.padding, + child: Align( + alignment: widget.alignment, + widthFactor: 1.0, + heightFactor: 1.0, + child: DefaultTextStyle( + style: widget.controlSize.textStyle(baseStyle), + child: widget.child, + ), + ), + ), + ), + ); + }, + ); + }, ), ), ), @@ -369,3 +386,317 @@ class PushButtonState extends State ); } } + +class _BoxDecorationBuilder { + /// Gets the colors to use for the [BoxDecoration]’s gradient based on the + /// provided [accentColor], [isEnabled], and [isDarkModeEnabled] properties. + static List getGradientColors({ + required AccentColor accentColor, + required bool isEnabled, + required bool isDarkModeEnabled, + required bool isSecondary, + }) { + final isEnabledFactor = isEnabled ? 1.0 : 0.5; + + if (isSecondary) { + return isDarkModeEnabled + ? [ + MacosColor.fromRGBO(255, 255, 255, 0.251 * isEnabledFactor), + MacosColor.fromRGBO(255, 255, 255, 0.251 * isEnabledFactor), + ] + : [ + MacosColor.fromRGBO(255, 255, 255, 1.0 * isEnabledFactor), + MacosColor.fromRGBO(255, 255, 255, 1.0 * isEnabledFactor), + ]; + } + + if (isDarkModeEnabled) { + switch (accentColor) { + case AccentColor.blue: + return [ + MacosColor.fromRGBO(0, 114, 238, 1.0 * isEnabledFactor), + MacosColor.fromRGBO(0, 94, 211, 1.0 * isEnabledFactor), + ]; + + case AccentColor.purple: + return [ + MacosColor.fromRGBO(135, 65, 131, 1.0 * isEnabledFactor), + MacosColor.fromRGBO(120, 57, 116, 1.0 * isEnabledFactor), + ]; + + case AccentColor.pink: + return [ + MacosColor.fromRGBO(188, 52, 105, 1.0 * isEnabledFactor), + MacosColor.fromRGBO(168, 46, 93, 1.0 * isEnabledFactor), + ]; + + case AccentColor.red: + return [ + MacosColor.fromRGBO(186, 53, 46, 1.0 * isEnabledFactor), + MacosColor.fromRGBO(166, 48, 41, 1.0 * isEnabledFactor), + ]; + + case AccentColor.orange: + return [ + MacosColor.fromRGBO(212, 133, 33, 1.0 * isEnabledFactor), + MacosColor.fromRGBO(189, 118, 30, 1.0 * isEnabledFactor), + ]; + + case AccentColor.yellow: + return [ + MacosColor.fromRGBO(229, 203, 35, 1.0 * isEnabledFactor), + MacosColor.fromRGBO(204, 179, 21, 1.0 * isEnabledFactor), + ]; + + case AccentColor.green: + return [ + MacosColor.fromRGBO(58, 138, 46, 1.0 * isEnabledFactor), + MacosColor.fromRGBO(52, 123, 39, 1.0 * isEnabledFactor), + ]; + + case AccentColor.graphite: + return [ + MacosColor.fromRGBO(64, 64, 64, 1.0 * isEnabledFactor), + MacosColor.fromRGBO(57, 57, 57, 1.0 * isEnabledFactor), + ]; + + default: + throw UnimplementedError(); + } + } else { + switch (accentColor) { + case AccentColor.blue: + return [ + MacosColor.fromRGBO(39, 125, 255, 1.0 * isEnabledFactor), + MacosColor.fromRGBO(1, 101, 255, 1.0 * isEnabledFactor), + ]; + + case AccentColor.purple: + return [ + MacosColor.fromRGBO(148, 73, 143, 1.0 * isEnabledFactor), + MacosColor.fromRGBO(128, 39, 121, 1.0 * isEnabledFactor), + ]; + + case AccentColor.pink: + return [ + MacosColor.fromRGBO(212, 71, 125, 1.0 * isEnabledFactor), + MacosColor.fromRGBO(203, 36, 101, 1.0 * isEnabledFactor), + ]; + + case AccentColor.red: + return [ + MacosColor.fromRGBO(198, 64, 57, 1.0 * isEnabledFactor), + MacosColor.fromRGBO(188, 29, 21, 1.0 * isEnabledFactor), + ]; + + case AccentColor.orange: + return [ + MacosColor.fromRGBO(237, 154, 51, 1.0 * isEnabledFactor), + MacosColor.fromRGBO(234, 136, 13, 1.0 * isEnabledFactor), + ]; + + case AccentColor.yellow: + return [ + MacosColor.fromRGBO(242, 211, 61, 1.0 * isEnabledFactor), + MacosColor.fromRGBO(240, 203, 25, 1.0 * isEnabledFactor), + ]; + + case AccentColor.green: + return [ + MacosColor.fromRGBO(77, 161, 63, 1.0 * isEnabledFactor), + MacosColor.fromRGBO(45, 143, 28, 1.0 * isEnabledFactor), + ]; + + case AccentColor.graphite: + return [ + MacosColor.fromRGBO(86, 86, 86, 1.0 * isEnabledFactor), + MacosColor.fromRGBO(55, 55, 55, 1.0 * isEnabledFactor), + ]; + + default: + throw UnimplementedError(); + } + } + } + + /// Gets the shadow to use for the [BoxDecoration] based on the provided + /// [accentColor], [isEnabled], and [isDarkModeEnabled] properties. + static List _getShadow({ + required AccentColor accentColor, + required bool isEnabled, + required bool isDarkModeEnabled, + required bool isSecondary, + }) { + final isEnabledFactor = isEnabled ? 1.0 : 0.5; + + if (isSecondary) { + return isDarkModeEnabled + ? [ + BoxShadow( + color: MacosColor.fromRGBO(0, 0, 0, 0.4 * isEnabledFactor), + blurRadius: 0.5, + offset: Offset.zero, + spreadRadius: 0.0, + blurStyle: BlurStyle.outer, + ), + ] + : [ + BoxShadow( + color: MacosColor.fromRGBO(0, 0, 0, 0.4 * isEnabledFactor), + blurRadius: 0.5, + offset: isEnabled ? const Offset(0.0, 0.3) : Offset.zero, + spreadRadius: 0.0, + blurStyle: isEnabled ? BlurStyle.normal : BlurStyle.outer, + ), + ]; + } + + if (isDarkModeEnabled) { + return [ + BoxShadow( + color: MacosColor.fromRGBO(0, 0, 0, 0.4 * isEnabledFactor), + blurRadius: 0.5, + offset: isEnabled ? const Offset(0.0, 0.3) : Offset.zero, + spreadRadius: 0.0, + blurStyle: isEnabled ? BlurStyle.normal : BlurStyle.outer, + ), + ]; + } else { + switch (accentColor) { + case AccentColor.blue: + return [ + BoxShadow( + color: MacosColor.fromRGBO(0, 103, 255, 0.21 * isEnabledFactor), + blurRadius: 0.5, + offset: isEnabled ? const Offset(0.0, 0.3) : Offset.zero, + spreadRadius: 0.0, + blurStyle: isEnabled ? BlurStyle.normal : BlurStyle.outer, + ), + ]; + + case AccentColor.purple: + return [ + BoxShadow( + color: MacosColor.fromRGBO(139, 29, 125, 0.21 * isEnabledFactor), + blurRadius: 0.5, + offset: isEnabled ? const Offset(0.0, 0.3) : Offset.zero, + spreadRadius: 0.0, + blurStyle: isEnabled ? BlurStyle.normal : BlurStyle.outer, + ), + ]; + + case AccentColor.pink: + return [ + BoxShadow( + color: MacosColor.fromRGBO(222, 0, 101, 0.21 * isEnabledFactor), + blurRadius: 0.5, + offset: isEnabled ? const Offset(0.0, 0.3) : Offset.zero, + spreadRadius: 0.0, + blurStyle: isEnabled ? BlurStyle.normal : BlurStyle.outer, + ), + ]; + + case AccentColor.red: + return [ + BoxShadow( + color: MacosColor.fromRGBO(188, 29, 21, 0.35 * isEnabledFactor), + blurRadius: 0.5, + offset: isEnabled ? const Offset(0.0, 0.3) : Offset.zero, + spreadRadius: 0.0, + blurStyle: isEnabled ? BlurStyle.normal : BlurStyle.outer, + ), + ]; + + case AccentColor.orange: + return [ + BoxShadow( + color: MacosColor.fromRGBO(234, 136, 13, 0.35 * isEnabledFactor), + blurRadius: 0.5, + offset: isEnabled ? const Offset(0.0, 0.3) : Offset.zero, + spreadRadius: 0.0, + blurStyle: isEnabled ? BlurStyle.normal : BlurStyle.outer, + ), + ]; + + case AccentColor.yellow: + return [ + BoxShadow( + color: MacosColor.fromRGBO(240, 203, 25, 0.35 * isEnabledFactor), + blurRadius: 0.5, + offset: isEnabled ? const Offset(0.0, 0.3) : Offset.zero, + spreadRadius: 0.0, + blurStyle: isEnabled ? BlurStyle.normal : BlurStyle.outer, + ), + ]; + + case AccentColor.green: + return [ + BoxShadow( + color: MacosColor.fromRGBO(45, 143, 28, 0.35 * isEnabledFactor), + blurRadius: 0.5, + offset: isEnabled ? const Offset(0.0, 0.3) : Offset.zero, + spreadRadius: 0.0, + blurStyle: isEnabled ? BlurStyle.normal : BlurStyle.outer, + ), + ]; + + case AccentColor.graphite: + return [ + BoxShadow( + color: MacosColor.fromRGBO(55, 55, 55, 0.35 * isEnabledFactor), + blurRadius: 0.5, + offset: isEnabled ? const Offset(0.0, 0.3) : Offset.zero, + spreadRadius: 0.0, + blurStyle: isEnabled ? BlurStyle.normal : BlurStyle.outer, + ), + ]; + + default: + throw UnimplementedError(); + } + } + } + + /// Builds a [BoxDecoration] for a [MacosPushButton]. + static BoxDecoration buildBoxDecoration({ + required AccentColor accentColor, + required bool isEnabled, + required bool isDarkModeEnabled, + required bool isSecondary, + }) { + final isEnabledFactor = isEnabled ? 1.0 : 0.5; + + return BoxDecoration( + border: isDarkModeEnabled + ? GradientBoxBorder( + gradient: LinearGradient( + colors: [ + MacosColor.fromRGBO(255, 255, 255, 0.43 * isEnabledFactor), + const MacosColor.fromRGBO(255, 255, 255, 0.0), + ], + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + stops: const [0.0, 0.2], + ), + width: 0.7, + ) + : null, + gradient: LinearGradient( + colors: getGradientColors( + accentColor: accentColor, + isEnabled: isEnabled, + isDarkModeEnabled: isDarkModeEnabled, + isSecondary: isSecondary, + ), + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + ), + boxShadow: _getShadow( + accentColor: accentColor, + isEnabled: isEnabled, + isDarkModeEnabled: isDarkModeEnabled, + isSecondary: isSecondary, + ), + ); + } +} diff --git a/lib/src/enums/accent_color.dart b/lib/src/enums/accent_color.dart new file mode 100644 index 00000000..95ab8ea2 --- /dev/null +++ b/lib/src/enums/accent_color.dart @@ -0,0 +1,27 @@ +/// The macOS accent color which can be changed by the user in *System Settings* +/// → *Appearance* → *Accent color*. +enum AccentColor { + /// The blue accent color. + blue, + + /// The purple accent color. + purple, + + /// The pink accent color. + pink, + + /// The red accent color. + red, + + /// The orange accent color. + orange, + + /// The yellow accent color. + yellow, + + /// The green accent color. + green, + + /// The graphite accent color. + graphite, +} diff --git a/lib/src/library.dart b/lib/src/library.dart index 37fb1228..2273d447 100644 --- a/lib/src/library.dart +++ b/lib/src/library.dart @@ -32,4 +32,4 @@ export 'package:flutter/material.dart' MaterialState; export 'package:flutter/widgets.dart'; -export 'utils.dart'; +export 'utils/utils.dart'; diff --git a/lib/src/theme/macos_theme.dart b/lib/src/theme/macos_theme.dart index 87405882..0bda4d11 100644 --- a/lib/src/theme/macos_theme.dart +++ b/lib/src/theme/macos_theme.dart @@ -514,8 +514,7 @@ class MacosThemeData with Diagnosticable { typography: MacosTypography.lerp(a.typography, b.typography, t), helpButtonTheme: HelpButtonThemeData.lerp(a.helpButtonTheme, b.helpButtonTheme, t), - pushButtonTheme: - PushButtonThemeData.lerp(a.pushButtonTheme, b.pushButtonTheme, t), + pushButtonTheme: a.pushButtonTheme, tooltipTheme: MacosTooltipThemeData.lerp(a.tooltipTheme, b.tooltipTheme, t), visualDensity: VisualDensity.lerp(a.visualDensity, b.visualDensity, t), @@ -581,7 +580,7 @@ class MacosThemeData with Diagnosticable { canvasColor: canvasColor ?? this.canvasColor, dividerColor: dividerColor ?? this.dividerColor, typography: this.typography.merge(typography), - pushButtonTheme: this.pushButtonTheme.merge(pushButtonTheme), + pushButtonTheme: this.pushButtonTheme, helpButtonTheme: this.helpButtonTheme.merge(helpButtonTheme), tooltipTheme: this.tooltipTheme.merge(tooltipTheme), visualDensity: visualDensity ?? this.visualDensity, @@ -605,7 +604,7 @@ class MacosThemeData with Diagnosticable { canvasColor: other.canvasColor, dividerColor: other.dividerColor, typography: typography.merge(other.typography), - pushButtonTheme: pushButtonTheme.merge(other.pushButtonTheme), + pushButtonTheme: pushButtonTheme, helpButtonTheme: helpButtonTheme.merge(other.helpButtonTheme), tooltipTheme: tooltipTheme.merge(other.tooltipTheme), visualDensity: other.visualDensity, diff --git a/lib/src/theme/push_button_theme.dart b/lib/src/theme/push_button_theme.dart index c7d27039..0c69aebd 100644 --- a/lib/src/theme/push_button_theme.dart +++ b/lib/src/theme/push_button_theme.dart @@ -9,8 +9,6 @@ import 'package:macos_ui/src/library.dart'; /// * [PushButtonThemeData], which is used to configure this theme. class PushButtonTheme extends InheritedTheme { /// Create a [PushButtonTheme]. - /// - /// The [data] parameter must not be null. const PushButtonTheme({ super.key, required this.data, @@ -18,6 +16,9 @@ class PushButtonTheme extends InheritedTheme { }); /// The configuration of this theme. + @Deprecated( + "'PushButton' no longer uses singular colors and therefore cannot " + "be themed using a 'PushButtonTheme'.") final PushButtonThemeData data; /// The closest instance of this class that encloses the given context. @@ -30,6 +31,9 @@ class PushButtonTheme extends InheritedTheme { /// ```dart /// PushButtonTheme theme = PushButtonTheme.of(context); /// ``` + @Deprecated( + "'PushButton' no longer uses singular colors and therefore cannot " + "be themed using a 'PushButtonTheme'.") static PushButtonThemeData of(BuildContext context) { final PushButtonTheme? buttonTheme = context.dependOnInheritedWidgetOfExactType(); @@ -37,11 +41,17 @@ class PushButtonTheme extends InheritedTheme { } @override + @Deprecated( + "'PushButton' no longer uses singular colors and therefore cannot " + "be themed using a 'PushButtonTheme'.") Widget wrap(BuildContext context, Widget child) { return PushButtonTheme(data: data, child: child); } @override + @Deprecated( + "'PushButton' no longer uses singular colors and therefore cannot " + "be themed using a 'PushButtonTheme'.") bool updateShouldNotify(PushButtonTheme oldWidget) => data != oldWidget.data; } @@ -63,15 +73,27 @@ class PushButtonThemeData with Diagnosticable { }); /// The default background color for [PushButton] + @Deprecated( + "'PushButton' no longer uses singular colors and therefore cannot " + "be themed using a 'PushButtonTheme'.") final Color? color; /// The default disabled color for [PushButton] + @Deprecated( + "'PushButton' no longer uses singular colors and therefore cannot " + "be themed using a 'PushButtonTheme'.") final Color? disabledColor; /// The default secondary color (e.g. Cancel/Go back buttons) for [PushButton] + @Deprecated( + "'PushButton' no longer uses singular colors and therefore cannot " + "be themed using a 'PushButtonTheme'.") final Color? secondaryColor; /// Copies this [PushButtonThemeData] into another. + @Deprecated( + "'PushButton' no longer uses singular colors and therefore cannot " + "be themed using a 'PushButtonTheme'.") PushButtonThemeData copyWith({ Color? color, Color? disabledColor, @@ -87,6 +109,9 @@ class PushButtonThemeData with Diagnosticable { /// Linearly interpolate between two [PushButtonThemeData]. /// /// All the properties must be non-null. + @Deprecated( + "'PushButton' no longer uses singular colors and therefore cannot " + "be themed using a 'PushButtonTheme'.") static PushButtonThemeData lerp( PushButtonThemeData a, PushButtonThemeData b, @@ -100,6 +125,9 @@ class PushButtonThemeData with Diagnosticable { } @override + @Deprecated( + "'PushButton' no longer uses singular colors and therefore cannot " + "be themed using a 'PushButtonTheme'.") bool operator ==(Object other) => identical(this, other) || other is PushButtonThemeData && @@ -109,9 +137,15 @@ class PushButtonThemeData with Diagnosticable { secondaryColor?.value == other.secondaryColor?.value; @override + @Deprecated( + "'PushButton' no longer uses singular colors and therefore cannot " + "be themed using a 'PushButtonTheme'.") int get hashCode => color.hashCode ^ disabledColor.hashCode; @override + @Deprecated( + "'PushButton' no longer uses singular colors and therefore cannot " + "be themed using a 'PushButtonTheme'.") void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); properties.add(ColorProperty('color', color)); @@ -120,6 +154,9 @@ class PushButtonThemeData with Diagnosticable { } /// Merges this [PushButtonThemeData] with another. + @Deprecated( + "'PushButton' no longer uses singular colors and therefore cannot " + "be themed using a 'PushButtonTheme'.") PushButtonThemeData merge(PushButtonThemeData? other) { if (other == null) return this; return copyWith( diff --git a/lib/src/utils/accent_color_listener.dart b/lib/src/utils/accent_color_listener.dart new file mode 100644 index 00000000..5965e3ab --- /dev/null +++ b/lib/src/utils/accent_color_listener.dart @@ -0,0 +1,160 @@ +import 'dart:async'; +import 'dart:io'; +import 'dart:math'; + +import 'package:appkit_ui_element_colors/appkit_ui_element_colors.dart'; +import 'package:flutter/foundation.dart'; +import 'package:macos_ui/src/enums/accent_color.dart'; + +/// A class that listens for changes to the user’s selected system accent color. +/// +/// Native macOS applications respond to such changes immediately. +/// +/// Example using [StreamBuilder]: +/// +/// ```dart +/// StreamBuilder( +/// stream: AccentColorListener.instance.onChanged, +/// builder: (context, _) { +/// final AccentColor? accentColor = +/// AccentColorListener.instance.currentAccentColor; +/// +/// return SomeWidget( +/// accentColor: accentColor, +/// child: … +/// ); +/// }, +/// ); +/// ``` +class AccentColorListener { + /// A class that listens to accent color changes. + AccentColorListener() { + _init(); + } + + /// A shared instance of [AccentColorListener]. + static final instance = AccentColorListener(); + + /// A map which maps hue components of the [UiElementColor.controlAccentColor] + /// color captured with the [NSAppearanceName.aqua] appearance in the + /// [NSColorSpace.genericRGB] color space to the corresponding [AccentColor]. + static final hueComponentToAccentColor = { + 0.6085324903200698: AccentColor.blue, + 0.8285987697113538: AccentColor.purple, + 0.9209523937489168: AccentColor.pink, + 0.9861913496946438: AccentColor.red, + 0.06543037411201169: AccentColor.orange, + 0.11813830353929083: AccentColor.yellow, + 0.29428158007138466: AccentColor.green, + 0.0: AccentColor.graphite, + }; + + /// The active accent color selection. + AccentColor? _currentAccentColor; + + /// The currently active accent color. + AccentColor? get currentAccentColor => _currentAccentColor; + + /// Notifies listeners when the accent color changes. + final _accentColorStreamController = StreamController.broadcast(); + + /// Streams the user’s system accent color selection. + /// + /// Emits a new value whenever the system accent color selection changes. + Stream get onChanged => _accentColorStreamController.stream; + + /// A stream subscription for the [SystemColorObserver] stream. + StreamSubscription? _systemColorObserverStreamSubscription; + + /// Initializes this class. + void _init() { + if (kIsWeb) return; + if (!Platform.isMacOS) return; + + _initCurrentAccentColor(); + _initSystemColorObserver(); + } + + /// Disposes this listener. + void dispose() { + _systemColorObserverStreamSubscription?.cancel(); + } + + /// Initializes the current accent color. This method is to be called whenever + /// a change is detected. + Future _initCurrentAccentColor() async { + final hueComponent = await _getHueComponent(); + _currentAccentColor = _resolveAccentColorFromHueComponent(hueComponent); + _accentColorStreamController.add(null); + } + + /// Initializes the current system color observer. This method may only be + /// called once. + void _initSystemColorObserver() { + assert(_systemColorObserverStreamSubscription == null); + + _systemColorObserverStreamSubscription = + AppkitUiElementColors.systemColorObserver.stream.listen((_) { + _initCurrentAccentColor(); + _accentColorStreamController.add(null); + }); + } + + /// Returns the hue component of the active accent color selection on macOS. + Future _getHueComponent() async { + final color = await AppkitUiElementColors.getColorComponents( + uiElementColor: UiElementColor.controlAccentColor, + components: const { + NSColorComponent.hueComponent, + }, + colorSpace: NSColorSpace.genericRGB, + appearance: NSAppearanceName.aqua, + ); + + assert(color.containsKey("hueComponent")); + + return color["hueComponent"]!; + } + + /// Returns the [AccentColor] which corresponds to the provided + /// [hueComponent]. + AccentColor _resolveAccentColorFromHueComponent(double hueComponent) { + if (hueComponentToAccentColor.containsKey(hueComponent)) { + return hueComponentToAccentColor[hueComponent]!; + } + + debugPrint( + 'Warning: Falling back on slow accent color resolution. It’s possible ' + 'that the accent colors have changed in a recent version of macOS, thus ' + 'invalidating macos_ui’s accent colors, which were captured on macOS ' + 'Ventura. If you see this message, please notify a maintainer of the ' + 'macos_ui package.', + ); + + return _slowlyResolveAccentColorFromHueComponent(hueComponent); + } + + /// This is a fallback method in case [_resolveAccentColorFromHueComponent] + /// fails. + AccentColor _slowlyResolveAccentColorFromHueComponent(double hueComponent) { + final entries = hueComponentToAccentColor.entries; + var lowestDistance = double.maxFinite; + var toBeReturnedAccentColor = AccentColor.values.first; + + for (final entry in entries) { + final distance = _distanceBetweenHueComponents(hueComponent, entry.key); + if (distance < lowestDistance) { + lowestDistance = distance; + toBeReturnedAccentColor = entry.value; + } + } + + return toBeReturnedAccentColor; + } + + /// Returns the distance between two hue components. + double _distanceBetweenHueComponents(double component1, double component2) { + final rawDifference = (component1 - component2).abs(); + return sin(rawDifference * pi); + } +} diff --git a/lib/src/utils/macos_brightness_override_handler.dart b/lib/src/utils/macos_brightness_override_handler.dart new file mode 100644 index 00000000..3fad912b --- /dev/null +++ b/lib/src/utils/macos_brightness_override_handler.dart @@ -0,0 +1,25 @@ +import 'dart:io'; + +import 'package:flutter/foundation.dart'; +import 'package:macos_ui/macos_ui.dart'; + +/// 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/lib/src/utils.dart b/lib/src/utils/utils.dart similarity index 67% rename from lib/src/utils.dart rename to lib/src/utils/utils.dart index 1f2d8a1e..3d63855c 100644 --- a/lib/src/utils.dart +++ b/lib/src/utils/utils.dart @@ -1,9 +1,10 @@ -import 'dart:io'; - -import 'package:flutter/foundation.dart'; import 'package:macos_ui/macos_ui.dart'; import 'package:macos_ui/src/library.dart'; +export 'window_main_state_listener.dart'; +export 'accent_color_listener.dart'; +export 'macos_brightness_override_handler.dart'; + /// Asserts that the given context has a [MacosTheme] ancestor. /// /// To call this function, use the following pattern, typically in the @@ -65,24 +66,3 @@ class Unsupported { 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/lib/src/utils/window_main_state_listener.dart b/lib/src/utils/window_main_state_listener.dart new file mode 100644 index 00000000..c926f2b3 --- /dev/null +++ b/lib/src/utils/window_main_state_listener.dart @@ -0,0 +1,109 @@ +import 'dart:async'; +import 'dart:io'; + +import 'package:flutter/foundation.dart'; +import 'package:macos_ui/macos_ui.dart'; + +/// A class that listens for changes to the application’s main window. +/// +/// A common use-case for responding to such changes would be to mute the colors +/// of certain primary UI elements when the window is no longer in focus, which +/// is something native macOS applications do out of the box. +/// +/// Example using [StreamBuilder]: +/// +/// ```dart +/// StreamBuilder( +/// stream: WindowMainStateListener.instance.onChanged, +/// builder: (context, _) { +/// final bool isMainWindow +/// = WindowMainStateListener.instance.isMainWindow; +/// +/// return SomeWidget( +/// isMainWindow: isMainWindow, +/// child: … +/// ); +/// }, +/// ); +/// ``` +class WindowMainStateListener { + /// A class that listens for changes to the application’s window being the + /// main window, and notifies listeners. + WindowMainStateListener() { + _init(); + } + + /// A shared instance of [WindowMainStateListener]. + static final instance = WindowMainStateListener(); + + /// A [NSWindowDelegateHandle], to be used when disposing the listener. + NSWindowDelegateHandle? handle; + + /// Whether the window is currently the main window. + bool _isMainWindow = true; + + /// Whether the window is currently the main window. + bool get isMainWindow => _isMainWindow; + + /// Notifies listeners when the window’s main state changes. + final _windowMainStateStreamController = StreamController.broadcast(); + + /// A stream of the window’s main state. Emits a new value whenever the state + /// changes. + Stream get onChanged => _windowMainStateStreamController.stream; + + /// Initializes the listener. This should only be called once. + void _init() { + if (kIsWeb) return; + if (!Platform.isMacOS) return; + + _initDelegate(); + _initIsWindowMain(); + } + + /// Initializes the [NSWindowDelegate] to listen for main window changes. + void _initDelegate() { + final delegate = _WindowMainStateListenerDelegate( + onWindowDidBecomeMain: () { + _isMainWindow = true; + _windowMainStateStreamController.add(true); + }, + onWindowDidResignMain: () { + _isMainWindow = false; + _windowMainStateStreamController.add(false); + }, + ); + handle = WindowManipulator.addNSWindowDelegate(delegate); + } + + /// Initializes the [_isMainWindow] variable. + Future _initIsWindowMain() async { + _isMainWindow = await WindowManipulator.isMainWindow(); + _windowMainStateStreamController.add(_isMainWindow); + } + + /// Disposes this listener. + void dispose() { + handle?.removeFromHandler(); + } +} + +/// The [NSWindowDelegate] used by [WindowMainStateListener]. +class _WindowMainStateListenerDelegate extends NSWindowDelegate { + _WindowMainStateListenerDelegate({ + required this.onWindowDidBecomeMain, + required this.onWindowDidResignMain, + }); + + /// Called when the window becomes the main window. + final void Function() onWindowDidBecomeMain; + + /// Called when the window resigns as the main window. + final void Function() onWindowDidResignMain; + + @override + void windowDidBecomeMain() => onWindowDidBecomeMain(); + + @override + void windowDidResignMain() => onWindowDidResignMain(); +} diff --git a/pubspec.lock b/pubspec.lock index dfa8a61c..62a3f534 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -17,6 +17,14 @@ packages: url: "https://pub.dev" source: hosted version: "5.13.0" + appkit_ui_element_colors: + dependency: "direct main" + description: + name: appkit_ui_element_colors + sha256: c3e50f900aae314d339de489535736238627071457c4a4a2dbbb1545b4f04f22 + url: "https://pub.dev" + source: hosted + version: "1.0.0" args: dependency: transitive description: @@ -61,10 +69,10 @@ packages: dependency: transitive description: name: collection - sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c" + sha256: f092b211a4319e98e5ff58223576de6c2803db36221657b46c82574721240687 url: "https://pub.dev" source: hosted - version: "1.17.1" + version: "1.17.2" convert: dependency: transitive description: @@ -89,6 +97,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.3" + equatable: + dependency: transitive + description: + name: equatable + sha256: c2b87cb7756efdf69892005af546c56c0b5037f54d2a88269b4f347a505e3ca2 + url: "https://pub.dev" + source: hosted + version: "2.0.5" fake_async: dependency: transitive description: @@ -139,6 +155,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.1" + gradient_borders: + dependency: "direct main" + description: + name: gradient_borders + sha256: "69eeaff519d145a4c6c213ada1abae386bcc8981a4970d923e478ce7ba19e309" + url: "https://pub.dev" + source: hosted + version: "1.0.0" http_multi_server: dependency: transitive description: @@ -191,26 +215,26 @@ packages: dependency: "direct main" description: name: macos_window_utils - sha256: b78a210aa70ca7ccad6e7b7b810fb4689c507f4a46e299214900b2a1eb70ea23 + sha256: "43a90473f8786f00f07203e6819dab67e032f8896dafa4a6f85fbc71fba32c0b" url: "https://pub.dev" source: hosted - version: "1.1.3" + version: "1.2.0" matcher: dependency: transitive description: name: matcher - sha256: "6501fbd55da300384b768785b83e5ce66991266cec21af89ab9ae7f5ce1c4cbb" + sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e" url: "https://pub.dev" source: hosted - version: "0.12.15" + version: "0.12.16" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724 + sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41" url: "https://pub.dev" source: hosted - version: "0.2.0" + version: "0.5.0" meta: dependency: transitive description: @@ -259,6 +283,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.8.3" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + sha256: "43798d895c929056255600343db8f049921cbec94d31ec87f1dc5c16c01935dd" + url: "https://pub.dev" + source: hosted + version: "2.1.5" pool: dependency: transitive description: @@ -332,10 +364,10 @@ packages: dependency: transitive description: name: source_span - sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250 + sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" url: "https://pub.dev" source: hosted - version: "1.9.1" + version: "1.10.0" stack_trace: dependency: transitive description: @@ -372,26 +404,26 @@ packages: dependency: transitive description: name: test - sha256: "3dac9aecf2c3991d09b9cdde4f98ded7b30804a88a0d7e4e7e1678e78d6b97f4" + sha256: "13b41f318e2a5751c3169137103b60c584297353d4b1761b66029bae6411fe46" url: "https://pub.dev" source: hosted - version: "1.24.1" + version: "1.24.3" test_api: dependency: transitive description: name: test_api - sha256: eb6ac1540b26de412b3403a163d919ba86f6a973fe6cc50ae3541b80092fdcfb + sha256: "75760ffd7786fffdfb9597c35c5b27eaeec82be8edfb6d71d32651128ed7aab8" url: "https://pub.dev" source: hosted - version: "0.5.1" + version: "0.6.0" test_core: dependency: transitive description: name: test_core - sha256: "5138dbffb77b2289ecb12b81c11ba46036590b72a64a7a90d6ffb880f1a29e93" + sha256: "99806e9e6d95c7b059b7a0fc08f07fc53fabe54a829497f0d9676299f1e8637e" url: "https://pub.dev" source: hosted - version: "0.5.1" + version: "0.5.3" typed_data: dependency: transitive description: @@ -424,6 +456,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.0" + web: + dependency: transitive + description: + name: web + sha256: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10 + url: "https://pub.dev" + source: hosted + version: "0.1.4-beta" web_socket_channel: dependency: transitive description: @@ -449,5 +489,5 @@ packages: source: hosted version: "3.1.2" sdks: - dart: ">=3.0.0 <4.0.0" + dart: ">=3.1.0-185.0.dev <4.0.0" flutter: ">=3.10.0" diff --git a/pubspec.yaml b/pubspec.yaml index 13865b31..8ffa130b 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: 2.0.0 +version: 2.0.1 homepage: "https://macosui.dev" repository: "https://github.com/GroovinChip/macos_ui" @@ -11,7 +11,9 @@ environment: dependencies: flutter: sdk: flutter - macos_window_utils: ^1.1.3 + macos_window_utils: ^1.2.0 + gradient_borders: ^1.0.0 + appkit_ui_element_colors: ^1.0.0 dev_dependencies: flutter_test: diff --git a/test/buttons/push_button_test.dart b/test/buttons/push_button_test.dart index 8bb294a0..981c21df 100644 --- a/test/buttons/push_button_test.dart +++ b/test/buttons/push_button_test.dart @@ -99,7 +99,6 @@ void main() { 'controlSize: regular', 'color: null', 'disabledColor: null', - 'pressedOpacity: 0.4', 'alignment: Alignment.center', 'semanticLabel: null', 'borderRadius: BorderRadius.circular(4.0)', diff --git a/test/theme/push_button_theme_test.dart b/test/theme/push_button_theme_test.dart deleted file mode 100644 index 2ff90243..00000000 --- a/test/theme/push_button_theme_test.dart +++ /dev/null @@ -1,97 +0,0 @@ -import 'package:flutter/foundation.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', () { - test('lerps from light to dark', () { - final actual = - PushButtonThemeData.lerp(_pushButtonTheme, _pushButtonThemeDark, 1); - - expect(actual, _pushButtonThemeDark); - }); - - test('lerps from dark to light', () { - final actual = - PushButtonThemeData.lerp(_pushButtonThemeDark, _pushButtonTheme, 1); - - expect(actual, _pushButtonTheme); - }); - - test('copyWith, hashCode, ==', () { - expect( - const PushButtonThemeData(), - const PushButtonThemeData().copyWith(), - ); - expect( - const PushButtonThemeData().hashCode, - const PushButtonThemeData().copyWith().hashCode, - ); - }); - - testWidgets('debugFillProperties', (tester) async { - final builder = DiagnosticPropertiesBuilder(); - PushButtonThemeData( - color: MacosColors.appleBlue, - disabledColor: MacosColors.systemGrayColor.color, - secondaryColor: MacosColors.controlColor.color, - ).debugFillProperties(builder); - - final description = builder.properties - .where((node) => !node.isFiltered(DiagnosticLevel.info)) - .map((node) => node.toString()) - .toList(); - - expect( - description, - [ - 'color: MacosColor(0xff0433ff)', - 'disabledColor: MacosColor(0xff8e8e93)', - 'secondaryColor: Color(0x19000000)', - ], - ); - }); - - testWidgets('Default values in widget tree', (tester) async { - late BuildContext capturedContext; - await tester.pumpWidget( - MacosApp( - home: MacosWindow( - disableWallpaperTinting: true, - child: MacosScaffold( - children: [ - ContentArea( - builder: (context, _) { - capturedContext = context; - return const PushButton( - controlSize: ControlSize.regular, - child: Text('Push me'), - ); - }, - ), - ], - ), - ), - ), - ); - - final theme = PushButtonTheme.of(capturedContext); - expect(theme.color, const Color(0xff007aff)); - expect(theme.disabledColor, const Color.fromRGBO(244, 245, 245, 1.0)); - expect(theme.secondaryColor, MacosColors.white); - }); - }); -} - -final _pushButtonTheme = PushButtonThemeData( - color: MacosColors.appleRed, - disabledColor: MacosColors.systemGrayColor.color, - secondaryColor: MacosColors.controlColor.color, -); - -final _pushButtonThemeDark = PushButtonThemeData( - color: MacosColors.appleBlue, - disabledColor: MacosColors.systemGrayColor.darkColor, - secondaryColor: MacosColors.controlColor.darkColor, -); From 523cb67dc4038219a88f307855b767a4dff0a020 Mon Sep 17 00:00:00 2001 From: Adrian Samoticha Date: Sat, 30 Sep 2023 19:36:55 +0200 Subject: [PATCH 129/151] update `LastUpgradeVersion` and `LastUpgradeCheck` in example --- example/macos/Runner.xcodeproj/project.pbxproj | 2 +- .../Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/example/macos/Runner.xcodeproj/project.pbxproj b/example/macos/Runner.xcodeproj/project.pbxproj index 2c562a4e..428da703 100644 --- a/example/macos/Runner.xcodeproj/project.pbxproj +++ b/example/macos/Runner.xcodeproj/project.pbxproj @@ -202,7 +202,7 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0920; - LastUpgradeCheck = 1300; + LastUpgradeCheck = 1430; ORGANIZATIONNAME = ""; TargetAttributes = { 33CC10EC2044A3C60003C045 = { diff --git a/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index fb7259e1..83d88728 100644 --- a/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ Date: Sat, 30 Sep 2023 19:37:01 +0200 Subject: [PATCH 130/151] fix typo --- lib/src/layout/sidebar/sidebar_item.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/layout/sidebar/sidebar_item.dart b/lib/src/layout/sidebar/sidebar_item.dart index b7e2d487..706412d8 100644 --- a/lib/src/layout/sidebar/sidebar_item.dart +++ b/lib/src/layout/sidebar/sidebar_item.dart @@ -43,7 +43,7 @@ class SidebarItem with Diagnosticable { final Color? unselectedColor; /// The [shape] property specifies the outline (border) of the - /// decoration. The shape must not be null. It's used alonside + /// decoration. The shape must not be null. It's used alongside /// [selectedColor]. final ShapeBorder? shape; From 02d504ef9f7cae0615b5f21cb9244012987737b4 Mon Sep 17 00:00:00 2001 From: Adrian Samoticha Date: Sat, 30 Sep 2023 21:13:33 +0200 Subject: [PATCH 131/151] format `window.dart` --- lib/src/layout/window.dart | 178 ++++++++++++++++++++----------------- 1 file changed, 94 insertions(+), 84 deletions(-) diff --git a/lib/src/layout/window.dart b/lib/src/layout/window.dart index 85c20417..7db89923 100644 --- a/lib/src/layout/window.dart +++ b/lib/src/layout/window.dart @@ -265,95 +265,105 @@ class _MacosWindowState extends State { minHeight: height, maxHeight: height, ).normalize(), - 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, + 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 (sidebar.bottom != null && - constraints.maxHeight > 141) - Padding( - padding: const EdgeInsets.all(16.0), - child: 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, + ) + : 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!, + ), + ], ), ), - if (sidebar.bottom != null && - constraints.maxHeight > 141) - Padding( - padding: const EdgeInsets.all(16.0), - child: sidebar.bottom!, - ), - ], - ), - ), ), ), From 6557d05e7672fe02e417900f9d9749ee2f99ccb0 Mon Sep 17 00:00:00 2001 From: Adrian Samoticha Date: Sat, 30 Sep 2023 21:14:47 +0200 Subject: [PATCH 132/151] improve formatting for `window.dart` --- lib/src/layout/window.dart | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/lib/src/layout/window.dart b/lib/src/layout/window.dart index 7db89923..fa32d640 100644 --- a/lib/src/layout/window.dart +++ b/lib/src/layout/window.dart @@ -271,10 +271,10 @@ class _MacosWindowState extends State { 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. + // 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) ...[ @@ -291,7 +291,8 @@ class _MacosWindowState extends State { constraints.maxHeight > 81) Padding( padding: const EdgeInsets.symmetric( - horizontal: 8.0), + horizontal: 8.0, + ), child: sidebar.top!, ), Expanded( @@ -320,10 +321,10 @@ class _MacosWindowState extends State { 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. + // 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) ...[ @@ -340,7 +341,8 @@ class _MacosWindowState extends State { constraints.maxHeight > 81) Padding( padding: const EdgeInsets.symmetric( - horizontal: 8.0), + horizontal: 8.0, + ), child: sidebar.top!, ), Expanded( From 97751dc46ac0a4f8e598faf69974c70bf7389ebb Mon Sep 17 00:00:00 2001 From: Adrian Samoticha Date: Tue, 3 Oct 2023 23:56:14 +0200 Subject: [PATCH 133/151] fix sidebar appearing too dark --- lib/src/layout/window.dart | 90 ++++++++++++++++++++------------------ 1 file changed, 48 insertions(+), 42 deletions(-) diff --git a/lib/src/layout/window.dart b/lib/src/layout/window.dart index fa32d640..d28511c0 100644 --- a/lib/src/layout/window.dart +++ b/lib/src/layout/window.dart @@ -318,52 +318,58 @@ class _MacosWindowState extends State { ) : 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: DecoratedBox( + decoration: const BoxDecoration( + color: Color.fromRGBO(0, 0, 0, 1.0), + backgroundBlendMode: BlendMode.clear, + ), + 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!, ), - child: sidebar.top!, - ), - Expanded( - child: MacosScrollbar( - controller: _sidebarScrollController, - child: Padding( - padding: sidebar.padding, - child: sidebar.builder( - context, - _sidebarScrollController, + 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!, - ), - ], + if (sidebar.bottom != null && + constraints.maxHeight > 141) + Padding( + padding: const EdgeInsets.all(16.0), + child: sidebar.bottom!, + ), + ], + ), ), ), ), From 873807ae7cedbf6cb46d1403933ec266e5727f20 Mon Sep 17 00:00:00 2001 From: Adrian Samoticha Date: Sat, 7 Oct 2023 19:39:03 +0200 Subject: [PATCH 134/151] =?UTF-8?q?make=20sidebar=20items=20adapt=20their?= =?UTF-8?q?=20color=20based=20on=20the=20user=E2=80=99s=20selected=20syste?= =?UTF-8?q?m=20accent=20color?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/src/layout/sidebar/sidebar_items.dart | 163 +++++++++++++++++----- 1 file changed, 131 insertions(+), 32 deletions(-) diff --git a/lib/src/layout/sidebar/sidebar_items.dart b/lib/src/layout/sidebar/sidebar_items.dart index 65b3dadb..e24f90d3 100644 --- a/lib/src/layout/sidebar/sidebar_items.dart +++ b/lib/src/layout/sidebar/sidebar_items.dart @@ -1,4 +1,5 @@ import 'package:macos_ui/macos_ui.dart'; +import 'package:macos_ui/src/enums/accent_color.dart'; import 'package:macos_ui/src/library.dart'; const Duration _kExpand = Duration(milliseconds: 200); @@ -79,7 +80,8 @@ class SidebarItems extends StatelessWidget { /// The color to paint the item when it's selected. /// - /// If null, [MacosThemeData.primaryColor] is used. + /// If null, the color is chosen automatically based on the user’s selected + /// system accent color and whether the sidebar is in the main window. final Color? selectedColor; /// The color to paint the item when it's unselected. @@ -97,6 +99,21 @@ class SidebarItems extends StatelessWidget { /// Defaults to [SystemMouseCursors.basic]. final MouseCursor? cursor; + /// The user’s selected system accent color. + AccentColor get _accentColor => + AccentColorListener.instance.currentAccentColor ?? AccentColor.blue; + + /// Returns the sidebar item’s selected color. + Color _getColor(BuildContext context) { + final isMainWindow = WindowMainStateListener.instance.isMainWindow; + + return _ColorProvider.getSelectedColor( + accentColor: _accentColor, + isDarkModeEnabled: MacosTheme.of(context).brightness.isDark, + isWindowMain: isMainWindow, + ); + } + List get _allItems { List result = []; for (var element in items) { @@ -117,39 +134,50 @@ class SidebarItems extends StatelessWidget { final theme = MacosTheme.of(context); return MacosIconTheme.merge( data: const MacosIconThemeData(size: 20), - child: _SidebarItemsConfiguration( - selectedColor: selectedColor ?? theme.primaryColor, - unselectedColor: unselectedColor ?? MacosColors.transparent, - shape: shape ?? _defaultShape, - itemSize: itemSize, - child: ListView( - controller: scrollController, - physics: const ClampingScrollPhysics(), - padding: EdgeInsets.all(10.0 - theme.visualDensity.horizontal), - children: List.generate(items.length, (index) { - final item = items[index]; - if (item.disclosureItems != null) { - return MouseRegion( - cursor: cursor!, - child: _DisclosureSidebarItem( - item: item, - selectedItem: _allItems[currentIndex], - onChanged: (item) { - onChanged(_allItems.indexOf(item)); - }, + child: StreamBuilder( + stream: AccentColorListener.instance.onChanged, + builder: (context, _) { + return StreamBuilder( + stream: WindowMainStateListener.instance.onChanged, + builder: (context, _) { + return _SidebarItemsConfiguration( + selectedColor: selectedColor ?? _getColor(context), + unselectedColor: unselectedColor ?? MacosColors.transparent, + shape: shape ?? _defaultShape, + itemSize: itemSize, + child: ListView( + controller: scrollController, + physics: const ClampingScrollPhysics(), + padding: + EdgeInsets.all(10.0 - theme.visualDensity.horizontal), + children: List.generate(items.length, (index) { + final item = items[index]; + if (item.disclosureItems != null) { + return MouseRegion( + cursor: cursor!, + child: _DisclosureSidebarItem( + item: item, + selectedItem: _allItems[currentIndex], + onChanged: (item) { + onChanged(_allItems.indexOf(item)); + }, + ), + ); + } + return MouseRegion( + cursor: cursor!, + child: _SidebarItem( + item: item, + selected: _allItems[currentIndex] == item, + onClick: () => onChanged(_allItems.indexOf(item)), + ), + ); + }), ), ); - } - return MouseRegion( - cursor: cursor!, - child: _SidebarItem( - item: item, - selected: _allItems[currentIndex] == item, - onClick: () => onChanged(_allItems.indexOf(item)), - ), - ); - }), - ), + }, + ); + }, ), ); } @@ -497,3 +525,74 @@ class __DisclosureSidebarItemState extends State<_DisclosureSidebarItem> ); } } + +class _ColorProvider { + /// Returns the selected color based on the provided parameters. + static Color getSelectedColor({ + required AccentColor accentColor, + required bool isDarkModeEnabled, + required bool isWindowMain, + }) { + if (isDarkModeEnabled) { + if (!isWindowMain) { + return const MacosColor.fromRGBO(76, 78, 65, 1.0); + } + + switch (accentColor) { + case AccentColor.blue: + return const MacosColor.fromRGBO(22, 105, 229, 0.749); + + case AccentColor.purple: + return const MacosColor.fromRGBO(204, 45, 202, 0.749); + + case AccentColor.pink: + return const MacosColor.fromRGBO(229, 74, 145, 0.749); + + case AccentColor.red: + return const MacosColor.fromRGBO(238, 64, 68, 0.749); + + case AccentColor.orange: + return const MacosColor.fromRGBO(244, 114, 0, 0.749); + + case AccentColor.yellow: + return const MacosColor.fromRGBO(233, 176, 0, 0.749); + + case AccentColor.green: + return const MacosColor.fromRGBO(76, 177, 45, 0.749); + + case AccentColor.graphite: + return const MacosColor.fromRGBO(129, 129, 122, 0.824); + } + } + + if (!isWindowMain) { + return const MacosColor.fromRGBO(213, 213, 208, 1.0); + } + + switch (accentColor) { + case AccentColor.blue: + return const MacosColor.fromRGBO(9, 129, 255, 0.749); + + case AccentColor.purple: + return const MacosColor.fromRGBO(162, 28, 165, 0.749); + + case AccentColor.pink: + return const MacosColor.fromRGBO(234, 81, 152, 0.749); + + case AccentColor.red: + return const MacosColor.fromRGBO(220, 32, 40, 0.749); + + case AccentColor.orange: + return const MacosColor.fromRGBO(245, 113, 0, 0.749); + + case AccentColor.yellow: + return const MacosColor.fromRGBO(240, 180, 2, 0.749); + + case AccentColor.green: + return const MacosColor.fromRGBO(66, 174, 33, 0.749); + + case AccentColor.graphite: + return const MacosColor.fromRGBO(174, 174, 167, 0.847); + } + } +} From 2b1dba16c00ea67262750cffe8ba026f46c12125 Mon Sep 17 00:00:00 2001 From: Adrian Samoticha Date: Sat, 7 Oct 2023 19:44:29 +0200 Subject: [PATCH 135/151] update changelog entry for version 2.0.1 --- CHANGELOG.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f27d69cf..981068e8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,9 @@ ## [2.0.1] ### 🔄 Updated 🔄 * `PushButton` has received a facelift. It now mimics the look and feel of native macOS buttons more closely. - * **Note:** As a result, its `pressedOpacity` property and the `PushButtonTheme` class have been deprecated. + * **Note:** As a result, its `pressedOpacity` property and the `PushButtonTheme` class have been deprecated. +* Fixed a bug that caused the sidebar to appear darker than intended. +* `SidebarItems` has now respects the user’s selected accent color and mimics the look of macOS’ sidebar items more closely. ## [2.0.0] ### 🚨 Breaking Changes 🚨 From c6d37d88e38bddc0723fee9eb57c146aa9f0053d Mon Sep 17 00:00:00 2001 From: Sam Rawlins Date: Thu, 12 Oct 2023 10:13:18 -0700 Subject: [PATCH 136/151] Remove usages of '@image' directive (#483) * Remove usages of '@image' directive * changelog --- CHANGELOG.md | 4 ++++ lib/src/indicators/slider.dart | 1 - lib/src/layout/sidebar/sidebar_item.dart | 2 +- lib/src/layout/tab_view/tab_view.dart | 2 +- pubspec.yaml | 2 +- 5 files changed, 7 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f27d69cf..b6ddedda 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## [2.0.2] +### 🛠️ Fixed 🛠️ +* Fixed images in generated documentation. + ## [2.0.1] ### 🔄 Updated 🔄 * `PushButton` has received a facelift. It now mimics the look and feel of native macOS buttons more closely. diff --git a/lib/src/indicators/slider.dart b/lib/src/indicators/slider.dart index 9e5e7514..e32fc360 100644 --- a/lib/src/indicators/slider.dart +++ b/lib/src/indicators/slider.dart @@ -20,7 +20,6 @@ const double _kDiscreteThumbBorderRadius = 8; /// The slider doesn't maintain any state itself, instead the user is expected to /// update this widget with a new [value] whenever the slider changes. /// -/// {@image } /// {@endtemplate} class MacosSlider extends StatelessWidget { /// {@macro macosSlider} diff --git a/lib/src/layout/sidebar/sidebar_item.dart b/lib/src/layout/sidebar/sidebar_item.dart index b7e2d487..1454ccbd 100644 --- a/lib/src/layout/sidebar/sidebar_item.dart +++ b/lib/src/layout/sidebar/sidebar_item.dart @@ -62,7 +62,7 @@ class SidebarItem with Diagnosticable { /// /// Typically a text indicator of a count of items, like in this /// screenshots from the Apple Notes app: - /// {@image } + /// final Widget? trailing; @override diff --git a/lib/src/layout/tab_view/tab_view.dart b/lib/src/layout/tab_view/tab_view.dart index fa32c734..cca58d2a 100644 --- a/lib/src/layout/tab_view/tab_view.dart +++ b/lib/src/layout/tab_view/tab_view.dart @@ -26,7 +26,7 @@ enum MacosTabPosition { /// {@template macosTabView} /// A multipage interface that displays one page at a time. /// -/// {@image } +/// /// /// A tab view contains a row of navigational items, [tabs], that move the /// user through the provided views ([children]). The user selects the desired diff --git a/pubspec.yaml b/pubspec.yaml index 8ffa130b..8d81c2da 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: 2.0.1 +version: 2.0.2 homepage: "https://macosui.dev" repository: "https://github.com/GroovinChip/macos_ui" From 328f9cbf805b4365288ef1ee7304906239df8b9f Mon Sep 17 00:00:00 2001 From: GroovinChip Date: Thu, 12 Oct 2023 13:14:51 -0400 Subject: [PATCH 137/151] Run `pub get` --- example/pubspec.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/example/pubspec.lock b/example/pubspec.lock index f61168d0..253f08cb 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -166,7 +166,7 @@ packages: path: ".." relative: true source: path - version: "2.0.1" + version: "2.0.2" macos_window_utils: dependency: transitive description: From 5ec00bc17aadcb8391ba13c224a96a50fd82ef45 Mon Sep 17 00:00:00 2001 From: Abbas Hussein Date: Thu, 19 Oct 2023 22:23:50 +0300 Subject: [PATCH 138/151] Added initialTime parameter to MacosTimePicker --- .gitignore | 1 + CHANGELOG.md | 4 ++++ lib/src/selectors/time_picker.dart | 14 ++++++++++---- pubspec.yaml | 2 +- 4 files changed, 16 insertions(+), 5 deletions(-) diff --git a/.gitignore b/.gitignore index fcfa202c..c8887dcd 100644 --- a/.gitignore +++ b/.gitignore @@ -80,3 +80,4 @@ metrics coverage_report coverage +example/macos/Flutter/GeneratedPluginRegistrant.swift diff --git a/CHANGELOG.md b/CHANGELOG.md index fd70a111..36d3e5b2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## [2.0.3] +### 🔄 Updated 🔄 +* Added `initialTime` parameter to `MacosTimePicker`, allowing to set an initial time for the picker.This provides more customization options for selecting time. + ## [2.0.2] ### 🛠️ Fixed 🛠️ * Fixed images in generated documentation. diff --git a/lib/src/selectors/time_picker.dart b/lib/src/selectors/time_picker.dart index 815b23d5..47b4bb15 100644 --- a/lib/src/selectors/time_picker.dart +++ b/lib/src/selectors/time_picker.dart @@ -20,8 +20,8 @@ enum TimePickerStyle { /// {template onTimeChanged} /// The action to perform when a new time is selected. -/// {endtemplate} -typedef OnTimeChanged = Function(TimeOfDay time); +/// {end-template} +typedef OnTimeChanged = void Function(TimeOfDay time); /// {template macosTimePicker} /// A [MacosTimePicker] lets the user choose a time. @@ -36,15 +36,21 @@ typedef OnTimeChanged = Function(TimeOfDay time); /// /// The [onTimeChanged] callback passes through the user's selected time, and /// must be provided. -/// {endtemplate} +/// {end-template} class MacosTimePicker extends StatefulWidget { /// {@macro macosTimePicker} const MacosTimePicker({ super.key, required this.onTimeChanged, + this.initialTime, this.style = TimePickerStyle.combined, }); + /// Set an initial date for the picker. + /// + /// Defaults to `TimeOfDay.now()`. + final TimeOfDay? initialTime; + /// The [TimePickerStyle] to use. /// /// Defaults to [TimePickerStyle.combined]. @@ -58,7 +64,7 @@ class MacosTimePicker extends StatefulWidget { } class _MacosTimePickerState extends State { - final _initialTime = TimeOfDay.now(); + late final _initialTime = widget.initialTime ?? TimeOfDay.now(); late int _selectedHour; late int _selectedMinute; late DayPeriod _selectedPeriod; diff --git a/pubspec.yaml b/pubspec.yaml index 8d81c2da..983e4bed 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: 2.0.2 +version: 2.0.3 homepage: "https://macosui.dev" repository: "https://github.com/GroovinChip/macos_ui" From 908bb8b5e0d989ec1758107f7fda5211d4f74901 Mon Sep 17 00:00:00 2001 From: Abbas Hussein Date: Thu, 19 Oct 2023 22:24:07 +0300 Subject: [PATCH 139/151] Added a void return type to OnDateChanged --- lib/src/selectors/date_picker.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/selectors/date_picker.dart b/lib/src/selectors/date_picker.dart index 2b3cd9b0..25515a7d 100644 --- a/lib/src/selectors/date_picker.dart +++ b/lib/src/selectors/date_picker.dart @@ -21,7 +21,7 @@ enum DatePickerStyle { /// {template onDateChanged} /// The action to perform when a new date is selected. /// {endtemplate} -typedef OnDateChanged = Function(DateTime date); +typedef OnDateChanged = void Function(DateTime date); /// {template macosDatePicker} /// A [MacosDatePicker] lets the user choose a date. From 5e49ad66e1d4c77aadc605a91bfc79bdb178fe74 Mon Sep 17 00:00:00 2001 From: Adrian Samoticha <86920182+Adrian-Samoticha@users.noreply.github.com> Date: Mon, 30 Oct 2023 12:42:32 +0100 Subject: [PATCH 140/151] automatic changes --- pubspec.lock | 20 ++++++-------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/pubspec.lock b/pubspec.lock index 62a3f534..b2e287b4 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -223,18 +223,18 @@ packages: dependency: transitive description: name: matcher - sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e" + sha256: "6501fbd55da300384b768785b83e5ce66991266cec21af89ab9ae7f5ce1c4cbb" url: "https://pub.dev" source: hosted - version: "0.12.16" + version: "0.12.15" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41" + sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724 url: "https://pub.dev" source: hosted - version: "0.5.0" + version: "0.2.0" meta: dependency: transitive description: @@ -364,10 +364,10 @@ packages: dependency: transitive description: name: source_span - sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" + sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250 url: "https://pub.dev" source: hosted - version: "1.10.0" + version: "1.9.1" stack_trace: dependency: transitive description: @@ -456,14 +456,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.0" - web: - dependency: transitive - description: - name: web - sha256: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10 - url: "https://pub.dev" - source: hosted - version: "0.1.4-beta" web_socket_channel: dependency: transitive description: From 1f563c0498ba37e030288de9ddcc5f41dd94d782 Mon Sep 17 00:00:00 2001 From: Adrian Samoticha <86920182+Adrian-Samoticha@users.noreply.github.com> Date: Mon, 30 Oct 2023 13:01:59 +0100 Subject: [PATCH 141/151] bump version to 2.0.3 --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index 8d81c2da..983e4bed 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: 2.0.2 +version: 2.0.3 homepage: "https://macosui.dev" repository: "https://github.com/GroovinChip/macos_ui" From ec4a63b223d6511b57ebef550da33410806de680 Mon Sep 17 00:00:00 2001 From: Adrian Samoticha <86920182+Adrian-Samoticha@users.noreply.github.com> Date: Wed, 17 Jan 2024 17:18:21 +0100 Subject: [PATCH 142/151] Fix incorrect sidebar and sidebar item color (#484) --- CHANGELOG.md | 9 +- .../macos/Runner.xcodeproj/project.pbxproj | 2 +- .../xcshareddata/xcschemes/Runner.xcscheme | 2 +- lib/src/layout/sidebar/sidebar_item.dart | 2 +- lib/src/layout/sidebar/sidebar_items.dart | 163 ++++++++++++--- lib/src/layout/window.dart | 186 ++++++++++-------- pubspec.lock | 20 +- pubspec.yaml | 2 +- 8 files changed, 251 insertions(+), 135 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fd70a111..166aed08 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## [2.0.3] +### 🛠️ Fixed 🛠️ +* Fixed a bug that caused the sidebar to appear darker than intended. + +### 🔄 Updated 🔄 +* `SidebarItems` has now respects the user’s selected accent color and mimics the look of macOS’ sidebar items more closely. + ## [2.0.2] ### 🛠️ Fixed 🛠️ * Fixed images in generated documentation. @@ -5,7 +12,7 @@ ## [2.0.1] ### 🔄 Updated 🔄 * `PushButton` has received a facelift. It now mimics the look and feel of native macOS buttons more closely. - * **Note:** As a result, its `pressedOpacity` property and the `PushButtonTheme` class have been deprecated. + * **Note:** As a result, its `pressedOpacity` property and the `PushButtonTheme` class have been deprecated. ## [2.0.0] ### 🚨 Breaking Changes 🚨 diff --git a/example/macos/Runner.xcodeproj/project.pbxproj b/example/macos/Runner.xcodeproj/project.pbxproj index 2c562a4e..428da703 100644 --- a/example/macos/Runner.xcodeproj/project.pbxproj +++ b/example/macos/Runner.xcodeproj/project.pbxproj @@ -202,7 +202,7 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0920; - LastUpgradeCheck = 1300; + LastUpgradeCheck = 1430; ORGANIZATIONNAME = ""; TargetAttributes = { 33CC10EC2044A3C60003C045 = { diff --git a/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index fb7259e1..83d88728 100644 --- a/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ + AccentColorListener.instance.currentAccentColor ?? AccentColor.blue; + + /// Returns the sidebar item’s selected color. + Color _getColor(BuildContext context) { + final isMainWindow = WindowMainStateListener.instance.isMainWindow; + + return _ColorProvider.getSelectedColor( + accentColor: _accentColor, + isDarkModeEnabled: MacosTheme.of(context).brightness.isDark, + isWindowMain: isMainWindow, + ); + } + List get _allItems { List result = []; for (var element in items) { @@ -117,39 +134,50 @@ class SidebarItems extends StatelessWidget { final theme = MacosTheme.of(context); return MacosIconTheme.merge( data: const MacosIconThemeData(size: 20), - child: _SidebarItemsConfiguration( - selectedColor: selectedColor ?? theme.primaryColor, - unselectedColor: unselectedColor ?? MacosColors.transparent, - shape: shape ?? _defaultShape, - itemSize: itemSize, - child: ListView( - controller: scrollController, - physics: const ClampingScrollPhysics(), - padding: EdgeInsets.all(10.0 - theme.visualDensity.horizontal), - children: List.generate(items.length, (index) { - final item = items[index]; - if (item.disclosureItems != null) { - return MouseRegion( - cursor: cursor!, - child: _DisclosureSidebarItem( - item: item, - selectedItem: _allItems[currentIndex], - onChanged: (item) { - onChanged(_allItems.indexOf(item)); - }, + child: StreamBuilder( + stream: AccentColorListener.instance.onChanged, + builder: (context, _) { + return StreamBuilder( + stream: WindowMainStateListener.instance.onChanged, + builder: (context, _) { + return _SidebarItemsConfiguration( + selectedColor: selectedColor ?? _getColor(context), + unselectedColor: unselectedColor ?? MacosColors.transparent, + shape: shape ?? _defaultShape, + itemSize: itemSize, + child: ListView( + controller: scrollController, + physics: const ClampingScrollPhysics(), + padding: + EdgeInsets.all(10.0 - theme.visualDensity.horizontal), + children: List.generate(items.length, (index) { + final item = items[index]; + if (item.disclosureItems != null) { + return MouseRegion( + cursor: cursor!, + child: _DisclosureSidebarItem( + item: item, + selectedItem: _allItems[currentIndex], + onChanged: (item) { + onChanged(_allItems.indexOf(item)); + }, + ), + ); + } + return MouseRegion( + cursor: cursor!, + child: _SidebarItem( + item: item, + selected: _allItems[currentIndex] == item, + onClick: () => onChanged(_allItems.indexOf(item)), + ), + ); + }), ), ); - } - return MouseRegion( - cursor: cursor!, - child: _SidebarItem( - item: item, - selected: _allItems[currentIndex] == item, - onClick: () => onChanged(_allItems.indexOf(item)), - ), - ); - }), - ), + }, + ); + }, ), ); } @@ -497,3 +525,74 @@ class __DisclosureSidebarItemState extends State<_DisclosureSidebarItem> ); } } + +class _ColorProvider { + /// Returns the selected color based on the provided parameters. + static Color getSelectedColor({ + required AccentColor accentColor, + required bool isDarkModeEnabled, + required bool isWindowMain, + }) { + if (isDarkModeEnabled) { + if (!isWindowMain) { + return const MacosColor.fromRGBO(76, 78, 65, 1.0); + } + + switch (accentColor) { + case AccentColor.blue: + return const MacosColor.fromRGBO(22, 105, 229, 0.749); + + case AccentColor.purple: + return const MacosColor.fromRGBO(204, 45, 202, 0.749); + + case AccentColor.pink: + return const MacosColor.fromRGBO(229, 74, 145, 0.749); + + case AccentColor.red: + return const MacosColor.fromRGBO(238, 64, 68, 0.749); + + case AccentColor.orange: + return const MacosColor.fromRGBO(244, 114, 0, 0.749); + + case AccentColor.yellow: + return const MacosColor.fromRGBO(233, 176, 0, 0.749); + + case AccentColor.green: + return const MacosColor.fromRGBO(76, 177, 45, 0.749); + + case AccentColor.graphite: + return const MacosColor.fromRGBO(129, 129, 122, 0.824); + } + } + + if (!isWindowMain) { + return const MacosColor.fromRGBO(213, 213, 208, 1.0); + } + + switch (accentColor) { + case AccentColor.blue: + return const MacosColor.fromRGBO(9, 129, 255, 0.749); + + case AccentColor.purple: + return const MacosColor.fromRGBO(162, 28, 165, 0.749); + + case AccentColor.pink: + return const MacosColor.fromRGBO(234, 81, 152, 0.749); + + case AccentColor.red: + return const MacosColor.fromRGBO(220, 32, 40, 0.749); + + case AccentColor.orange: + return const MacosColor.fromRGBO(245, 113, 0, 0.749); + + case AccentColor.yellow: + return const MacosColor.fromRGBO(240, 180, 2, 0.749); + + case AccentColor.green: + return const MacosColor.fromRGBO(66, 174, 33, 0.749); + + case AccentColor.graphite: + return const MacosColor.fromRGBO(174, 174, 167, 0.847); + } + } +} diff --git a/lib/src/layout/window.dart b/lib/src/layout/window.dart index 85c20417..d28511c0 100644 --- a/lib/src/layout/window.dart +++ b/lib/src/layout/window.dart @@ -265,95 +265,113 @@ class _MacosWindowState extends State { minHeight: height, maxHeight: height, ).normalize(), - 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, + 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 (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!, + if (sidebar.bottom != null && + constraints.maxHeight > 141) + Padding( + padding: const EdgeInsets.all(16.0), + child: sidebar.bottom!, + ), + ], ), - Expanded( - child: MacosScrollbar( - controller: _sidebarScrollController, - child: Padding( - padding: sidebar.padding, - child: sidebar.builder( - context, - _sidebarScrollController, - ), + ) + : TransparentMacOSSidebar( + state: sidebarState, + child: DecoratedBox( + decoration: const BoxDecoration( + color: Color.fromRGBO(0, 0, 0, 1.0), + backgroundBlendMode: BlendMode.clear, + ), + 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!, + ), + ], ), ), ), - if (sidebar.bottom != null && - constraints.maxHeight > 141) - Padding( - padding: const EdgeInsets.all(16.0), - child: sidebar.bottom!, - ), - ], - ), - ), ), ), diff --git a/pubspec.lock b/pubspec.lock index 62a3f534..b2e287b4 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -223,18 +223,18 @@ packages: dependency: transitive description: name: matcher - sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e" + sha256: "6501fbd55da300384b768785b83e5ce66991266cec21af89ab9ae7f5ce1c4cbb" url: "https://pub.dev" source: hosted - version: "0.12.16" + version: "0.12.15" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41" + sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724 url: "https://pub.dev" source: hosted - version: "0.5.0" + version: "0.2.0" meta: dependency: transitive description: @@ -364,10 +364,10 @@ packages: dependency: transitive description: name: source_span - sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" + sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250 url: "https://pub.dev" source: hosted - version: "1.10.0" + version: "1.9.1" stack_trace: dependency: transitive description: @@ -456,14 +456,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.0" - web: - dependency: transitive - description: - name: web - sha256: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10 - url: "https://pub.dev" - source: hosted - version: "0.1.4-beta" web_socket_channel: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 8d81c2da..983e4bed 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: 2.0.2 +version: 2.0.3 homepage: "https://macosui.dev" repository: "https://github.com/GroovinChip/macos_ui" From 77f92462c807b1bcff2729e6bd5cf0cf33080291 Mon Sep 17 00:00:00 2001 From: Adrian Samoticha <86920182+Adrian-Samoticha@users.noreply.github.com> Date: Sun, 21 Jan 2024 16:11:04 +0100 Subject: [PATCH 143/151] increment version to 2.0.4 --- example/macos/Podfile.lock | 2 +- pubspec.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/example/macos/Podfile.lock b/example/macos/Podfile.lock index 6aa1d86e..92146f31 100644 --- a/example/macos/Podfile.lock +++ b/example/macos/Podfile.lock @@ -44,4 +44,4 @@ SPEC CHECKSUMS: PODFILE CHECKSUM: ff0a9a3ce75ee73f200ca7e2f47745698c917ef9 -COCOAPODS: 1.12.1 +COCOAPODS: 1.13.0 diff --git a/pubspec.yaml b/pubspec.yaml index 983e4bed..d2273afb 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: 2.0.3 +version: 2.0.4 homepage: "https://macosui.dev" repository: "https://github.com/GroovinChip/macos_ui" From e2ae2c4da8a6e9cd3d5a5b00de0c35468fda9fcd Mon Sep 17 00:00:00 2001 From: Adrian Samoticha <86920182+Adrian-Samoticha@users.noreply.github.com> Date: Sun, 21 Jan 2024 19:52:15 +0100 Subject: [PATCH 144/151] increment version to 2.0.5 --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index d2273afb..0cfba245 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: 2.0.4 +version: 2.0.5 homepage: "https://macosui.dev" repository: "https://github.com/GroovinChip/macos_ui" From e9da375ec75cf6d484026fe527d0c5ee82a336be Mon Sep 17 00:00:00 2001 From: Abbas Hussein <112737126+Abbas1Hussein@users.noreply.github.com> Date: Sun, 21 Jan 2024 21:53:34 +0300 Subject: [PATCH 145/151] Fix radio button issue (#497) * Update radio_button.dart * Update CHANGELOG.md --------- Co-authored-by: Adrian Samoticha <86920182+Adrian-Samoticha@users.noreply.github.com> --- CHANGELOG.md | 164 +++++++++++++++--------------- lib/src/buttons/radio_button.dart | 2 +- 2 files changed, 85 insertions(+), 81 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e018ca0a..3c9e7321 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## [2.0.5] +### 🛠️ Fixed 🛠️ +* Fixed `MacosRadioButton` check null value issue. + ## [2.0.4] ### 🔄 Updated 🔄 * Added `initialTime` parameter to `MacosTimePicker`, allowing to set an initial time for the picker.This provides more customization options for selecting time. @@ -16,21 +20,21 @@ ## [2.0.1] ### 🔄 Updated 🔄 * `PushButton` has received a facelift. It now mimics the look and feel of native macOS buttons more closely. - * **Note:** As a result, its `pressedOpacity` property and the `PushButtonTheme` class have been deprecated. + * **Note:** As a result, its `pressedOpacity` property and the `PushButtonTheme` class have been deprecated. ## [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. + * 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. + * 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 @@ -41,22 +45,22 @@ * `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`. + * 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`. + * 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` + * 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. + * `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 @@ -96,9 +100,9 @@ ## [1.11.0] * 🚨 Breaking Changes 🚨 * `ResizablePane` can now be vertically resized - * `ResizablePane.startWidth` has been changed to `ResizablePane.startSize` - * `ResizablePane.minWidth` has been changed to `ResizablePane.minSize` - * `ResizablePane.maxWidth` has been changed to `ResizablePane.maxSize` + * `ResizablePane.startWidth` has been changed to `ResizablePane.startSize` + * `ResizablePane.minWidth` has been changed to `ResizablePane.minSize` + * `ResizablePane.maxWidth` has been changed to `ResizablePane.maxSize` ## [1.10.0] 🚨 Breaking Changes 🚨 @@ -121,8 +125,8 @@ Other changes: ## [1.8.0] 🚨 Breaking Changes 🚨 * `ContentArea.builder` has been changed from a `ScrollableWidgetBuilder` to a `WidgetBuilder` due to -changes in Flutter 3.7. The `MacosScrollbar` widget needs to undergo radical changes in order to achieve the -native macOS scrollbar look and feel in the future, so this will be revisited at that time. + changes in Flutter 3.7. The `MacosScrollbar` widget needs to undergo radical changes in order to achieve the + native macOS scrollbar look and feel in the future, so this will be revisited at that time. Other changes: * Per Flutter 3.7.0: Replace deprecated `MacosTextField.toolbarOptions` with `MacosTextField.contextMenuBuilder` @@ -151,21 +155,21 @@ Other changes: ## [1.7.0] * ✨ New - * `MacosImageIcon` widget. Identical to the `ImageIcon` from `flutter/widgets.dart` except it will obey a -`MacosIconThemeData` instead of an `IconThemeData` - * `SidebarItemSize` enum, which determines the height of sidebar items and the maximum size their `leading` widgets. - * `SidebarItem` now accepts an optional `trailing` widget. + * `MacosImageIcon` widget. Identical to the `ImageIcon` from `flutter/widgets.dart` except it will obey a + `MacosIconThemeData` instead of an `IconThemeData` + * `SidebarItemSize` enum, which determines the height of sidebar items and the maximum size their `leading` widgets. + * `SidebarItem` now accepts an optional `trailing` widget. * 🔄 Updated - * `SidebarItems` now supports `SidebarItemSize` via the `itemSize` property, which defaults to -`SidebarItemSize.medium`. The widget has been updated to manage the item's height, the maximum size of the item's -leading widget, and the font size of the item's label widget according to the given `SidebarItemSize`. - * The example app has been tweaked to use some icons from the SF Symbols 4 Beta via the new `MacosImageIcon` widget. + * `SidebarItems` now supports `SidebarItemSize` via the `itemSize` property, which defaults to + `SidebarItemSize.medium`. The widget has been updated to manage the item's height, the maximum size of the item's + leading widget, and the font size of the item's label widget according to the given `SidebarItemSize`. + * The example app has been tweaked to use some icons from the SF Symbols 4 Beta via the new `MacosImageIcon` widget. ## [1.6.0] * New widgets: `MacosTabView` and `MacosTabView` * BREAKING CHANGE: `Label.yAxis` has been renamed to `Label.crossAxisAlignment` * BREAKING CHANGE: `TooltipTheme` and `TooltipThemeData` have been renamed to `MacosTooltipTheme` and -`MacosTooltipThemeData` + `MacosTooltipThemeData` ## [1.5.1] * Correct the placement of the leading widget in disclosure sidebar items [#268](https://github.com/GroovinChip/macos_ui/issues/268) @@ -185,15 +189,15 @@ leading widget, and the font size of the item's label widget according to the gi ## [1.4.0] * Migration to Flutter 3.0 - * Minimum dart sdk version is now 2.17.0 - * Use new super parameters feature - * Update to `flutter_lints: ^2.0.1` with subsequent fixes - * `MacosScrollbar` API more closely matches its material counterpart + * Minimum dart sdk version is now 2.17.0 + * Use new super parameters feature + * Update to `flutter_lints: ^2.0.1` with subsequent fixes + * `MacosScrollbar` API more closely matches its material counterpart * Update `MacosColor` to more closely match the `Color` class - * Adds `MacosColor.fromARGB` constructor - * Adds `MacosColor.fromRGBO` constructor - * Adds `alphaBlend` function - * Adds `getAlphaFromOpacity` function + * Adds `MacosColor.fromARGB` constructor + * Adds `MacosColor.fromRGBO` constructor + * Adds `alphaBlend` function + * Adds `getAlphaFromOpacity` function ## [1.3.0] * Add a `top` property to `Sidebar` @@ -207,8 +211,8 @@ leading widget, and the font size of the item's label widget according to the gi ## [1.2.0] * Improved styling for `MacosTooltip`: - * Better color and shadows. - * Displays left-aligned, below the mouse cursor. + * Better color and shadows. + * Displays left-aligned, below the mouse cursor. * New widget: `ToolBarDivider` that can be used as a divider (vertical/horizontal line) in the `ToolBar` [#231](https://github.com/GroovinChip/macos_ui/issues/231). * All toolbar widgets can now receive a `tooltipMessage` property to display a `MacosTooltip` when user hovers over them [#232](https://github.com/GroovinChip/macos_ui/issues/232). @@ -217,17 +221,17 @@ leading widget, and the font size of the item's label widget according to the gi ## [1.1.0] * New functionality for `MacosSearchField` - * Shows a list of search results in an overlay below the field - * A result can be selected and customized. + * Shows a list of search results in an overlay below the field + * A result can be selected and customized. * A `MacosOverlayFilter` widget can now be used to apply the blurry "frosted glass" effect on surfaces. * New widget: `CustomToolbarItem` that enables any widget to be used in the `Toolbar`. ## [1.0.1] * Improvements to the graphical `MacosTimePicker` - * Better color gradient on the border - * Better inner shadow - * Minor size adjustments - * API improvements + * Better color gradient on the border + * Better inner shadow + * Minor size adjustments + * API improvements * Throw an exception if `MacosColorWell` is clicked on a non-macOS platform ## [1.0.0+1] @@ -299,9 +303,9 @@ leading widget, and the font size of the item's label widget according to the gi ## [0.10.2] * Updates to `MacosIconButton` and `MacosBackButton`: - * Added a hover effect when mouse moves over the buttons ([#168](https://github.com/GroovinChip/macos_ui/issues/168)) - * Added `hoverColor` property. - * Default shape is now `BoxShape.rectangle` with border radius, as it seems to be the most used in macOS design. + * Added a hover effect when mouse moves over the buttons ([#168](https://github.com/GroovinChip/macos_ui/issues/168)) + * Added `hoverColor` property. + * Default shape is now `BoxShape.rectangle` with border radius, as it seems to be the most used in macOS design. ## [0.10.1] * Added support for transparent sidebar. Please note that changes to `MainFlutterWindow.swift` are required for this to work. [(#175)](https://github.com/GroovinChip/macos_ui/pull/175) @@ -315,7 +319,7 @@ leading widget, and the font size of the item's label widget according to the gi ## [0.9.3] * Update to `PushButton`: - * Added `isSecondary` property + * Added `isSecondary` property ## [0.9.2] * Nearly all `MouseRegion`s have been updated to use `SystemMouseCursors.basic` in order to more closely adhere to Apple norms @@ -323,7 +327,7 @@ leading widget, and the font size of the item's label widget according to the gi ## [0.9.1] * Added top-level theming for `MacosIconButton` - * Introduces the `MacosIconButtonTheme` InheritedTheme and the `MacosIconButtonThemeData` theme class + * Introduces the `MacosIconButtonTheme` InheritedTheme and the `MacosIconButtonThemeData` theme class * Updates `MacosThemeData` and `MacosIconButton` to use the new `MacosIconButtonThemeData` * Removes an unnecessary setting of VisualDensity from `MacosThemeData.dark()` @@ -332,10 +336,10 @@ leading widget, and the font size of the item's label widget according to the gi ## [0.8.2] * Updates to `MacosListTile`: - * Added `leadingWhitespace` property - * Added `onClick` callback - * Added `onLongPress` callback - * Added `mouseCursor` property + * Added `leadingWhitespace` property + * Added `onClick` callback + * Added `onLongPress` callback + * Added `mouseCursor` property ## [0.8.1] * Fix the outer border of `MacosSheet` not having a border radius @@ -397,16 +401,16 @@ leading widget, and the font size of the item's label widget according to the gi ## [0.3.0] * Add `MacosPrefix` to widgets/classes with names that overlap with the material/cupertino libraries: - * `TextField` -> `MacosTextField` - * `Scaffold` -> `MacosTextField` - * `IconButton` -> `MacosIconButton` - * `BackButton` -> `MacosBackButton` - * `Scrollbar` -> `MacosScrollbar` - * `Checkbox` -> `MacosCheckbox` - * `RadioButton` -> `MacosRadioButton` - * `Tooltip` -> `MacosTooltip` - * `Typography` -> `MacosTypography` - * `Switch` -> `MacosSwitch` + * `TextField` -> `MacosTextField` + * `Scaffold` -> `MacosTextField` + * `IconButton` -> `MacosIconButton` + * `BackButton` -> `MacosBackButton` + * `Scrollbar` -> `MacosScrollbar` + * `Checkbox` -> `MacosCheckbox` + * `RadioButton` -> `MacosRadioButton` + * `Tooltip` -> `MacosTooltip` + * `Typography` -> `MacosTypography` + * `Switch` -> `MacosSwitch` ## [0.2.4] * Fix text field prefix icon alignment @@ -420,9 +424,9 @@ leading widget, and the font size of the item's label widget according to the gi ## [0.2.1] * `IconButton` updates: - - The `color` property is now `backgroundColor` - - The widget now takes a `Widget icon` rather than `IconData iconData` for better control over widget properties - - Deprecate and remove internal `foregroundColor` value + - The `color` property is now `backgroundColor` + - The widget now takes a `Widget icon` rather than `IconData iconData` for better control over widget properties + - Deprecate and remove internal `foregroundColor` value ## [0.2.0] * New widget: `BackButton`, `IconButton` @@ -439,9 +443,9 @@ leading widget, and the font size of the item's label widget according to the gi ## [0.1.2] * Updated the theme api - * Properties in `MacosThemeData` and in `Typography` can't be null - * Renamed `DynamicColorX` to `MacosDynamicColor` - * Added the method `lerp` on all theme data classes. + * Properties in `MacosThemeData` and in `Typography` can't be null + * Renamed `DynamicColorX` to `MacosDynamicColor` + * Added the method `lerp` on all theme data classes. ## [0.1.1] * Implemented `Label` ([#61](https://github.com/GroovinChip/macos_ui/issues/61)) @@ -488,12 +492,12 @@ leading widget, and the font size of the item's label widget according to the gi ## [0.0.4] * Major theme refactor that more closely resembles flutter/material and flutter/cupertino - * The `Style` class is now `MacosThemeData` - * `MacosTheme` is now a `StatelessWidget` that returns a private `_InheritedMacosTheme`. - The static `MacosTheme.of(context)` is now defined here. - * `MacosApp` now takes a `theme` and `darkTheme` rather than `style` and `darkStyle`. - Additionally, there are minor changes to the way `MacosApp` is built that more closely - resemble how `MaterialApp` is built. + * The `Style` class is now `MacosThemeData` + * `MacosTheme` is now a `StatelessWidget` that returns a private `_InheritedMacosTheme`. + The static `MacosTheme.of(context)` is now defined here. + * `MacosApp` now takes a `theme` and `darkTheme` rather than `style` and `darkStyle`. + Additionally, there are minor changes to the way `MacosApp` is built that more closely + resemble how `MaterialApp` is built. ## [0.0.3] @@ -509,6 +513,6 @@ leading widget, and the font size of the item's label widget according to the gi ## [0.0.1] * Project creation - * `MacosApp` widget - * Basic `Typography` - * Basic theming via `MacosTheme` and `Style` + * `MacosApp` widget + * Basic `Typography` + * Basic theming via `MacosTheme` and `Style` diff --git a/lib/src/buttons/radio_button.dart b/lib/src/buttons/radio_button.dart index 57097758..eda3de26 100644 --- a/lib/src/buttons/radio_button.dart +++ b/lib/src/buttons/radio_button.dart @@ -91,7 +91,7 @@ class MacosRadioButton extends StatelessWidget { final MacosThemeData theme = MacosTheme.of(context); final isLight = !theme.brightness.isDark; return GestureDetector( - onTap: () => onChanged!(value), + onTap: isDisabled ? null : () => onChanged!(value), child: Semantics( checked: selected, label: semanticLabel, From 37c95be61dc02f4478b74bdf910d8b8319c04420 Mon Sep 17 00:00:00 2001 From: Adrian Samoticha <86920182+Adrian-Samoticha@users.noreply.github.com> Date: Fri, 3 May 2024 17:40:03 +0200 Subject: [PATCH 146/151] Issue #505 (#509) * Update radio_button.dart * Update CHANGELOG.md * automatic changes * install equatable * make `MacosTypography` extend `Equatable` * make `MacosThemeData` extend `Equatable` * bump version to 2.0.6 * add changelog entry for version 2.0.6 * automatic changes * organize imports * automatic changes * migrate to `onKeyEvent` in three cases * automatic changes --------- Co-authored-by: Abbas Hussein --- CHANGELOG.md | 4 + example/macos/Podfile.lock | 2 +- .../macos/Runner.xcodeproj/project.pbxproj | 2 +- .../xcshareddata/xcschemes/Runner.xcscheme | 2 +- example/pubspec.lock | 190 ++++++++++-------- lib/src/buttons/popup_button.dart | 2 +- lib/src/buttons/pulldown_button.dart | 2 +- .../toolbar/toolbar_overflow_menu_item.dart | 2 +- lib/src/theme/macos_theme.dart | 24 ++- lib/src/theme/typography.dart | 18 +- pubspec.lock | 140 ++++++++----- pubspec.yaml | 3 +- test/src/theme/macos_theme_test.dart | 49 +++++ test/src/theme/typography_test.dart | 82 ++++++++ 14 files changed, 372 insertions(+), 150 deletions(-) create mode 100644 test/src/theme/macos_theme_test.dart create mode 100644 test/src/theme/typography_test.dart diff --git a/CHANGELOG.md b/CHANGELOG.md index 3c9e7321..d8cacfe3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## [2.0.6] +### 🛠️ Updated 🛠️ +* Implemented value equality for `MacosThemeData`. + ## [2.0.5] ### 🛠️ Fixed 🛠️ * Fixed `MacosRadioButton` check null value issue. diff --git a/example/macos/Podfile.lock b/example/macos/Podfile.lock index 92146f31..2f92e758 100644 --- a/example/macos/Podfile.lock +++ b/example/macos/Podfile.lock @@ -39,7 +39,7 @@ SPEC CHECKSUMS: FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24 macos_ui: 6229a8922cd97bafb7d9636c8eb8dfb0744183ca macos_window_utils: 933f91f64805e2eb91a5bd057cf97cd097276663 - path_provider_foundation: eaf5b3e458fc0e5fbb9940fb09980e853fe058b8 + path_provider_foundation: 3784922295ac71e43754bd15e0653ccfd36a147c url_launcher_macos: d2691c7dd33ed713bf3544850a623080ec693d95 PODFILE CHECKSUM: ff0a9a3ce75ee73f200ca7e2f47745698c917ef9 diff --git a/example/macos/Runner.xcodeproj/project.pbxproj b/example/macos/Runner.xcodeproj/project.pbxproj index 428da703..717c51d9 100644 --- a/example/macos/Runner.xcodeproj/project.pbxproj +++ b/example/macos/Runner.xcodeproj/project.pbxproj @@ -202,7 +202,7 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0920; - LastUpgradeCheck = 1430; + LastUpgradeCheck = 1510; ORGANIZATIONNAME = ""; TargetAttributes = { 33CC10EC2044A3C60003C045 = { diff --git a/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index 83d88728..5b055a3a 100644 --- a/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ =3.1.0-185.0.dev <4.0.0" - flutter: ">=3.10.0" + dart: ">=3.3.0 <4.0.0" + flutter: ">=3.19.0" diff --git a/lib/src/buttons/popup_button.dart b/lib/src/buttons/popup_button.dart index 37c6559b..8082f78c 100644 --- a/lib/src/buttons/popup_button.dart +++ b/lib/src/buttons/popup_button.dart @@ -99,7 +99,7 @@ class _MacosPopupMenuItemButtonState child: GestureDetector( onTap: _handleOnTap, child: Focus( - onKey: (FocusNode node, RawKeyEvent event) { + onKeyEvent: (FocusNode node, KeyEvent event) { if (event.logicalKey == LogicalKeyboardKey.enter) { _handleOnTap(); return KeyEventResult.handled; diff --git a/lib/src/buttons/pulldown_button.dart b/lib/src/buttons/pulldown_button.dart index 91de97a7..37c0e7d1 100644 --- a/lib/src/buttons/pulldown_button.dart +++ b/lib/src/buttons/pulldown_button.dart @@ -94,7 +94,7 @@ class _MacosPulldownMenuItemButtonState child: GestureDetector( onTap: _handleOnTap, child: Focus( - onKey: (FocusNode node, RawKeyEvent event) { + onKeyEvent: (FocusNode node, KeyEvent event) { if (event.logicalKey == LogicalKeyboardKey.enter) { _handleOnTap(); return KeyEventResult.handled; diff --git a/lib/src/layout/toolbar/toolbar_overflow_menu_item.dart b/lib/src/layout/toolbar/toolbar_overflow_menu_item.dart index e92c57c2..952420f9 100644 --- a/lib/src/layout/toolbar/toolbar_overflow_menu_item.dart +++ b/lib/src/layout/toolbar/toolbar_overflow_menu_item.dart @@ -89,7 +89,7 @@ class _ToolbarOverflowMenuItemState extends State { child: GestureDetector( onTap: _handleOnTap, child: Focus( - onKey: (FocusNode node, RawKeyEvent event) { + onKeyEvent: (FocusNode node, KeyEvent event) { if (event.logicalKey == LogicalKeyboardKey.enter) { _handleOnTap(); return KeyEventResult.handled; diff --git a/lib/src/theme/macos_theme.dart b/lib/src/theme/macos_theme.dart index 0bda4d11..59776de6 100644 --- a/lib/src/theme/macos_theme.dart +++ b/lib/src/theme/macos_theme.dart @@ -1,3 +1,4 @@ +import 'package:equatable/equatable.dart'; import 'package:flutter/foundation.dart'; import 'package:macos_ui/macos_ui.dart'; import 'package:macos_ui/src/library.dart'; @@ -178,7 +179,7 @@ class _InheritedMacosTheme extends InheritedWidget { /// See also: /// /// * [MacosTheme], in which this [MacosThemeData] is inserted. -class MacosThemeData with Diagnosticable { +class MacosThemeData extends Equatable with Diagnosticable { /// Creates a [MacosThemeData] that's used to configure [MacosTheme]. /// /// The [typography] [TextStyle] colors are black if the [brightness] @@ -682,6 +683,27 @@ class MacosThemeData with Diagnosticable { ), ); } + + @override + List get props => [ + brightness, + primaryColor, + canvasColor, + typography, + pushButtonTheme, + dividerColor, + helpButtonTheme, + tooltipTheme, + visualDensity, + scrollbarTheme, + iconButtonTheme, + iconTheme, + popupButtonTheme, + pulldownButtonTheme, + datePickerTheme, + timePickerTheme, + searchFieldTheme, + ]; } /// Brightness extensions diff --git a/lib/src/theme/typography.dart b/lib/src/theme/typography.dart index 7b2f72a7..462e332e 100644 --- a/lib/src/theme/typography.dart +++ b/lib/src/theme/typography.dart @@ -1,3 +1,4 @@ +import 'package:equatable/equatable.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/foundation.dart'; import 'package:macos_ui/src/theme/macos_colors.dart'; @@ -15,7 +16,7 @@ const _kDefaultFontFamily = '.AppleSystemUIFont'; /// * [MacosTheme], for aspects of a macos application that can be globally /// adjusted, such as the primary color. /// * -class MacosTypography with Diagnosticable { +class MacosTypography extends Equatable with Diagnosticable { /// Creates a typography that uses the given values. /// /// Rather than creating a new typography, consider using [MacosTypography.darkOpaque] @@ -297,6 +298,21 @@ class MacosTypography with Diagnosticable { defaultValue: defaultStyle.caption2, )); } + + @override + List get props => [ + body, + callout, + caption1, + caption2, + footnote, + headline, + largeTitle, + subheadline, + title1, + title2, + title3, + ]; } /// The thickness of the glyphs used to draw the text. diff --git a/pubspec.lock b/pubspec.lock index b2e287b4..40925f5d 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -5,18 +5,18 @@ packages: dependency: transitive description: name: _fe_analyzer_shared - sha256: ae92f5d747aee634b87f89d9946000c2de774be1d6ac3e58268224348cd0101a + sha256: "0b2f2bd91ba804e53a61d757b986f89f1f9eaed5b11e4b2f5a2468d86d6c9fc7" url: "https://pub.dev" source: hosted - version: "61.0.0" + version: "67.0.0" analyzer: dependency: transitive description: name: analyzer - sha256: ea3d8652bda62982addfd92fdc2d0214e5f82e43325104990d4f4c4a2a313562 + sha256: "37577842a27e4338429a1cbc32679d508836510b056f1eedf0c8d20e39c1383d" url: "https://pub.dev" source: hosted - version: "5.13.0" + version: "6.4.1" appkit_ui_element_colors: dependency: "direct main" description: @@ -29,10 +29,10 @@ packages: dependency: transitive description: name: args - sha256: c372bb384f273f0c2a8aaaa226dad84dc27c8519a691b888725dec59518ad53a + sha256: "7cf60b9f0cc88203c5a190b4cd62a99feea42759a7fa695010eb5de1c0b2252a" url: "https://pub.dev" source: hosted - version: "2.4.1" + version: "2.5.0" async: dependency: transitive description: @@ -69,10 +69,10 @@ packages: dependency: transitive description: name: collection - sha256: f092b211a4319e98e5ff58223576de6c2803db36221657b46c82574721240687 + sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a url: "https://pub.dev" source: hosted - version: "1.17.2" + version: "1.18.0" convert: dependency: transitive description: @@ -85,10 +85,10 @@ packages: dependency: transitive description: name: coverage - sha256: "2fb815080e44a09b85e0f2ca8a820b15053982b2e714b59267719e8a9ff17097" + sha256: "8acabb8306b57a409bf4c83522065672ee13179297a6bb0cb9ead73948df7c76" url: "https://pub.dev" source: hosted - version: "1.6.3" + version: "1.7.2" crypto: dependency: transitive description: @@ -98,7 +98,7 @@ packages: source: hosted version: "3.0.3" equatable: - dependency: transitive + dependency: "direct main" description: name: equatable sha256: c2b87cb7756efdf69892005af546c56c0b5037f54d2a88269b4f347a505e3ca2 @@ -117,10 +117,10 @@ packages: dependency: transitive description: name: file - sha256: "1b92bec4fc2a72f59a8e15af5f52cd441e4a7860b49499d69dfa817af20e925d" + sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c" url: "https://pub.dev" source: hosted - version: "6.1.4" + version: "7.0.0" flutter: dependency: "direct main" description: flutter @@ -130,10 +130,10 @@ packages: dependency: "direct dev" description: name: flutter_lints - sha256: "2118df84ef0c3ca93f96123a616ae8540879991b8b57af2f81b76a7ada49b2a4" + sha256: a25a15ebbdfc33ab1cd26c63a6ee519df92338a9c10f122adda92938253bef04 url: "https://pub.dev" source: hosted - version: "2.0.2" + version: "2.0.3" flutter_test: dependency: "direct dev" description: flutter @@ -151,10 +151,10 @@ packages: dependency: transitive description: name: glob - sha256: "4515b5b6ddb505ebdd242a5f2cc5d22d3d6a80013789debfbda7777f47ea308c" + sha256: "0e7014b3b7d4dac1ca4d6114f82bf1782ee86745b9b42a92c9289c23d8a0ab63" url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.2" gradient_borders: dependency: "direct main" description: @@ -195,62 +195,86 @@ packages: url: "https://pub.dev" source: hosted version: "0.6.7" + leak_tracker: + dependency: transitive + description: + name: leak_tracker + sha256: "78eb209deea09858f5269f5a5b02be4049535f568c07b275096836f01ea323fa" + url: "https://pub.dev" + source: hosted + version: "10.0.0" + leak_tracker_flutter_testing: + dependency: transitive + description: + name: leak_tracker_flutter_testing + sha256: b46c5e37c19120a8a01918cfaf293547f47269f7cb4b0058f21531c2465d6ef0 + url: "https://pub.dev" + source: hosted + version: "2.0.1" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: a597f72a664dbd293f3bfc51f9ba69816f84dcd403cdac7066cb3f6003f3ab47 + url: "https://pub.dev" + source: hosted + version: "2.0.1" lints: dependency: transitive description: name: lints - sha256: "6b0206b0bf4f04961fc5438198ccb3a885685cd67d4d4a32cc20ad7f8adbe015" + sha256: "0a217c6c989d21039f1498c3ed9f3ed71b354e69873f13a8dfc3c9fe76f1b452" url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.1.1" logging: dependency: transitive description: name: logging - sha256: "04094f2eb032cbb06c6f6e8d3607edcfcb0455e2bb6cbc010cb01171dcb64e6d" + sha256: "623a88c9594aa774443aa3eb2d41807a48486b5613e67599fb4c41c0ad47c340" url: "https://pub.dev" source: hosted - version: "1.1.1" + version: "1.2.0" macos_window_utils: dependency: "direct main" description: name: macos_window_utils - sha256: "43a90473f8786f00f07203e6819dab67e032f8896dafa4a6f85fbc71fba32c0b" + sha256: "230be594d26f6dee92c5a1544f4242d25138a5bfb9f185b27f14de3949ef0be8" url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "1.5.0" matcher: dependency: transitive description: name: matcher - sha256: "6501fbd55da300384b768785b83e5ce66991266cec21af89ab9ae7f5ce1c4cbb" + sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb url: "https://pub.dev" source: hosted - version: "0.12.15" + version: "0.12.16+1" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724 + sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" url: "https://pub.dev" source: hosted - version: "0.2.0" + version: "0.8.0" meta: dependency: transitive description: name: meta - sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3" + sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04 url: "https://pub.dev" source: hosted - version: "1.9.1" + version: "1.11.0" mime: dependency: transitive description: name: mime - sha256: e4ff8e8564c03f255408decd16e7899da1733852a9110a58fe6d1b817684a63e + sha256: "2e123074287cc9fd6c09de8336dae606d1ddb88d9ac47358826db698c176a1f2" url: "https://pub.dev" source: hosted - version: "1.0.4" + version: "1.0.5" mocktail: dependency: "direct dev" description: @@ -279,18 +303,18 @@ packages: dependency: transitive description: name: path - sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" + sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" url: "https://pub.dev" source: hosted - version: "1.8.3" + version: "1.9.0" plugin_platform_interface: dependency: transitive description: name: plugin_platform_interface - sha256: "43798d895c929056255600343db8f049921cbec94d31ec87f1dc5c16c01935dd" + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" url: "https://pub.dev" source: hosted - version: "2.1.5" + version: "2.1.8" pool: dependency: transitive description: @@ -364,26 +388,26 @@ packages: dependency: transitive description: name: source_span - sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250 + sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" url: "https://pub.dev" source: hosted - version: "1.9.1" + version: "1.10.0" stack_trace: dependency: transitive description: name: stack_trace - sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5 + sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" url: "https://pub.dev" source: hosted - version: "1.11.0" + version: "1.11.1" stream_channel: dependency: transitive description: name: stream_channel - sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8" + sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.2" string_scanner: dependency: transitive description: @@ -404,26 +428,26 @@ packages: dependency: transitive description: name: test - sha256: "13b41f318e2a5751c3169137103b60c584297353d4b1761b66029bae6411fe46" + sha256: a1f7595805820fcc05e5c52e3a231aedd0b72972cb333e8c738a8b1239448b6f url: "https://pub.dev" source: hosted - version: "1.24.3" + version: "1.24.9" test_api: dependency: transitive description: name: test_api - sha256: "75760ffd7786fffdfb9597c35c5b27eaeec82be8edfb6d71d32651128ed7aab8" + sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b" url: "https://pub.dev" source: hosted - version: "0.6.0" + version: "0.6.1" test_core: dependency: transitive description: name: test_core - sha256: "99806e9e6d95c7b059b7a0fc08f07fc53fabe54a829497f0d9676299f1e8637e" + sha256: a757b14fc47507060a162cc2530d9a4a2f92f5100a952c7443b5cad5ef5b106a url: "https://pub.dev" source: hosted - version: "0.5.3" + version: "0.5.9" typed_data: dependency: transitive description: @@ -444,10 +468,10 @@ packages: dependency: transitive description: name: vm_service - sha256: f3743ca475e0c9ef71df4ba15eb2d7684eecd5c8ba20a462462e4e8b561b2e11 + sha256: b3d56ff4341b8f182b96aceb2fa20e3dcb336b9f867bc0eafc0de10f1048e957 url: "https://pub.dev" source: hosted - version: "11.6.0" + version: "13.0.0" watcher: dependency: transitive description: @@ -456,22 +480,30 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.0" + web: + dependency: transitive + description: + name: web + sha256: "97da13628db363c635202ad97068d47c5b8aa555808e7a9411963c533b449b27" + url: "https://pub.dev" + source: hosted + version: "0.5.1" web_socket_channel: dependency: transitive description: name: web_socket_channel - sha256: d88238e5eac9a42bb43ca4e721edba3c08c6354d4a53063afaa568516217621b + sha256: "58c6666b342a38816b2e7e50ed0f1e261959630becd4c879c4f26bfa14aa5a42" url: "https://pub.dev" source: hosted - version: "2.4.0" + version: "2.4.5" webkit_inspection_protocol: dependency: transitive description: name: webkit_inspection_protocol - sha256: "67d3a8b6c79e1987d19d848b0892e582dbb0c66c57cc1fef58a177dd2aa2823d" + sha256: "87d3f2333bb240704cd3f1c6b5b7acd8a10e7f0bc28c28dcf14e782014f4a572" url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "1.2.1" yaml: dependency: transitive description: @@ -481,5 +513,5 @@ packages: source: hosted version: "3.1.2" sdks: - dart: ">=3.1.0-185.0.dev <4.0.0" + dart: ">=3.3.0 <4.0.0" flutter: ">=3.10.0" diff --git a/pubspec.yaml b/pubspec.yaml index 0cfba245..63220838 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: 2.0.5 +version: 2.0.6 homepage: "https://macosui.dev" repository: "https://github.com/GroovinChip/macos_ui" @@ -14,6 +14,7 @@ dependencies: macos_window_utils: ^1.2.0 gradient_borders: ^1.0.0 appkit_ui_element_colors: ^1.0.0 + equatable: ^2.0.5 dev_dependencies: flutter_test: diff --git a/test/src/theme/macos_theme_test.dart b/test/src/theme/macos_theme_test.dart new file mode 100644 index 00000000..92df987c --- /dev/null +++ b/test/src/theme/macos_theme_test.dart @@ -0,0 +1,49 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:macos_ui/src/library.dart'; +import 'package:macos_ui/src/theme/help_button_theme.dart'; +import 'package:macos_ui/src/theme/macos_colors.dart'; +import 'package:macos_ui/src/theme/macos_theme.dart'; + +void main() { + testWidgets('macos theme MacosThemeData equality 1', (tester) async { + final macosThemeData1 = MacosThemeData(); + final macosThemeData2 = MacosThemeData(); + + expect(macosThemeData1, equals(macosThemeData2)); + }); + + testWidgets('macos theme MacosThemeData equality 2', (tester) async { + final macosThemeData1 = MacosThemeData(brightness: Brightness.dark); + final macosThemeData2 = MacosThemeData(brightness: Brightness.light); + + expect(macosThemeData1, isNot(equals(macosThemeData2))); + }); + + testWidgets('macos theme MacosThemeData equality 3', (tester) async { + final macosThemeData1 = MacosThemeData( + helpButtonTheme: const HelpButtonThemeData( + color: MacosColors.appleRed, + )); + + final macosThemeData2 = MacosThemeData( + helpButtonTheme: const HelpButtonThemeData( + color: MacosColors.appleGreen, + )); + + expect(macosThemeData1, isNot(equals(macosThemeData2))); + }); + + testWidgets('macos theme MacosThemeData equality 4', (tester) async { + final macosThemeData1 = MacosThemeData( + helpButtonTheme: const HelpButtonThemeData( + color: MacosColors.appleRed, + )); + + final macosThemeData2 = MacosThemeData( + helpButtonTheme: const HelpButtonThemeData( + color: MacosColors.appleRed, + )); + + expect(macosThemeData1, equals(macosThemeData2)); + }); +} diff --git a/test/src/theme/typography_test.dart b/test/src/theme/typography_test.dart new file mode 100644 index 00000000..9524a6d2 --- /dev/null +++ b/test/src/theme/typography_test.dart @@ -0,0 +1,82 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + testWidgets('typography equality 1', (tester) async { + final typography1 = Typography(); + final typography2 = Typography(); + + expect(typography1, equals(typography2)); + }); + + testWidgets('typography equality 2', (tester) async { + final typography1 = Typography( + black: const TextTheme(bodyLarge: TextStyle(fontSize: 10)), + ); + final typography2 = Typography( + black: const TextTheme(bodyLarge: TextStyle(fontSize: 12)), + ); + + expect(typography1, isNot(equals(typography2))); + }); + + testWidgets('typography equality 3', (tester) async { + final typography1 = Typography( + englishLike: const TextTheme( + bodyLarge: TextStyle(fontSize: 10), + ), + ); + final typography2 = Typography( + englishLike: const TextTheme( + bodyLarge: TextStyle(fontSize: 10), + ), + ); + + expect(typography1, equals(typography2)); + }); + + testWidgets('typography equality 4', (tester) async { + final typography1 = Typography( + englishLike: const TextTheme( + bodyLarge: TextStyle(fontSize: 10), + ), + ); + final typography2 = Typography( + englishLike: const TextTheme( + bodyLarge: TextStyle(fontSize: 12), + ), + ); + + expect(typography1, isNot(equals(typography2))); + }); + + testWidgets('typography equality 5', (tester) async { + final typography1 = Typography( + englishLike: const TextTheme( + headlineLarge: TextStyle(fontSize: 10), + ), + ); + final typography2 = Typography( + englishLike: const TextTheme( + headlineLarge: TextStyle(fontSize: 12), + ), + ); + + expect(typography1, isNot(equals(typography2))); + }); + + testWidgets('typography equality 6', (tester) async { + final typography1 = Typography( + englishLike: const TextTheme( + headlineLarge: TextStyle(fontSize: 10), + ), + ); + final typography2 = Typography( + englishLike: const TextTheme( + headlineLarge: TextStyle(fontSize: 10), + ), + ); + + expect(typography1, equals(typography2)); + }); +} From de31bfedd42cc57805542820f4f4a21cd01ae8f1 Mon Sep 17 00:00:00 2001 From: Adrian Samoticha <86920182+Adrian-Samoticha@users.noreply.github.com> Date: Sun, 5 May 2024 14:04:02 +0200 Subject: [PATCH 147/151] Version 2.0.7 (#511) * Update radio_button.dart * Update CHANGELOG.md * remove default themes from example app * add accent color and main window support to `MacosThemeData` * regenerate macOS theme on window state and accent color changes * export accent color enum * retrieve accent color from theme in push button * retrieve accent color from theme in sidebar items * add disabled checkbox to buttons page in example * style the `MacosCheckbox` to be match the native checkboxes better * add `example/devtools_options.yaml` to `.gitignore` * bump version to 2.0.7 * organize imports * add changelog entry for version 2.0.7 * fix typo * fix typo * remove `print` call * remove unnecessary imports * change colors in unit tests --------- Co-authored-by: Abbas Hussein --- .gitignore | 1 + CHANGELOG.md | 5 + example/lib/main.dart | 2 - example/lib/pages/buttons_page.dart | 19 +- lib/macos_ui.dart | 1 + lib/src/buttons/checkbox.dart | 468 +++++++++++++++++---- lib/src/buttons/push_button.dart | 9 +- lib/src/layout/sidebar/sidebar_items.dart | 7 +- lib/src/macos_app.dart | 94 +++-- lib/src/theme/macos_theme.dart | 242 ++++++++++- pubspec.yaml | 2 +- test/theme/icon_theme_test.dart | 2 +- test/theme/popup_button_theme_test.dart | 2 +- test/theme/pulldown_button_theme_test.dart | 2 +- test/theme/search_field_theme_test.dart | 2 +- 15 files changed, 706 insertions(+), 152 deletions(-) diff --git a/.gitignore b/.gitignore index c8887dcd..144640c7 100644 --- a/.gitignore +++ b/.gitignore @@ -81,3 +81,4 @@ metrics coverage_report coverage example/macos/Flutter/GeneratedPluginRegistrant.swift +example/devtools_options.yaml diff --git a/CHANGELOG.md b/CHANGELOG.md index d8cacfe3..85e8c48c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +## [2.0.7] +### 🛠️ Updated 🛠️ +* Made most widgets aware of the user’s accent color and window state by adding respective fields to `MacosThemeData`. +* `MacosCheckbox` has received a facelift to mimic the look and feel of native macOS checkboxes better. + ## [2.0.6] ### 🛠️ Updated 🛠️ * Implemented value equality for `MacosThemeData`. diff --git a/example/lib/main.dart b/example/lib/main.dart index 193bef1f..d59b1dcc 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -47,8 +47,6 @@ class MacosUIGalleryApp extends StatelessWidget { final appTheme = context.watch(); return MacosApp( title: 'macos_ui Widget Gallery', - theme: MacosThemeData.light(), - darkTheme: MacosThemeData.dark(), themeMode: appTheme.mode, debugShowCheckedModeBanner: false, home: const WidgetGallery(), diff --git a/example/lib/pages/buttons_page.dart b/example/lib/pages/buttons_page.dart index fd61774f..26952d90 100644 --- a/example/lib/pages/buttons_page.dart +++ b/example/lib/pages/buttons_page.dart @@ -558,11 +558,20 @@ class _ButtonsPageState extends State { const SizedBox(height: 16), const WidgetTextTitle2(widgetName: 'MacosCheckbox'), const SizedBox(height: 8), - MacosCheckbox( - value: switchValue, - onChanged: (value) { - setState(() => switchValue = value); - }, + Row( + children: [ + MacosCheckbox( + value: switchValue, + onChanged: (value) { + setState(() => switchValue = value); + }, + ), + const SizedBox(width: 8), + MacosCheckbox( + value: switchValue, + onChanged: null, + ), + ], ), const SizedBox(height: 16), const WidgetTextTitle2(widgetName: 'MacosRadioButton'), diff --git a/lib/macos_ui.dart b/lib/macos_ui.dart index 62dcc602..d47a163a 100644 --- a/lib/macos_ui.dart +++ b/lib/macos_ui.dart @@ -88,3 +88,4 @@ export 'src/theme/search_field_theme.dart'; export 'src/theme/time_picker_theme.dart'; export 'src/theme/tooltip_theme.dart'; export 'src/theme/typography.dart'; +export 'src/enums/accent_color.dart'; diff --git a/lib/src/buttons/checkbox.dart b/lib/src/buttons/checkbox.dart index d7f8bcc3..2d21e7c7 100644 --- a/lib/src/buttons/checkbox.dart +++ b/lib/src/buttons/checkbox.dart @@ -1,4 +1,5 @@ import 'package:flutter/rendering.dart'; +import 'package:gradient_borders/gradient_borders.dart'; import 'package:macos_ui/macos_ui.dart'; import 'package:macos_ui/src/library.dart'; @@ -82,92 +83,403 @@ class MacosCheckbox extends StatelessWidget { assert(debugCheckHasMacosTheme(context)); final MacosThemeData theme = MacosTheme.of(context); bool isLight = !theme.brightness.isDark; - return GestureDetector( - onTap: () { - if (value == null || value == false) { - onChanged?.call(true); - } else { - onChanged?.call(false); - } - }, - child: Semantics( - // value == true because [value] can be null - checked: value == true, - label: semanticLabel, - child: Container( - height: size, - width: size, - alignment: Alignment.center, - decoration: isDisabled || value == null || value == true - ? BoxDecoration( - color: MacosDynamicColor.resolve( - isDisabled - ? disabledColor - : activeColor ?? theme.primaryColor, - context, - ), - borderRadius: const BorderRadius.all(Radius.circular(4.0)), - ) - : 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), + return StreamBuilder( + stream: AccentColorListener.instance.onChanged, + builder: (context, _) { + return StreamBuilder( + stream: WindowMainStateListener.instance.onChanged, + builder: (context, _) { + final accentColor = + MacosTheme.of(context).accentColor ?? AccentColor.blue; + final isMainWindow = + MacosTheme.of(context).isMainWindow ?? true; + + return GestureDetector( + onTap: () { + if (value == null || value == false) { + onChanged?.call(true); + } else { + onChanged?.call(false); + } + }, + child: Semantics( + // value == true because [value] can be null + checked: value == true, + label: semanticLabel, + child: Container( + height: size, + width: size, + alignment: Alignment.center, + child: SizedBox.expand( + child: _DecoratedContainer( + accentColor: accentColor, + isDisabled: isDisabled, + isLight: isLight, + isMainWindow: isMainWindow, + value: value, + isMixed: isMixed, + theme: theme, + size: size, ), - 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, - ), - ], ), + ), + ); + }); + }); + } +} + +/// A widget that builds a decorated checkbox. +class _DecoratedContainer extends StatelessWidget { + const _DecoratedContainer({ + required this.accentColor, + required this.isDisabled, + required this.isLight, + required this.isMainWindow, + required this.value, + required this.isMixed, + required this.theme, + required this.size, + }); + + final AccentColor accentColor; + final bool isDisabled; + final bool isLight; + final bool isMainWindow; + final bool? value; + final bool isMixed; + final MacosThemeData theme; + final double size; + + @override + Widget build(BuildContext context) { + return Container( + decoration: _BoxDecorationBuilder.buildBoxDecoration( + accentColor: accentColor, + isEnabled: !isDisabled, + isDarkModeEnabled: !isLight, + isMainWindow: isMainWindow, + value: value, + ), + child: _CheckboxStack( + value: value, + isDisabled: isDisabled, + isMixed: isMixed, + theme: theme, + isMainWindow: isMainWindow, + size: size, + ), + ); + } +} + +/// A stack containing the checkbox’s inner drop shadow and checkmark icon. +class _CheckboxStack extends StatelessWidget { + const _CheckboxStack({ + required this.value, + required this.isDisabled, + required this.isMixed, + required this.theme, + required this.isMainWindow, + required this.size, + }); + + final bool? value; + final bool isDisabled; + final bool isMixed; + final MacosThemeData theme; + final bool isMainWindow; + final double size; + + @override + Widget build(BuildContext context) { + final icon = value == false + ? null + : isMixed + ? CupertinoIcons.minus + : CupertinoIcons.checkmark; + + return Stack( + children: [ + _InnerDropShadow( + value: value, + isEnabled: !isDisabled, + ), + Center( child: Icon( - isDisabled || value == false - ? null - : isMixed - ? CupertinoIcons.minus - : CupertinoIcons.check_mark, - color: CupertinoColors.white, + icon, + color: _getCheckmarkColor(), size: (size - 3).clamp(0, size), ), ), + ], + ); + } + + _getCheckmarkColor() { + if (isDisabled) { + return const MacosColor.fromRGBO(172, 172, 172, 1.0); + } + + if (theme.brightness.isDark) { + return theme.accentColor == AccentColor.graphite && isMainWindow + ? CupertinoColors.black + : CupertinoColors.white; + } + + if (theme.isMainWindow == false) { + return CupertinoColors.black; + } + + return CupertinoColors.white; + } +} + +/// A widget that paints an inner drop shadow for the checkbox in light mode. +class _InnerDropShadow extends StatelessWidget { + /// The value of the checkbox. + final bool? value; + + /// Whether the checkbox is enabled. + final bool isEnabled; + + /// Creates a widget that paints an inner drop shadow for a checkbox. + const _InnerDropShadow({ + required this.value, + required this.isEnabled, + }); + + @override + Widget build(BuildContext context) { + final theme = MacosTheme.of(context); + + if (theme.brightness.isDark) { + return const SizedBox(); + } + + if (value == true && theme.isMainWindow == true && isEnabled) { + return const SizedBox(); + } + + final color = isEnabled + ? CupertinoColors.white + : const MacosColor.fromRGBO(255, 255, 255, 0.8); + + return SizedBox.expand( + child: Container( + clipBehavior: Clip.antiAlias, + decoration: const BoxDecoration( + color: MacosColor.fromRGBO(0, 0, 0, 0.15), + borderRadius: BorderRadius.all(Radius.circular(3.5)), + ), + child: DecoratedBox( + decoration: BoxDecoration( + borderRadius: const BorderRadius.all(Radius.circular(3.5)), + boxShadow: [ + BoxShadow( + color: color, + offset: const Offset(0.0, 0.5), + blurRadius: 1.0, + ), + ], + ), + ), + ), + ); + } +} + +class _BoxDecorationBuilder { + /// Gets the colors to use for the [BoxDecoration]’s gradient based on the + /// provided [accentColor], [isEnabled], and [isDarkModeEnabled] properties. + static List getGradientColors({ + required AccentColor accentColor, + required bool isEnabled, + required bool isDarkModeEnabled, + required bool isMainWindow, + required bool? value, + }) { + final isEnabledFactor = isEnabled || !isDarkModeEnabled ? 1.0 : 0.5; + + final showDisabledCheckbox = !isMainWindow || !isEnabled || value == false; + + if (showDisabledCheckbox) { + return isDarkModeEnabled + ? [ + MacosColor.fromRGBO(74, 74, 74, 1.0 * isEnabledFactor), + MacosColor.fromRGBO(101, 101, 101, 1.0 * isEnabledFactor), + ] + : const [ + MacosColors.transparent, + MacosColors.transparent, + ]; + } + + if (isDarkModeEnabled) { + switch (accentColor) { + case AccentColor.blue: + return [ + MacosColor.fromRGBO(23, 105, 229, 1.0 * isEnabledFactor), + MacosColor.fromRGBO(20, 94, 203, 1.0 * isEnabledFactor), + ]; + + case AccentColor.purple: + return [ + MacosColor.fromRGBO(203, 46, 202, 1.0 * isEnabledFactor), + MacosColor.fromRGBO(182, 40, 182, 1.0 * isEnabledFactor), + ]; + + case AccentColor.pink: + return [ + MacosColor.fromRGBO(229, 75, 145, 1.0 * isEnabledFactor), + MacosColor.fromRGBO(205, 61, 129, 1.0 * isEnabledFactor), + ]; + + case AccentColor.red: + return [ + MacosColor.fromRGBO(237, 64, 68, 1.0 * isEnabledFactor), + MacosColor.fromRGBO(213, 56, 61, 1.0 * isEnabledFactor), + ]; + + case AccentColor.orange: + return [ + MacosColor.fromRGBO(244, 114, 0, 1.0 * isEnabledFactor), + MacosColor.fromRGBO(219, 102, 0, 1.0 * isEnabledFactor), + ]; + + case AccentColor.yellow: + return [ + MacosColor.fromRGBO(233, 176, 4, 1.0 * isEnabledFactor), + MacosColor.fromRGBO(209, 157, 3, 1.0 * isEnabledFactor), + ]; + + case AccentColor.green: + return [ + MacosColor.fromRGBO(75, 177, 45, 1.0 * isEnabledFactor), + MacosColor.fromRGBO(67, 159, 40, 1.0 * isEnabledFactor), + ]; + + case AccentColor.graphite: + return [ + MacosColor.fromRGBO(148, 148, 148, 1.0 * isEnabledFactor), + MacosColor.fromRGBO(148, 148, 148, 1.0 * isEnabledFactor), + ]; + + default: + throw UnimplementedError(); + } + } else { + switch (accentColor) { + case AccentColor.blue: + return [ + MacosColor.fromRGBO(39, 145, 255, 1.0 * isEnabledFactor), + MacosColor.fromRGBO(1, 123, 255, 1.0 * isEnabledFactor), + ]; + + case AccentColor.purple: + return [ + MacosColor.fromRGBO(173, 56, 177, 1.0 * isEnabledFactor), + MacosColor.fromRGBO(159, 19, 163, 1.0 * isEnabledFactor), + ]; + + case AccentColor.pink: + return [ + MacosColor.fromRGBO(237, 102, 165, 1.0 * isEnabledFactor), + MacosColor.fromRGBO(234, 76, 149, 1.0 * isEnabledFactor), + ]; + + case AccentColor.red: + return [ + MacosColor.fromRGBO(225, 60, 66, 1.0 * isEnabledFactor), + MacosColor.fromRGBO(220, 26, 31, 1.0 * isEnabledFactor), + ]; + + case AccentColor.orange: + return [ + MacosColor.fromRGBO(247, 130, 31, 1.0 * isEnabledFactor), + MacosColor.fromRGBO(245, 108, 0, 1.0 * isEnabledFactor), + ]; + + case AccentColor.yellow: + return [ + MacosColor.fromRGBO(242, 189, 32, 1.0 * isEnabledFactor), + MacosColor.fromRGBO(240, 178, 0, 1.0 * isEnabledFactor), + ]; + + case AccentColor.green: + return [ + MacosColor.fromRGBO(90, 185, 59, 1.0 * isEnabledFactor), + MacosColor.fromRGBO(60, 172, 25, 1.0 * isEnabledFactor), + ]; + + case AccentColor.graphite: + return [ + MacosColor.fromRGBO(86, 86, 86, 1.0 * isEnabledFactor), + MacosColor.fromRGBO(55, 55, 55, 1.0 * isEnabledFactor), + ]; + + default: + throw UnimplementedError(); + } + } + } + + /// Builds a [BoxDecoration] for a [MacosPushButton]. + static BoxDecoration buildBoxDecoration({ + required AccentColor accentColor, + required bool isEnabled, + required bool isDarkModeEnabled, + required bool isMainWindow, + required bool? value, + }) { + final isEnabledFactor = isEnabled ? 1.0 : 0.5; + + final List shadows = isDarkModeEnabled + ? const [ + BoxShadow( + color: MacosColor.fromRGBO(0, 0, 0, 0.8), + blurRadius: 0.7, + spreadRadius: -0.5, + offset: Offset(0.0, 0.5), + blurStyle: BlurStyle.outer, + ) + ] + : const []; + + return BoxDecoration( + border: isDarkModeEnabled + ? GradientBoxBorder( + gradient: LinearGradient( + colors: [ + MacosColor.fromRGBO(255, 255, 255, 0.43 * isEnabledFactor), + const MacosColor.fromRGBO(255, 255, 255, 0.0), + ], + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + stops: const [0.0, 0.2], + ), + width: 0.7, + ) + : Border.all( + color: value == false && isEnabled + ? const MacosColor.fromRGBO(0, 0, 0, 0.15) + : const MacosColor.fromRGBO(0, 0, 0, 0.12), + width: 0.5, + ), + gradient: LinearGradient( + colors: getGradientColors( + accentColor: accentColor, + isEnabled: isEnabled, + isDarkModeEnabled: isDarkModeEnabled, + isMainWindow: isMainWindow, + value: value, + ), + begin: Alignment.topCenter, + end: Alignment.bottomCenter, ), + boxShadow: shadows, + borderRadius: const BorderRadius.all(Radius.circular(3.5)), ); } } diff --git a/lib/src/buttons/push_button.dart b/lib/src/buttons/push_button.dart index df0cf4fe..e95a9857 100644 --- a/lib/src/buttons/push_button.dart +++ b/lib/src/buttons/push_button.dart @@ -4,7 +4,6 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/rendering.dart'; import 'package:gradient_borders/gradient_borders.dart'; import 'package:macos_ui/macos_ui.dart'; -import 'package:macos_ui/src/enums/accent_color.dart'; import 'package:macos_ui/src/library.dart'; const _kMiniButtonSize = Size(26.0, 11.0); @@ -256,8 +255,8 @@ class PushButtonState extends State @visibleForTesting bool buttonHeldDown = false; - AccentColor get _accentColor => - AccentColorListener.instance.currentAccentColor ?? AccentColor.blue; + AccentColor _getAccentColor(BuildContext context) => + MacosTheme.of(context).accentColor ?? AccentColor.blue; BoxDecoration _getBoxDecoration() { // If the window isn’t currently the main window (that is, it is not in @@ -265,7 +264,7 @@ class PushButtonState extends State final isMainWindow = WindowMainStateListener.instance.isMainWindow; return _BoxDecorationBuilder.buildBoxDecoration( - accentColor: _accentColor, + accentColor: _getAccentColor(context), isEnabled: widget.enabled, isDarkModeEnabled: MacosTheme.of(context).brightness.isDark, isSecondary: !isMainWindow || (widget.secondary ?? false), @@ -284,7 +283,7 @@ class PushButtonState extends State return MacosDynamicColor.resolve( widget.color ?? _BoxDecorationBuilder.getGradientColors( - accentColor: _accentColor, + accentColor: _getAccentColor(context), isEnabled: enabled, isDarkModeEnabled: theme.brightness.isDark, isSecondary: isSecondary || !isWindowMain, diff --git a/lib/src/layout/sidebar/sidebar_items.dart b/lib/src/layout/sidebar/sidebar_items.dart index e24f90d3..5dd4d7d1 100644 --- a/lib/src/layout/sidebar/sidebar_items.dart +++ b/lib/src/layout/sidebar/sidebar_items.dart @@ -1,5 +1,4 @@ import 'package:macos_ui/macos_ui.dart'; -import 'package:macos_ui/src/enums/accent_color.dart'; import 'package:macos_ui/src/library.dart'; const Duration _kExpand = Duration(milliseconds: 200); @@ -100,15 +99,15 @@ class SidebarItems extends StatelessWidget { final MouseCursor? cursor; /// The user’s selected system accent color. - AccentColor get _accentColor => - AccentColorListener.instance.currentAccentColor ?? AccentColor.blue; + AccentColor _getAccentColor(BuildContext context) => + MacosTheme.of(context).accentColor ?? AccentColor.blue; /// Returns the sidebar item’s selected color. Color _getColor(BuildContext context) { final isMainWindow = WindowMainStateListener.instance.isMainWindow; return _ColorProvider.getSelectedColor( - accentColor: _accentColor, + accentColor: _getAccentColor(context), isDarkModeEnabled: MacosTheme.of(context).brightness.isDark, isWindowMain: isMainWindow, ); diff --git a/lib/src/macos_app.dart b/lib/src/macos_app.dart index 459918fb..1589821e 100644 --- a/lib/src/macos_app.dart +++ b/lib/src/macos_app.dart @@ -310,41 +310,65 @@ class _MacosAppState extends State { widget.routerDelegate != null || widget.routerConfig != null; Widget _macosBuilder(BuildContext context, Widget? child) { - final mode = widget.themeMode ?? ThemeMode.system; - final platformBrightness = MediaQuery.platformBrightnessOf(context); - final useDarkTheme = mode == ThemeMode.dark || - (mode == ThemeMode.system && platformBrightness == Brightness.dark); - - late MacosThemeData theme; - if (useDarkTheme) { - theme = widget.darkTheme ?? MacosThemeData.dark(); - } else { - theme = widget.theme ?? MacosThemeData.light(); - } - - return MacosTheme( - data: theme, - child: DefaultTextStyle( - style: TextStyle(color: theme.typography.body.color), - child: widget.builder != null - // See the MaterialApp source code for the explanation for - // wrapping a builder in a builder - ? Builder( - builder: (context) { - // An Overlay is used here because MacosTooltip needs an - // Overlay as an ancestor in the widget tree. - return Overlay( - initialEntries: [ - OverlayEntry( - builder: (context) => widget.builder!(context, child), - ), - ], - ); - }, - ) - : child ?? const SizedBox.shrink(), - ), - ); + return StreamBuilder( + stream: WindowMainStateListener.instance.onChanged, + builder: (context, _) { + return StreamBuilder( + stream: AccentColorListener.instance.onChanged, + builder: (context, _) { + final mode = widget.themeMode ?? ThemeMode.system; + final platformBrightness = + MediaQuery.platformBrightnessOf(context); + final useDarkTheme = mode == ThemeMode.dark || + (mode == ThemeMode.system && + platformBrightness == Brightness.dark); + + final accentColor = + AccentColorListener.instance.currentAccentColor; + final isMainWindow = + WindowMainStateListener.instance.isMainWindow; + + late MacosThemeData theme; + if (useDarkTheme) { + theme = widget.darkTheme ?? + MacosThemeData.dark( + accentColor: accentColor, + isMainWindow: isMainWindow, + ); + } else { + theme = widget.theme ?? + MacosThemeData.light( + accentColor: accentColor, + isMainWindow: isMainWindow, + ); + } + + return MacosTheme( + data: theme, + child: DefaultTextStyle( + style: TextStyle(color: theme.typography.body.color), + child: widget.builder != null + // See the MaterialApp source code for the explanation for + // wrapping a builder in a builder + ? Builder( + builder: (context) { + // An Overlay is used here because MacosTooltip needs an + // Overlay as an ancestor in the widget tree. + return Overlay( + initialEntries: [ + OverlayEntry( + builder: (context) => + widget.builder!(context, child), + ), + ], + ); + }, + ) + : child ?? const SizedBox.shrink(), + ), + ); + }); + }); } Widget _buildMacosApp(BuildContext context) { diff --git a/lib/src/theme/macos_theme.dart b/lib/src/theme/macos_theme.dart index 59776de6..7b51b3a0 100644 --- a/lib/src/theme/macos_theme.dart +++ b/lib/src/theme/macos_theme.dart @@ -189,8 +189,8 @@ class MacosThemeData extends Equatable with Diagnosticable { /// /// See also: /// - /// * [MacosThemeData.light], which creates a light blue theme. - /// * [MacosThemeData.dark], which creates a dark blue theme. + /// * [MacosThemeData.light], which creates a light theme. + /// * [MacosThemeData.dark], which creates a dark theme. factory MacosThemeData({ Brightness? brightness, Color? primaryColor, @@ -209,11 +209,17 @@ class MacosThemeData extends Equatable with Diagnosticable { MacosDatePickerThemeData? datePickerTheme, MacosTimePickerThemeData? timePickerTheme, MacosSearchFieldThemeData? searchFieldTheme, + AccentColor? accentColor, + bool? isMainWindow, }) { // ignore: no_leading_underscores_for_local_identifiers final Brightness _brightness = brightness ?? Brightness.light; final bool isDark = _brightness == Brightness.dark; - primaryColor ??= MacosColors.controlAccentColor; + primaryColor ??= _ColorProvider.getPrimaryColor( + accentColor: accentColor ?? AccentColor.blue, + isDarkModeEnabled: isDark, + isWindowMain: isMainWindow ?? true, + ); canvasColor ??= isDark ? const Color.fromRGBO(40, 40, 40, 1.0) @@ -266,16 +272,20 @@ class MacosThemeData extends Equatable with Diagnosticable { visualDensity ??= VisualDensity.adaptivePlatformDensity; iconTheme ??= MacosIconThemeData( - color: isDark - ? CupertinoColors.activeBlue.darkColor - : CupertinoColors.activeBlue.color, + color: _ColorProvider.getActiveColor( + accentColor: accentColor ?? AccentColor.blue, + isDarkModeEnabled: isDark, + isWindowMain: isMainWindow ?? true, + ), size: 20, ); popupButtonTheme ??= MacosPopupButtonThemeData( - highlightColor: isDark - ? CupertinoColors.activeBlue.darkColor - : CupertinoColors.activeBlue.color, + highlightColor: _ColorProvider.getActiveColor( + accentColor: accentColor ?? AccentColor.blue, + isDarkModeEnabled: isDark, + isWindowMain: isMainWindow ?? true, + ), backgroundColor: isDark ? const Color.fromRGBO(255, 255, 255, 0.247) : const Color.fromRGBO(255, 255, 255, 1), @@ -285,9 +295,11 @@ class MacosThemeData extends Equatable with Diagnosticable { ); pulldownButtonTheme ??= MacosPulldownButtonThemeData( - highlightColor: isDark - ? CupertinoColors.activeBlue.darkColor - : CupertinoColors.activeBlue.color, + highlightColor: _ColorProvider.getActiveColor( + accentColor: accentColor ?? AccentColor.blue, + isDarkModeEnabled: isDark, + isWindowMain: isMainWindow ?? true, + ), backgroundColor: isDark ? const Color.fromRGBO(255, 255, 255, 0.247) : const Color.fromRGBO(255, 255, 255, 1), @@ -355,9 +367,11 @@ class MacosThemeData extends Equatable with Diagnosticable { ); searchFieldTheme ??= MacosSearchFieldThemeData( - highlightColor: isDark - ? CupertinoColors.activeBlue.darkColor - : CupertinoColors.activeBlue.color, + highlightColor: _ColorProvider.getActiveColor( + accentColor: accentColor ?? AccentColor.blue, + isDarkModeEnabled: isDark, + isWindowMain: isMainWindow ?? true, + ), resultsBackgroundColor: isDark ? const Color.fromRGBO(30, 30, 30, 1) : const Color.fromRGBO(242, 242, 247, 1), @@ -381,6 +395,8 @@ class MacosThemeData extends Equatable with Diagnosticable { datePickerTheme: datePickerTheme, timePickerTheme: timePickerTheme, searchFieldTheme: searchFieldTheme, + accentColor: accentColor, + isMainWindow: isMainWindow, ); final customizedData = defaultData.copyWith( @@ -400,6 +416,8 @@ class MacosThemeData extends Equatable with Diagnosticable { pulldownButtonTheme: pulldownButtonTheme, datePickerTheme: datePickerTheme, searchFieldTheme: searchFieldTheme, + accentColor: accentColor, + isMainWindow: isMainWindow, ); return defaultData.merge(customizedData); @@ -429,14 +447,31 @@ class MacosThemeData extends Equatable with Diagnosticable { required this.datePickerTheme, required this.timePickerTheme, required this.searchFieldTheme, + required this.accentColor, + required this.isMainWindow, }); /// A default light theme. - factory MacosThemeData.light() => - MacosThemeData(brightness: Brightness.light); + factory MacosThemeData.light({ + AccentColor? accentColor, + bool? isMainWindow, + }) => + MacosThemeData( + brightness: Brightness.light, + accentColor: accentColor, + isMainWindow: isMainWindow, + ); /// A default dark theme. - factory MacosThemeData.dark() => MacosThemeData(brightness: Brightness.dark); + factory MacosThemeData.dark({ + AccentColor? accentColor, + bool? isMainWindow, + }) => + MacosThemeData( + brightness: Brightness.dark, + accentColor: accentColor, + isMainWindow: isMainWindow, + ); /// The default color theme. Same as [ThemeData.light]. /// @@ -505,6 +540,13 @@ class MacosThemeData extends Equatable with Diagnosticable { /// The default style for [MacosSearchField]s below the overall [MacosTheme] final MacosSearchFieldThemeData searchFieldTheme; + /// The accent color to use for the application. + final AccentColor? accentColor; + + /// Whether the app is running in the main (i.e., the currently active) + /// window. + final bool? isMainWindow; + /// Linearly interpolate between two themes. static MacosThemeData lerp(MacosThemeData a, MacosThemeData b, double t) { return MacosThemeData.raw( @@ -552,6 +594,8 @@ class MacosThemeData extends Equatable with Diagnosticable { b.searchFieldTheme, t, ), + accentColor: t < 0.5 ? a.accentColor : b.accentColor, + isMainWindow: t < 0.5 ? a.isMainWindow : b.isMainWindow, ); } @@ -574,6 +618,8 @@ class MacosThemeData extends Equatable with Diagnosticable { MacosDatePickerThemeData? datePickerTheme, MacosTimePickerThemeData? timePickerTheme, MacosSearchFieldThemeData? searchFieldTheme, + AccentColor? accentColor, + bool? isMainWindow, }) { return MacosThemeData.raw( brightness: brightness ?? this.brightness, @@ -593,6 +639,8 @@ class MacosThemeData extends Equatable with Diagnosticable { datePickerTheme: this.datePickerTheme.merge(datePickerTheme), timePickerTheme: this.timePickerTheme.merge(timePickerTheme), searchFieldTheme: this.searchFieldTheme.merge(searchFieldTheme), + accentColor: accentColor ?? this.accentColor, + isMainWindow: isMainWindow ?? this.isMainWindow, ); } @@ -617,6 +665,8 @@ class MacosThemeData extends Equatable with Diagnosticable { datePickerTheme: datePickerTheme.merge(other.datePickerTheme), timePickerTheme: timePickerTheme.merge(other.timePickerTheme), searchFieldTheme: searchFieldTheme.merge(other.searchFieldTheme), + accentColor: other.accentColor, + isMainWindow: other.isMainWindow, ); } @@ -682,6 +732,18 @@ class MacosThemeData extends Equatable with Diagnosticable { searchFieldTheme, ), ); + properties.add( + DiagnosticsProperty( + 'accentColor', + accentColor, + ), + ); + properties.add( + DiagnosticsProperty( + 'isMainWindow', + isMainWindow, + ), + ); } @override @@ -703,6 +765,8 @@ class MacosThemeData extends Equatable with Diagnosticable { datePickerTheme, timePickerTheme, searchFieldTheme, + accentColor, + isMainWindow, ]; } @@ -717,3 +781,145 @@ extension BrightnessX on Brightness { return light; } } + +class _ColorProvider { + _ColorProvider._(); + + /// Returns the primary color based on the provided parameters. + static Color getPrimaryColor({ + required AccentColor accentColor, + required bool isDarkModeEnabled, + required bool isWindowMain, + }) { + if (isDarkModeEnabled) { + if (!isWindowMain) { + return const MacosColor.fromRGBO(100, 100, 100, 0.625); + } + + switch (accentColor) { + case AccentColor.blue: + return const MacosColor.fromRGBO(29, 151, 255, 1.0); + + case AccentColor.purple: + return const MacosColor.fromRGBO(204, 118, 207, 1.0); + + case AccentColor.pink: + return const MacosColor.fromRGBO(255, 114, 194, 1.0); + + case AccentColor.red: + return const MacosColor.fromRGBO(225, 118, 124, 1.0); + + case AccentColor.orange: + return const MacosColor.fromRGBO(255, 147, 44, 1.0); + + case AccentColor.yellow: + return const MacosColor.fromRGBO(255, 220, 24, 1.0); + + case AccentColor.green: + return const MacosColor.fromRGBO(114, 202, 87, 1.0); + + case AccentColor.graphite: + return const MacosColor.fromRGBO(152, 152, 152, 1.0); + } + } + + if (!isWindowMain) { + return const MacosColor.fromRGBO(190, 190, 190, 1.0); + } + + switch (accentColor) { + case AccentColor.blue: + return const MacosColor.fromRGBO(0, 88, 224, 1.0); + + case AccentColor.purple: + return const MacosColor.fromRGBO(131, 44, 134, 1.0); + + case AccentColor.pink: + return const MacosColor.fromRGBO(212, 45, 126, 1.0); + + case AccentColor.red: + return const MacosColor.fromRGBO(203, 45, 43, 1.0); + + case AccentColor.orange: + return const MacosColor.fromRGBO(198, 82, 0, 1.0); + + case AccentColor.yellow: + return const MacosColor.fromRGBO(206, 154, 2, 1.0); + + case AccentColor.green: + return const MacosColor.fromRGBO(56, 146, 30, 1.0); + + case AccentColor.graphite: + return const MacosColor.fromRGBO(100, 100, 100, 1.0); + } + } + + /// Returns the active color based on the provided parameters. + static Color getActiveColor({ + required AccentColor accentColor, + required bool isDarkModeEnabled, + required bool isWindowMain, + }) { + if (isDarkModeEnabled) { + if (!isWindowMain) { + return const MacosColor.fromRGBO(76, 78, 65, 1.0); + } + + switch (accentColor) { + case AccentColor.blue: + return const MacosColor.fromRGBO(22, 105, 229, 0.749); + + case AccentColor.purple: + return const MacosColor.fromRGBO(204, 45, 202, 0.749); + + case AccentColor.pink: + return const MacosColor.fromRGBO(229, 74, 145, 0.749); + + case AccentColor.red: + return const MacosColor.fromRGBO(238, 64, 68, 0.749); + + case AccentColor.orange: + return const MacosColor.fromRGBO(244, 114, 0, 0.749); + + case AccentColor.yellow: + return const MacosColor.fromRGBO(233, 176, 0, 0.749); + + case AccentColor.green: + return const MacosColor.fromRGBO(76, 177, 45, 0.749); + + case AccentColor.graphite: + return const MacosColor.fromRGBO(129, 129, 122, 0.824); + } + } + + if (!isWindowMain) { + return const MacosColor.fromRGBO(180, 180, 180, 1.0); + } + + switch (accentColor) { + case AccentColor.blue: + return const MacosColor.fromRGBO(9, 129, 255, 0.749); + + case AccentColor.purple: + return const MacosColor.fromRGBO(162, 28, 165, 0.749); + + case AccentColor.pink: + return const MacosColor.fromRGBO(234, 81, 152, 0.749); + + case AccentColor.red: + return const MacosColor.fromRGBO(220, 32, 40, 0.749); + + case AccentColor.orange: + return const MacosColor.fromRGBO(245, 113, 0, 0.749); + + case AccentColor.yellow: + return const MacosColor.fromRGBO(240, 180, 2, 0.749); + + case AccentColor.green: + return const MacosColor.fromRGBO(66, 174, 33, 0.749); + + case AccentColor.graphite: + return const MacosColor.fromRGBO(174, 174, 167, 0.847); + } + } +} diff --git a/pubspec.yaml b/pubspec.yaml index 63220838..547ae60c 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: 2.0.6 +version: 2.0.7 homepage: "https://macosui.dev" repository: "https://github.com/GroovinChip/macos_ui" diff --git a/test/theme/icon_theme_test.dart b/test/theme/icon_theme_test.dart index 79076b18..7efe67fa 100644 --- a/test/theme/icon_theme_test.dart +++ b/test/theme/icon_theme_test.dart @@ -81,7 +81,7 @@ void main() { ); final theme = MacosIconTheme.of(capturedContext); - expect(theme.color, CupertinoColors.activeBlue.color); + expect(theme.color, const MacosColor(0xbe0981ff)); expect(theme.size, 20); }); } diff --git a/test/theme/popup_button_theme_test.dart b/test/theme/popup_button_theme_test.dart index 7cbe6b7a..f704da09 100644 --- a/test/theme/popup_button_theme_test.dart +++ b/test/theme/popup_button_theme_test.dart @@ -96,7 +96,7 @@ void main() { final theme = MacosPopupButtonTheme.of(capturedContext); expect(theme.backgroundColor, const Color(0xffffffff)); - expect(theme.highlightColor, const Color(0xff007aff)); + expect(theme.highlightColor, const MacosColor(0xbe0981ff)); expect(theme.popupColor, const Color(0xfff2f2f7)); }); }); diff --git a/test/theme/pulldown_button_theme_test.dart b/test/theme/pulldown_button_theme_test.dart index 87fc9666..a082b42e 100644 --- a/test/theme/pulldown_button_theme_test.dart +++ b/test/theme/pulldown_button_theme_test.dart @@ -97,7 +97,7 @@ void main() { final theme = MacosPulldownButtonTheme.of(capturedContext); expect(theme.backgroundColor, const Color(0xffffffff)); - expect(theme.highlightColor, const Color(0xff007aff)); + expect(theme.highlightColor, const MacosColor(0xbe0981ff)); expect(theme.pulldownColor, const Color(0xfff2f2f7)); }); }); diff --git a/test/theme/search_field_theme_test.dart b/test/theme/search_field_theme_test.dart index 6f0851b9..39aa577f 100644 --- a/test/theme/search_field_theme_test.dart +++ b/test/theme/search_field_theme_test.dart @@ -80,7 +80,7 @@ void main() { ); final theme = MacosSearchFieldTheme.of(capturedContext); - expect(theme.highlightColor, const Color(0xff007aff)); + expect(theme.highlightColor, const MacosColor(0xbe0981ff)); expect(theme.resultsBackgroundColor, const Color(0xfff2f2f7)); }); }); From e35fdabd8db5259be88a921bf0823d85aad53c9d Mon Sep 17 00:00:00 2001 From: Faisal Ansari Date: Tue, 13 Aug 2024 20:22:43 +0530 Subject: [PATCH 148/151] fixed text overflow on sidebar item (#513) * fixed text overflow on sidebar item * updated version and changelog --- CHANGELOG.md | 4 ++++ lib/src/layout/sidebar/sidebar_items.dart | 11 +++++++---- pubspec.yaml | 2 +- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 85e8c48c..b5e17ad5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## [2.0.8] +### 🛠️ Updated 🛠️ +* Fixed `SidebarItem` text overflowing. + ## [2.0.7] ### 🛠️ Updated 🛠️ * Made most widgets aware of the user’s accent color and window state by adding respective fields to `MacosThemeData`. diff --git a/lib/src/layout/sidebar/sidebar_items.dart b/lib/src/layout/sidebar/sidebar_items.dart index 5dd4d7d1..dc9329c5 100644 --- a/lib/src/layout/sidebar/sidebar_items.dart +++ b/lib/src/layout/sidebar/sidebar_items.dart @@ -318,11 +318,14 @@ class _SidebarItem extends StatelessWidget { child: item.leading!, ), ), - DefaultTextStyle( - style: labelStyle.copyWith( - color: selected ? textLuminance(selectedColor) : null, + Expanded( + child: DefaultTextStyle( + style: labelStyle.copyWith( + color: selected ? textLuminance(selectedColor) : null, + overflow: TextOverflow.ellipsis, + ), + child: item.label, ), - child: item.label, ), if (hasTrailing) ...[ const Spacer(), diff --git a/pubspec.yaml b/pubspec.yaml index 547ae60c..81c1ed36 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: 2.0.7 +version: 2.0.8 homepage: "https://macosui.dev" repository: "https://github.com/GroovinChip/macos_ui" From 7f13dfdb8b4088b1821a32bfb9cce575eb5704d2 Mon Sep 17 00:00:00 2001 From: Scott Meeuwsen <3615587+nu11ptr@users.noreply.github.com> Date: Sat, 31 Aug 2024 04:39:08 -0400 Subject: [PATCH 149/151] Allow initial expansion of SidebarItem disclosure items (#514) * Allow initial expansion of SidebarItem disclosure items * Update version to 2.0.9 --- CHANGELOG.md | 4 ++++ example/lib/main.dart | 1 + lib/src/layout/sidebar/sidebar_item.dart | 8 ++++++++ lib/src/layout/sidebar/sidebar_items.dart | 8 ++++++-- pubspec.yaml | 2 +- 5 files changed, 20 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b5e17ad5..9f1c56de 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## [2.0.9] +### 🛠️ Updated 🛠️ +* Add `expandDisclosureItems` flag to `SidebarItem` to optionally (default not changed) expand disclosure items initially + ## [2.0.8] ### 🛠️ Updated 🛠️ * Fixed `SidebarItem` text overflowing. diff --git a/example/lib/main.dart b/example/lib/main.dart index d59b1dcc..cbe02dbc 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -221,6 +221,7 @@ class _WidgetGalleryState extends State { label: Text('ResizablePane'), ), ], + expandDisclosureItems: true, ), SidebarItem( leading: MacosImageIcon( diff --git a/lib/src/layout/sidebar/sidebar_item.dart b/lib/src/layout/sidebar/sidebar_item.dart index 4e5d1318..e2666f20 100644 --- a/lib/src/layout/sidebar/sidebar_item.dart +++ b/lib/src/layout/sidebar/sidebar_item.dart @@ -19,6 +19,7 @@ class SidebarItem with Diagnosticable { this.focusNode, this.semanticLabel, this.disclosureItems, + this.expandDisclosureItems = false, this.trailing, }); @@ -58,6 +59,11 @@ class SidebarItem with Diagnosticable { /// If non-null and [leading] is null, a local animated icon is created final List? disclosureItems; + /// If true, the disclosure items will be expanded otherwise collapsed. + /// + /// Defaults to false. There is no impact if [disclosureItems] is null. + final bool expandDisclosureItems; + /// An optional trailing widget. /// /// Typically a text indicator of a count of items, like in this @@ -77,6 +83,8 @@ class SidebarItem with Diagnosticable { 'disclosure items', disclosureItems, )); + properties.add( + FlagProperty('expandDisclosureItems', value: expandDisclosureItems)); properties.add(DiagnosticsProperty('trailing', trailing)); } } diff --git a/lib/src/layout/sidebar/sidebar_items.dart b/lib/src/layout/sidebar/sidebar_items.dart index dc9329c5..6000789c 100644 --- a/lib/src/layout/sidebar/sidebar_items.dart +++ b/lib/src/layout/sidebar/sidebar_items.dart @@ -378,8 +378,7 @@ class __DisclosureSidebarItemState extends State<_DisclosureSidebarItem> late AnimationController _controller; late Animation _iconTurns; late Animation _heightFactor; - - bool _isExpanded = false; + late bool _isExpanded; bool get hasLeading => widget.item.leading != null; @@ -389,6 +388,11 @@ class __DisclosureSidebarItemState extends State<_DisclosureSidebarItem> _controller = AnimationController(duration: _kExpand, vsync: this); _heightFactor = _controller.drive(_easeInTween); _iconTurns = _controller.drive(_halfTween.chain(_easeInTween)); + + _isExpanded = widget.item.expandDisclosureItems; + if (_isExpanded) { + _controller.forward(); + } } void _handleTap() { diff --git a/pubspec.yaml b/pubspec.yaml index 81c1ed36..c6a71885 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: 2.0.8 +version: 2.0.9 homepage: "https://macosui.dev" repository: "https://github.com/GroovinChip/macos_ui" From 715bb247f9eb1b6772a7ba3330079011f7d046f5 Mon Sep 17 00:00:00 2001 From: Reuben Turner Date: Thu, 19 Sep 2024 11:08:37 -0400 Subject: [PATCH 150/151] chore: Update dependencies (#515) * chore: Update dependencies to unblock supporting macOS 15 * chore: update version & changelog * chore: export `WidgetState` instead of `MaterialState` --- CHANGELOG.md | 4 + example/macos/Podfile.lock | 6 +- example/macos/Runner/AppDelegate.swift | 2 +- example/pubspec.lock | 122 +++++----- example/pubspec.yaml | 12 +- lib/src/library.dart | 2 +- pubspec.lock | 312 ++----------------------- pubspec.yaml | 13 +- 8 files changed, 102 insertions(+), 371 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9f1c56de..8d87d8aa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## [2.1.0] +* Updated dependencies +* Support macOS 15 + ## [2.0.9] ### 🛠️ Updated 🛠️ * Add `expandDisclosureItems` flag to `SidebarItem` to optionally (default not changed) expand disclosure items initially diff --git a/example/macos/Podfile.lock b/example/macos/Podfile.lock index 2f92e758..475f42a3 100644 --- a/example/macos/Podfile.lock +++ b/example/macos/Podfile.lock @@ -39,9 +39,9 @@ SPEC CHECKSUMS: FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24 macos_ui: 6229a8922cd97bafb7d9636c8eb8dfb0744183ca macos_window_utils: 933f91f64805e2eb91a5bd057cf97cd097276663 - path_provider_foundation: 3784922295ac71e43754bd15e0653ccfd36a147c - url_launcher_macos: d2691c7dd33ed713bf3544850a623080ec693d95 + path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46 + url_launcher_macos: 5f437abeda8c85500ceb03f5c1938a8c5a705399 PODFILE CHECKSUM: ff0a9a3ce75ee73f200ca7e2f47745698c917ef9 -COCOAPODS: 1.13.0 +COCOAPODS: 1.14.3 diff --git a/example/macos/Runner/AppDelegate.swift b/example/macos/Runner/AppDelegate.swift index d53ef643..8e02df28 100644 --- a/example/macos/Runner/AppDelegate.swift +++ b/example/macos/Runner/AppDelegate.swift @@ -1,7 +1,7 @@ import Cocoa import FlutterMacOS -@NSApplicationMain +@main class AppDelegate: FlutterAppDelegate { override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { return true diff --git a/example/pubspec.lock b/example/pubspec.lock index d490fe3a..640f7187 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -53,10 +53,10 @@ packages: dependency: transitive description: name: crypto - sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab + sha256: ec30d999af904f33454ba22ed9a86162b35e52b44ac4807d1d93c288041d7d27 url: "https://pub.dev" source: hosted - version: "3.0.3" + version: "3.0.5" cupertino_icons: dependency: "direct main" description: @@ -85,10 +85,10 @@ packages: dependency: transitive description: name: ffi - sha256: "493f37e7df1804778ff3a53bd691d8692ddf69702cf4c1c1096a2e41b4779e21" + sha256: "16ed7b077ef01ad6170a3d0c57caa4a112a38d7a2ed5602e0aca9ca6f3d98da6" url: "https://pub.dev" source: hosted - version: "2.1.2" + version: "2.1.3" flutter: dependency: "direct main" description: flutter @@ -98,10 +98,10 @@ packages: dependency: "direct dev" description: name: flutter_lints - sha256: a25a15ebbdfc33ab1cd26c63a6ee519df92338a9c10f122adda92938253bef04 + sha256: "3f41d009ba7172d5ff9be5f6e6e6abb4300e263aab8866d2a0842ed2a70f8f0c" url: "https://pub.dev" source: hosted - version: "2.0.3" + version: "4.0.0" flutter_test: dependency: "direct dev" description: flutter @@ -116,26 +116,26 @@ packages: dependency: "direct main" description: name: google_fonts - sha256: e20ff62b158b96f392bfc8afe29dee1503c94fbea2cbe8186fd59b756b8ae982 + sha256: b1ac0fe2832c9cc95e5e88b57d627c5e68c223b9657f4b96e1487aa9098c7b82 url: "https://pub.dev" source: hosted - version: "5.1.0" + version: "6.2.1" gradient_borders: dependency: transitive description: name: gradient_borders - sha256: "69eeaff519d145a4c6c213ada1abae386bcc8981a4970d923e478ce7ba19e309" + sha256: b1cd969552c83f458ff755aa68e13a0327d09f06c3f42f471b423b01427f21f8 url: "https://pub.dev" source: hosted - version: "1.0.0" + version: "1.0.1" http: dependency: transitive description: name: http - sha256: "761a297c042deedc1ffbb156d6e2af13886bb305c2a343a4d972504cd67dd938" + sha256: b9c29a161230ee03d3ccf545097fccd9b87a5264228c5d348202e0f0c28f9010 url: "https://pub.dev" source: hosted - version: "1.2.1" + version: "1.2.2" http_parser: dependency: transitive description: @@ -148,41 +148,41 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "78eb209deea09858f5269f5a5b02be4049535f568c07b275096836f01ea323fa" + sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05" url: "https://pub.dev" source: hosted - version: "10.0.0" + version: "10.0.5" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: b46c5e37c19120a8a01918cfaf293547f47269f7cb4b0058f21531c2465d6ef0 + sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806" url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "3.0.5" leak_tracker_testing: dependency: transitive description: name: leak_tracker_testing - sha256: a597f72a664dbd293f3bfc51f9ba69816f84dcd403cdac7066cb3f6003f3ab47 + sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "3.0.1" lints: dependency: transitive description: name: lints - sha256: "0a217c6c989d21039f1498c3ed9f3ed71b354e69873f13a8dfc3c9fe76f1b452" + sha256: "976c774dd944a42e83e2467f4cc670daef7eed6295b10b36ae8c85bcbf828235" url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "4.0.0" macos_ui: dependency: "direct main" description: path: ".." relative: true source: path - version: "2.0.6" + version: "2.1.0" macos_window_utils: dependency: transitive description: @@ -203,18 +203,18 @@ packages: dependency: transitive description: name: material_color_utilities - sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec url: "https://pub.dev" source: hosted - version: "0.8.0" + version: "0.11.1" meta: dependency: transitive description: name: meta - sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04 + sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 url: "https://pub.dev" source: hosted - version: "1.11.0" + version: "1.15.0" nested: dependency: transitive description: @@ -235,26 +235,26 @@ packages: dependency: transitive description: name: path_provider - sha256: c9e7d3a4cd1410877472158bee69963a4579f78b68c65a2b7d40d1a7a88bb161 + sha256: fec0d61223fba3154d87759e3cc27fe2c8dc498f6386c6d6fc80d1afdd1bf378 url: "https://pub.dev" source: hosted - version: "2.1.3" + version: "2.1.4" path_provider_android: dependency: transitive description: name: path_provider_android - sha256: a248d8146ee5983446bf03ed5ea8f6533129a12b11f12057ad1b4a67a2b3b41d + sha256: "6f01f8e37ec30b07bc424b4deabac37cacb1bc7e2e515ad74486039918a37eb7" url: "https://pub.dev" source: hosted - version: "2.2.4" + version: "2.2.10" path_provider_foundation: dependency: transitive description: name: path_provider_foundation - sha256: "5a7999be66e000916500be4f15a3633ebceb8302719b47b9cc49ce924125350f" + sha256: f234384a3fdd67f989b4d54a5d73ca2a6c422fa55ae694381ae0f4375cd1ea16 url: "https://pub.dev" source: hosted - version: "2.3.2" + version: "2.4.0" path_provider_linux: dependency: transitive description: @@ -275,18 +275,18 @@ packages: dependency: transitive description: name: path_provider_windows - sha256: "8bc9f22eee8690981c22aa7fc602f5c85b497a6fb2ceb35ee5a5e5ed85ad8170" + sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 url: "https://pub.dev" source: hosted - version: "2.2.1" + version: "2.3.0" platform: dependency: transitive description: name: platform - sha256: "12220bb4b65720483f8fa9450b4332347737cf8213dd2840d8b2c823e47243ec" + sha256: "9b71283fc13df574056616011fb138fd3b793ea47cc509c189a6c3fa5f8a1a65" url: "https://pub.dev" source: hosted - version: "3.1.4" + version: "3.1.5" plugin_platform_interface: dependency: transitive description: @@ -352,10 +352,10 @@ packages: dependency: transitive description: name: test_api - sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b" + sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb" url: "https://pub.dev" source: hosted - version: "0.6.1" + version: "0.7.2" typed_data: dependency: transitive description: @@ -368,42 +368,42 @@ packages: dependency: "direct main" description: name: url_launcher - sha256: "6ce1e04375be4eed30548f10a315826fd933c1e493206eab82eed01f438c8d2e" + sha256: "21b704ce5fa560ea9f3b525b43601c678728ba46725bab9b01187b4831377ed3" url: "https://pub.dev" source: hosted - version: "6.2.6" + version: "6.3.0" url_launcher_android: dependency: transitive description: name: url_launcher_android - sha256: "360a6ed2027f18b73c8d98e159dda67a61b7f2e0f6ec26e86c3ada33b0621775" + sha256: e35a698ac302dd68e41f73250bd9517fe3ab5fa4f18fe4647a0872db61bacbab url: "https://pub.dev" source: hosted - version: "6.3.1" + version: "6.3.10" url_launcher_ios: dependency: transitive description: name: url_launcher_ios - sha256: "9149d493b075ed740901f3ee844a38a00b33116c7c5c10d7fb27df8987fb51d5" + sha256: e43b677296fadce447e987a2f519dcf5f6d1e527dc35d01ffab4fff5b8a7063e url: "https://pub.dev" source: hosted - version: "6.2.5" + version: "6.3.1" url_launcher_linux: dependency: transitive description: name: url_launcher_linux - sha256: ab360eb661f8879369acac07b6bb3ff09d9471155357da8443fd5d3cf7363811 + sha256: e2b9622b4007f97f504cd64c0128309dfb978ae66adbe944125ed9e1750f06af url: "https://pub.dev" source: hosted - version: "3.1.1" + version: "3.2.0" url_launcher_macos: dependency: transitive description: name: url_launcher_macos - sha256: b7244901ea3cf489c5335bdacda07264a6e960b1c1b1a9f91e4bc371d9e68234 + sha256: "9a1a42d5d2d95400c795b2914c36fdcb525870c752569438e4ebb09a2b5d90de" url: "https://pub.dev" source: hosted - version: "3.1.0" + version: "3.2.0" url_launcher_platform_interface: dependency: transitive description: @@ -416,18 +416,18 @@ packages: dependency: transitive description: name: url_launcher_web - sha256: "8d9e750d8c9338601e709cd0885f95825086bd8b642547f26bda435aade95d8a" + sha256: "772638d3b34c779ede05ba3d38af34657a05ac55b06279ea6edd409e323dca8e" url: "https://pub.dev" source: hosted - version: "2.3.1" + version: "2.3.3" url_launcher_windows: dependency: transitive description: name: url_launcher_windows - sha256: ecf9725510600aa2bb6d7ddabe16357691b6d2805f66216a97d1b881e21beff7 + sha256: "49c10f879746271804767cb45551ec5592cdab00ee105c06dddde1a98f73b185" url: "https://pub.dev" source: hosted - version: "3.1.1" + version: "3.1.2" vector_math: dependency: transitive description: @@ -440,26 +440,18 @@ packages: dependency: transitive description: name: vm_service - sha256: b3d56ff4341b8f182b96aceb2fa20e3dcb336b9f867bc0eafc0de10f1048e957 + sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d" url: "https://pub.dev" source: hosted - version: "13.0.0" + version: "14.2.5" web: dependency: transitive description: name: web - sha256: "97da13628db363c635202ad97068d47c5b8aa555808e7a9411963c533b449b27" - url: "https://pub.dev" - source: hosted - version: "0.5.1" - win32: - dependency: transitive - description: - name: win32 - sha256: "0eaf06e3446824099858367950a813472af675116bf63f008a4c2a75ae13e9cb" + sha256: d43c1d6b787bf0afad444700ae7f4db8827f701bc61c255ac8d328c6f4d52062 url: "https://pub.dev" source: hosted - version: "5.5.0" + version: "1.0.0" xdg_directories: dependency: transitive description: @@ -469,5 +461,5 @@ packages: source: hosted version: "1.0.4" sdks: - dart: ">=3.3.0 <4.0.0" - flutter: ">=3.19.0" + dart: ">=3.5.3 <4.0.0" + flutter: ">=3.24.0" diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 0a008b51..22e45eb8 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -4,23 +4,23 @@ publish_to: 'none' version: 1.0.0+1 environment: - sdk: '>=3.0.0 <4.0.0' + sdk: '>=3.5.3 <4.0.0' dependencies: flutter: sdk: flutter - cupertino_icons: ^1.0.5 + cupertino_icons: ^1.0.8 macos_ui: path: .. - provider: ^6.0.5 - google_fonts: ^5.1.0 - url_launcher: ^6.1.12 + provider: ^6.1.2 + google_fonts: ^6.2.1 + url_launcher: ^6.3.0 dev_dependencies: flutter_test: sdk: flutter - flutter_lints: ^2.0.2 + flutter_lints: ^4.0.0 flutter: assets: diff --git a/lib/src/library.dart b/lib/src/library.dart index 2273d447..b42ba8e8 100644 --- a/lib/src/library.dart +++ b/lib/src/library.dart @@ -29,7 +29,7 @@ export 'package:flutter/material.dart' TimeOfDay, DayPeriod, FlexibleSpaceBar, - MaterialState; + WidgetState; export 'package:flutter/widgets.dart'; export 'utils/utils.dart'; diff --git a/pubspec.lock b/pubspec.lock index 40925f5d..cf35ae3d 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1,22 +1,6 @@ # Generated by pub # See https://dart.dev/tools/pub/glossary#lockfile packages: - _fe_analyzer_shared: - dependency: transitive - description: - name: _fe_analyzer_shared - sha256: "0b2f2bd91ba804e53a61d757b986f89f1f9eaed5b11e4b2f5a2468d86d6c9fc7" - url: "https://pub.dev" - source: hosted - version: "67.0.0" - analyzer: - dependency: transitive - description: - name: analyzer - sha256: "37577842a27e4338429a1cbc32679d508836510b056f1eedf0c8d20e39c1383d" - url: "https://pub.dev" - source: hosted - version: "6.4.1" appkit_ui_element_colors: dependency: "direct main" description: @@ -25,14 +9,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.0" - args: - dependency: transitive - description: - name: args - sha256: "7cf60b9f0cc88203c5a190b4cd62a99feea42759a7fa695010eb5de1c0b2252a" - url: "https://pub.dev" - source: hosted - version: "2.5.0" async: dependency: transitive description: @@ -73,30 +49,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.18.0" - convert: - dependency: transitive - description: - name: convert - sha256: "0f08b14755d163f6e2134cb58222dd25ea2a2ee8a195e53983d57c075324d592" - url: "https://pub.dev" - source: hosted - version: "3.1.1" - coverage: - dependency: transitive - description: - name: coverage - sha256: "8acabb8306b57a409bf4c83522065672ee13179297a6bb0cb9ead73948df7c76" - url: "https://pub.dev" - source: hosted - version: "1.7.2" - crypto: - dependency: transitive - description: - name: crypto - sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab - url: "https://pub.dev" - source: hosted - version: "3.0.3" equatable: dependency: "direct main" description: @@ -113,14 +65,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.1" - file: - dependency: transitive - description: - name: file - sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c" - url: "https://pub.dev" - source: hosted - version: "7.0.0" flutter: dependency: "direct main" description: flutter @@ -130,111 +74,55 @@ packages: dependency: "direct dev" description: name: flutter_lints - sha256: a25a15ebbdfc33ab1cd26c63a6ee519df92338a9c10f122adda92938253bef04 + sha256: "3f41d009ba7172d5ff9be5f6e6e6abb4300e263aab8866d2a0842ed2a70f8f0c" url: "https://pub.dev" source: hosted - version: "2.0.3" + version: "4.0.0" flutter_test: dependency: "direct dev" description: flutter source: sdk version: "0.0.0" - frontend_server_client: - dependency: transitive - description: - name: frontend_server_client - sha256: "408e3ca148b31c20282ad6f37ebfa6f4bdc8fede5b74bc2f08d9d92b55db3612" - url: "https://pub.dev" - source: hosted - version: "3.2.0" - glob: - dependency: transitive - description: - name: glob - sha256: "0e7014b3b7d4dac1ca4d6114f82bf1782ee86745b9b42a92c9289c23d8a0ab63" - url: "https://pub.dev" - source: hosted - version: "2.1.2" gradient_borders: dependency: "direct main" description: name: gradient_borders - sha256: "69eeaff519d145a4c6c213ada1abae386bcc8981a4970d923e478ce7ba19e309" - url: "https://pub.dev" - source: hosted - version: "1.0.0" - http_multi_server: - dependency: transitive - description: - name: http_multi_server - sha256: "97486f20f9c2f7be8f514851703d0119c3596d14ea63227af6f7a481ef2b2f8b" - url: "https://pub.dev" - source: hosted - version: "3.2.1" - http_parser: - dependency: transitive - description: - name: http_parser - sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b" - url: "https://pub.dev" - source: hosted - version: "4.0.2" - io: - dependency: transitive - description: - name: io - sha256: "2ec25704aba361659e10e3e5f5d672068d332fc8ac516421d483a11e5cbd061e" + sha256: b1cd969552c83f458ff755aa68e13a0327d09f06c3f42f471b423b01427f21f8 url: "https://pub.dev" source: hosted - version: "1.0.4" - js: - dependency: transitive - description: - name: js - sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 - url: "https://pub.dev" - source: hosted - version: "0.6.7" + version: "1.0.1" leak_tracker: dependency: transitive description: name: leak_tracker - sha256: "78eb209deea09858f5269f5a5b02be4049535f568c07b275096836f01ea323fa" + sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05" url: "https://pub.dev" source: hosted - version: "10.0.0" + version: "10.0.5" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: b46c5e37c19120a8a01918cfaf293547f47269f7cb4b0058f21531c2465d6ef0 + sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806" url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "3.0.5" leak_tracker_testing: dependency: transitive description: name: leak_tracker_testing - sha256: a597f72a664dbd293f3bfc51f9ba69816f84dcd403cdac7066cb3f6003f3ab47 + sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "3.0.1" lints: dependency: transitive description: name: lints - sha256: "0a217c6c989d21039f1498c3ed9f3ed71b354e69873f13a8dfc3c9fe76f1b452" + sha256: "976c774dd944a42e83e2467f4cc670daef7eed6295b10b36ae8c85bcbf828235" url: "https://pub.dev" source: hosted - version: "2.1.1" - logging: - dependency: transitive - description: - name: logging - sha256: "623a88c9594aa774443aa3eb2d41807a48486b5613e67599fb4c41c0ad47c340" - url: "https://pub.dev" - source: hosted - version: "1.2.0" + version: "4.0.0" macos_window_utils: dependency: "direct main" description: @@ -255,50 +143,26 @@ packages: dependency: transitive description: name: material_color_utilities - sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec url: "https://pub.dev" source: hosted - version: "0.8.0" + version: "0.11.1" meta: dependency: transitive description: name: meta - sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04 + sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 url: "https://pub.dev" source: hosted - version: "1.11.0" - mime: - dependency: transitive - description: - name: mime - sha256: "2e123074287cc9fd6c09de8336dae606d1ddb88d9ac47358826db698c176a1f2" - url: "https://pub.dev" - source: hosted - version: "1.0.5" + version: "1.15.0" mocktail: dependency: "direct dev" description: name: mocktail - sha256: "80a996cd9a69284b3dc521ce185ffe9150cde69767c2d3a0720147d93c0cef53" - url: "https://pub.dev" - source: hosted - version: "0.3.0" - node_preamble: - dependency: transitive - description: - name: node_preamble - sha256: "6e7eac89047ab8a8d26cf16127b5ed26de65209847630400f9aefd7cd5c730db" - url: "https://pub.dev" - source: hosted - version: "2.0.2" - package_config: - dependency: transitive - description: - name: package_config - sha256: "1c5b77ccc91e4823a5af61ee74e6b972db1ef98c2ff5a18d3161c982a55448bd" + sha256: "890df3f9688106f25755f26b1c60589a92b3ab91a22b8b224947ad041bf172d8" url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "1.0.4" path: dependency: transitive description: @@ -315,75 +179,11 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.8" - pool: - dependency: transitive - description: - name: pool - sha256: "20fe868b6314b322ea036ba325e6fc0711a22948856475e2c2b6306e8ab39c2a" - url: "https://pub.dev" - source: hosted - version: "1.5.1" - pub_semver: - dependency: transitive - description: - name: pub_semver - sha256: "40d3ab1bbd474c4c2328c91e3a7df8c6dd629b79ece4c4bd04bee496a224fb0c" - url: "https://pub.dev" - source: hosted - version: "2.1.4" - shelf: - dependency: transitive - description: - name: shelf - sha256: ad29c505aee705f41a4d8963641f91ac4cee3c8fad5947e033390a7bd8180fa4 - url: "https://pub.dev" - source: hosted - version: "1.4.1" - shelf_packages_handler: - dependency: transitive - description: - name: shelf_packages_handler - sha256: "89f967eca29607c933ba9571d838be31d67f53f6e4ee15147d5dc2934fee1b1e" - url: "https://pub.dev" - source: hosted - version: "3.0.2" - shelf_static: - dependency: transitive - description: - name: shelf_static - sha256: a41d3f53c4adf0f57480578c1d61d90342cd617de7fc8077b1304643c2d85c1e - url: "https://pub.dev" - source: hosted - version: "1.1.2" - shelf_web_socket: - dependency: transitive - description: - name: shelf_web_socket - sha256: "9ca081be41c60190ebcb4766b2486a7d50261db7bd0f5d9615f2d653637a84c1" - url: "https://pub.dev" - source: hosted - version: "1.0.4" sky_engine: dependency: transitive description: flutter source: sdk version: "0.0.99" - source_map_stack_trace: - dependency: transitive - description: - name: source_map_stack_trace - sha256: "84cf769ad83aa6bb61e0aa5a18e53aea683395f196a6f39c4c881fb90ed4f7ae" - url: "https://pub.dev" - source: hosted - version: "2.1.1" - source_maps: - dependency: transitive - description: - name: source_maps - sha256: "708b3f6b97248e5781f493b765c3337db11c5d2c81c3094f10904bfa8004c703" - url: "https://pub.dev" - source: hosted - version: "0.10.12" source_span: dependency: transitive description: @@ -424,38 +224,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.2.1" - test: - dependency: transitive - description: - name: test - sha256: a1f7595805820fcc05e5c52e3a231aedd0b72972cb333e8c738a8b1239448b6f - url: "https://pub.dev" - source: hosted - version: "1.24.9" test_api: dependency: transitive description: name: test_api - sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b" - url: "https://pub.dev" - source: hosted - version: "0.6.1" - test_core: - dependency: transitive - description: - name: test_core - sha256: a757b14fc47507060a162cc2530d9a4a2f92f5100a952c7443b5cad5ef5b106a - url: "https://pub.dev" - source: hosted - version: "0.5.9" - typed_data: - dependency: transitive - description: - name: typed_data - sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c + sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb" url: "https://pub.dev" source: hosted - version: "1.3.2" + version: "0.7.2" vector_math: dependency: transitive description: @@ -468,50 +244,10 @@ packages: dependency: transitive description: name: vm_service - sha256: b3d56ff4341b8f182b96aceb2fa20e3dcb336b9f867bc0eafc0de10f1048e957 - url: "https://pub.dev" - source: hosted - version: "13.0.0" - watcher: - dependency: transitive - description: - name: watcher - sha256: "3d2ad6751b3c16cf07c7fca317a1413b3f26530319181b37e3b9039b84fc01d8" - url: "https://pub.dev" - source: hosted - version: "1.1.0" - web: - dependency: transitive - description: - name: web - sha256: "97da13628db363c635202ad97068d47c5b8aa555808e7a9411963c533b449b27" - url: "https://pub.dev" - source: hosted - version: "0.5.1" - web_socket_channel: - dependency: transitive - description: - name: web_socket_channel - sha256: "58c6666b342a38816b2e7e50ed0f1e261959630becd4c879c4f26bfa14aa5a42" - url: "https://pub.dev" - source: hosted - version: "2.4.5" - webkit_inspection_protocol: - dependency: transitive - description: - name: webkit_inspection_protocol - sha256: "87d3f2333bb240704cd3f1c6b5b7acd8a10e7f0bc28c28dcf14e782014f4a572" - url: "https://pub.dev" - source: hosted - version: "1.2.1" - yaml: - dependency: transitive - description: - name: yaml - sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5" + sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d" url: "https://pub.dev" source: hosted - version: "3.1.2" + version: "14.2.5" sdks: - dart: ">=3.3.0 <4.0.0" - flutter: ">=3.10.0" + dart: ">=3.5.3 <4.0.0" + flutter: ">=3.18.0-18.0.pre.54" diff --git a/pubspec.yaml b/pubspec.yaml index c6a71885..ca5712a3 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,26 +1,25 @@ name: macos_ui description: Flutter widgets and themes implementing the current macOS design language. -version: 2.0.9 +version: 2.1.0 homepage: "https://macosui.dev" repository: "https://github.com/GroovinChip/macos_ui" environment: - sdk: ">=3.0.0 <4.0.0" - flutter: ">=3.10.0" + sdk: ">=3.5.3 <4.0.0" dependencies: flutter: sdk: flutter - macos_window_utils: ^1.2.0 - gradient_borders: ^1.0.0 + macos_window_utils: ^1.5.0 + gradient_borders: ^1.0.1 appkit_ui_element_colors: ^1.0.0 equatable: ^2.0.5 dev_dependencies: flutter_test: sdk: flutter - flutter_lints: ^2.0.2 - mocktail: ^0.3.0 + flutter_lints: ^4.0.0 + mocktail: ^1.0.4 flutter: plugin: From 6313ee5bb707a2bbad631eb0b3a7fc9212d1aece Mon Sep 17 00:00:00 2001 From: GroovinChip Date: Thu, 19 Sep 2024 16:10:16 -0400 Subject: [PATCH 151/151] chore: remove straggling import --- lib/src/buttons/push_button.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/src/buttons/push_button.dart b/lib/src/buttons/push_button.dart index 418046ed..e95a9857 100644 --- a/lib/src/buttons/push_button.dart +++ b/lib/src/buttons/push_button.dart @@ -4,7 +4,6 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/rendering.dart'; import 'package:gradient_borders/gradient_borders.dart'; import 'package:macos_ui/macos_ui.dart'; -import 'package:macos_ui/src/enums/accent_color.dart'; import 'package:macos_ui/src/library.dart'; const _kMiniButtonSize = Size(26.0, 11.0);