diff --git a/.github/workflows/dart_code_metrics.yaml b/.github/workflows/dart_code_metrics.yaml index 964933df..a22ecac7 100644 --- a/.github/workflows/dart_code_metrics.yaml +++ b/.github/workflows/dart_code_metrics.yaml @@ -9,7 +9,7 @@ jobs: - uses: actions/checkout@v3 - name: Run Dart Code Metrics - uses: dart-code-checker/dart-code-metrics-action@v2 + uses: dart-code-checker/dart-code-metrics-action@v3 with: github_token: ${{ secrets.GITHUB_TOKEN }} pull_request_comment: true diff --git a/CHANGELOG.md b/CHANGELOG.md index da9c5875..994154c1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ ## [1.10.0] +🚨 Breaking Changes 🚨 +* `MacosScrollbar` has been completely overhauled and now resembles the native macOS scrollbar in appearance and + behavior. Previously, it wrapped the material scrollbar, and now creates a custom scrollbar that extends + `RawScrollbar`. This resulted in the removal of several material-based properties for the scrollbar, and + `ContentArea.builder` is once again a `ScrollableWidgetBuilder`! 🎉 +* Removed material-based scrollbar properties from `MacosScrollbarThemeData` + +Other changes: * Added implementation of `MacosDisclosureButton` +* Fixed a bug where `CapacityIndicator` only worked correctly for splits = 10 ## [1.9.1] * Adds optional `initialDate` to `MacosDatePicker` @@ -10,19 +19,16 @@ ## [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 +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 +Other changes: * Per Flutter 3.7.0: Replace deprecated `MacosTextField.toolbarOptions` with `MacosTextField.contextMenuBuilder` * Ensure the color panel releases when it is closed * Avoid render overflows in the `Sidebar` when the window height is resized below a certain threshold ([#325](https://github.com/GroovinChip/macos_ui/issues/325)) -* Update `MacosScrollBar.thumbVisibility` with the latest change introduced in Flutter 3.7 +* Update `MacosScrollbar.thumbVisibility` with the latest change introduced in Flutter 3.7 * Update `README.md` to address issues [#325](https://github.com/GroovinChip/macos_ui/issues/325) & [#332](https://github.com/GroovinChip/macos_ui/issues/332) -* Fixed a bug where `CapacityIndicator` only worked correctly for splits = 10 - - ## [1.7.6] * Fixed a bug where `MacosPopupButton` would report that a `ScrollController` was not attached to any views @@ -244,7 +250,7 @@ leading widget, and the font size of the item's label widget according to the gi * Added `==` and `hashCode` to various classes ## [0.7.1] -* Add generics support to `MacosRadioButton` - Thank you [Sacha Arbonel](https://github.com/sachaarbonel)! +* Add generics support to `MacosRadioButton` - Thank you, [Sacha Arbonel](https://github.com/sachaarbonel)! ## [0.7.0+2] * Add note in docs that a `Builder` is required for manual sidebar toggling to work. @@ -301,7 +307,7 @@ leading widget, and the font size of the item's label widget according to the gi * `Switch` -> `MacosSwitch` ## [0.2.4] -* Fix textfield prefix icon alignment +* Fix text field prefix icon alignment ## [0.2.3] * Add `canvasColor` to `MacosThemeData`. `Scaffold` now uses this as its default background color. @@ -333,7 +339,7 @@ leading widget, and the font size of the item's label widget according to the gi * 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 datas. + * Added the method `lerp` on all theme data classes. ## [0.1.1] * Implemented `Label` ([#61](https://github.com/GroovinChip/macos_ui/issues/61)) diff --git a/README.md b/README.md index a697ca0c..663d9d65 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ Guides, codelabs, and other documentation can be found at https://macosui.dev [![pub package](https://img.shields.io/pub/v/macos_ui.svg)](https://pub.dev/packages/macos_ui) [![pub package](https://img.shields.io/pub/publisher/macos_ui.svg)](https://pub.dev/packages/macos_ui) -[![Flutter Analysis](https://github.com/GroovinChip/macos_ui/actions/workflows/flutter_analysis.yml/badge.svg)](https://github.com/GroovinChip/macos_ui/actions/workflows/flutter_analysis.yml) +[![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) diff --git a/analysis_options.yaml b/analysis_options.yaml index e71274dd..d4a906dd 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -8,7 +8,8 @@ analyzer: plugins: - dart_code_metrics exclude: - - starter_app/** + - test/mock_canvas.dart + - test/recording_canvas.dart dart_code_metrics: metrics: @@ -25,3 +26,12 @@ dart_code_metrics: order: - constructors - public_fields + - private-fields + widgets-order: + - const fields + - init-state-method + - did-change-dependencies-method + - did-update-widget-method + - private-methods + - dispose-method + - build-method diff --git a/example/lib/main.dart b/example/lib/main.dart index 0b33c9af..447f1fcd 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -171,11 +171,11 @@ class _WidgetGalleryState extends State { ], ), minWidth: 200, - builder: (context, _) { + builder: (context, scrollController) { return SidebarItems( currentIndex: pageIndex, onChanged: (i) => setState(() => pageIndex = i), - scrollController: ScrollController(), + scrollController: scrollController, itemSize: SidebarItemSize.large, items: [ const SidebarItem( diff --git a/example/lib/pages/buttons_page.dart b/example/lib/pages/buttons_page.dart index b57536a2..dafc5ea2 100644 --- a/example/lib/pages/buttons_page.dart +++ b/example/lib/pages/buttons_page.dart @@ -71,9 +71,9 @@ class _ButtonsPageState extends State { }, ), ContentArea( - builder: (context) { + builder: (context, scrollController) { return SingleChildScrollView( - // controller: _, + controller: scrollController, padding: const EdgeInsets.all(20), child: Column( children: [ @@ -168,7 +168,7 @@ class _ButtonsPageState extends State { ), children: [ ContentArea( - builder: (context) { + builder: (context, _) { return Center( child: PushButton( buttonSize: ButtonSize.large, diff --git a/example/lib/pages/colors_page.dart b/example/lib/pages/colors_page.dart index 37bc59f2..7a9df4fe 100644 --- a/example/lib/pages/colors_page.dart +++ b/example/lib/pages/colors_page.dart @@ -28,7 +28,7 @@ class _ColorsPageState extends State { ), children: [ ContentArea( - builder: (context) { + builder: (context, scrollController) { return SingleChildScrollView( padding: const EdgeInsets.all(20), child: Column( diff --git a/example/lib/pages/dialogs_page.dart b/example/lib/pages/dialogs_page.dart index 53db5028..b7645978 100644 --- a/example/lib/pages/dialogs_page.dart +++ b/example/lib/pages/dialogs_page.dart @@ -29,7 +29,7 @@ class _DialogsPageState extends State { ), children: [ ContentArea( - builder: (context) { + builder: (context, scrollController) { return SingleChildScrollView( padding: const EdgeInsets.all(20), child: Center( diff --git a/example/lib/pages/fields_page.dart b/example/lib/pages/fields_page.dart index e7865f98..906cb13f 100644 --- a/example/lib/pages/fields_page.dart +++ b/example/lib/pages/fields_page.dart @@ -28,7 +28,7 @@ class _FieldsPageState extends State { ), children: [ ContentArea( - builder: (context) { + builder: (context, scrollController) { return SingleChildScrollView( padding: const EdgeInsets.all(20), child: Column( diff --git a/example/lib/pages/indicators_page.dart b/example/lib/pages/indicators_page.dart index e5923d2d..7ab0ee46 100644 --- a/example/lib/pages/indicators_page.dart +++ b/example/lib/pages/indicators_page.dart @@ -33,7 +33,7 @@ class _IndicatorsPageState extends State { ), children: [ ContentArea( - builder: (context) { + builder: (context, scrollController) { return SingleChildScrollView( padding: const EdgeInsets.all(20), child: Column( diff --git a/example/lib/pages/selectors_page.dart b/example/lib/pages/selectors_page.dart index a8c1634a..e44a1936 100644 --- a/example/lib/pages/selectors_page.dart +++ b/example/lib/pages/selectors_page.dart @@ -28,9 +28,8 @@ class _SelectorsPageState extends State { ), children: [ ContentArea( - builder: (context) { + 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 2497c2ea..786b11b0 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) { + builder: (context, scrollController) { 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 4e77fc86..af01f229 100644 --- a/example/lib/pages/toolbar_page.dart +++ b/example/lib/pages/toolbar_page.dart @@ -156,26 +156,28 @@ class _ToolbarPageState extends State { ], ), children: [ - ContentArea(builder: (context) { - return SingleChildScrollView( - padding: const EdgeInsets.all(30), - child: Center( - child: Column( - children: const [ - Text( - "The toolbar appears below the title bar of the macOS app or integrates with it.", - textAlign: TextAlign.center, - ), - SizedBox(height: 20.0), - Text( - "It provides convenient access to frequently used commands and features.", - textAlign: TextAlign.center, - ), - ], + ContentArea( + builder: (context, scrollController) { + return SingleChildScrollView( + padding: const EdgeInsets.all(30), + child: Center( + child: Column( + children: const [ + Text( + "The toolbar appears below the title bar of the macOS app or integrates with it.", + textAlign: TextAlign.center, + ), + SizedBox(height: 20.0), + Text( + "It provides convenient access to frequently used commands and features.", + textAlign: TextAlign.center, + ), + ], + ), ), - ), - ); - }), + ); + }, + ), ], ); } diff --git a/lib/macos_ui.dart b/lib/macos_ui.dart index bd2828b4..4bd80c30 100644 --- a/lib/macos_ui.dart +++ b/lib/macos_ui.dart @@ -37,7 +37,7 @@ 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/indicators/scrollbar.dart'; +export 'src/layout/scrollbar.dart'; export 'src/indicators/slider.dart'; export 'src/labels/label.dart'; export 'src/labels/tooltip.dart'; diff --git a/lib/src/buttons/back_button.dart b/lib/src/buttons/back_button.dart index 088b382c..5bce067b 100644 --- a/lib/src/buttons/back_button.dart +++ b/lib/src/buttons/back_button.dart @@ -88,12 +88,6 @@ class MacosBackButtonState extends State _opacityTween.end = 1.0; } - @override - void dispose() { - _animationController.dispose(); - super.dispose(); - } - @visibleForTesting bool buttonHeldDown = false; @@ -129,6 +123,12 @@ class MacosBackButtonState extends State }); } + @override + void dispose() { + _animationController.dispose(); + super.dispose(); + } + @override Widget build(BuildContext context) { final bool enabled = widget.enabled; diff --git a/lib/src/buttons/disclosure_button.dart b/lib/src/buttons/disclosure_button.dart index ac07b218..3a8a3d6b 100644 --- a/lib/src/buttons/disclosure_button.dart +++ b/lib/src/buttons/disclosure_button.dart @@ -88,12 +88,6 @@ class MacosDisclosureButtonState extends State _opacityTween.end = 1.0; } - @override - void dispose() { - _animationController.dispose(); - super.dispose(); - } - @visibleForTesting bool buttonHeldDown = false; @@ -129,6 +123,12 @@ class MacosDisclosureButtonState extends State }); } + @override + void dispose() { + _animationController.dispose(); + super.dispose(); + } + @override Widget build(BuildContext context) { final bool enabled = widget.enabled; diff --git a/lib/src/buttons/help_button.dart b/lib/src/buttons/help_button.dart index 70ae07f7..0c5d5ece 100644 --- a/lib/src/buttons/help_button.dart +++ b/lib/src/buttons/help_button.dart @@ -114,12 +114,6 @@ class HelpButtonState extends State _opacityTween.end = widget.pressedOpacity ?? 1.0; } - @override - void dispose() { - _animationController.dispose(); - super.dispose(); - } - @visibleForTesting bool buttonHeldDown = false; @@ -155,6 +149,12 @@ class HelpButtonState extends State }); } + @override + void dispose() { + _animationController.dispose(); + super.dispose(); + } + @override Widget build(BuildContext context) { final bool enabled = widget.enabled; diff --git a/lib/src/buttons/icon_button.dart b/lib/src/buttons/icon_button.dart index e8e6e9f5..9b339aa7 100644 --- a/lib/src/buttons/icon_button.dart +++ b/lib/src/buttons/icon_button.dart @@ -161,12 +161,6 @@ class MacosIconButtonState extends State _opacityTween.end = widget.pressedOpacity ?? 1.0; } - @override - void dispose() { - _animationController.dispose(); - super.dispose(); - } - @visibleForTesting bool buttonHeldDown = false; @@ -202,6 +196,12 @@ class MacosIconButtonState extends State }); } + @override + void dispose() { + _animationController.dispose(); + super.dispose(); + } + @override Widget build(BuildContext context) { final bool enabled = widget.enabled; diff --git a/lib/src/buttons/popup_button.dart b/lib/src/buttons/popup_button.dart index 481a25fe..a08cb113 100644 --- a/lib/src/buttons/popup_button.dart +++ b/lib/src/buttons/popup_button.dart @@ -1007,11 +1007,6 @@ class _MacosPopupButtonState extends State> late Map> _actionMap; late FocusHighlightMode _focusHighlightMode; - // Only used if needed to create _internalNode. - FocusNode _createFocusNode() { - return FocusNode(debugLabel: '${widget.runtimeType}'); - } - @override void initState() { super.initState(); @@ -1034,14 +1029,22 @@ class _MacosPopupButtonState extends State> } @override - void dispose() { - WidgetsBinding.instance.removeObserver(this); - _removeMacosPopupRoute(); - WidgetsBinding.instance.focusManager - .removeHighlightModeListener(_handleFocusHighlightModeChange); - focusNode!.removeListener(_handleFocusChanged); - _internalNode?.dispose(); - super.dispose(); + void didUpdateWidget(MacosPopupButton oldWidget) { + super.didUpdateWidget(oldWidget); + if (widget.focusNode != oldWidget.focusNode) { + oldWidget.focusNode?.removeListener(_handleFocusChanged); + if (widget.focusNode == null) { + _internalNode ??= _createFocusNode(); + } + _hasPrimaryFocus = focusNode!.hasPrimaryFocus; + focusNode!.addListener(_handleFocusChanged); + } + _updateSelectedIndex(); + } + + // Only used if needed to create _internalNode. + FocusNode _createFocusNode() { + return FocusNode(debugLabel: '${widget.runtimeType}'); } void _removeMacosPopupRoute() { @@ -1062,20 +1065,6 @@ class _MacosPopupButtonState extends State> setState(() => _focusHighlightMode = mode); } - @override - void didUpdateWidget(MacosPopupButton oldWidget) { - super.didUpdateWidget(oldWidget); - if (widget.focusNode != oldWidget.focusNode) { - oldWidget.focusNode?.removeListener(_handleFocusChanged); - if (widget.focusNode == null) { - _internalNode ??= _createFocusNode(); - } - _hasPrimaryFocus = focusNode!.hasPrimaryFocus; - focusNode!.addListener(_handleFocusChanged); - } - _updateSelectedIndex(); - } - void _updateSelectedIndex() { if (widget.items == null || widget.items!.isEmpty || @@ -1100,9 +1089,6 @@ class _MacosPopupButtonState extends State> } } - TextStyle? get _textStyle => - widget.style ?? MacosTheme.of(context).typography.body; - void _handleTap() { final TextDirection? textDirection = Directionality.maybeOf(context); const EdgeInsetsGeometry menuMargin = @@ -1161,6 +1147,20 @@ class _MacosPopupButtonState extends State> widget.onTap?.call(); } + @override + void dispose() { + WidgetsBinding.instance.removeObserver(this); + _removeMacosPopupRoute(); + WidgetsBinding.instance.focusManager + .removeHighlightModeListener(_handleFocusHighlightModeChange); + focusNode!.removeListener(_handleFocusChanged); + _internalNode?.dispose(); + super.dispose(); + } + + TextStyle? get _textStyle => + widget.style ?? MacosTheme.of(context).typography.body; + bool get _enabled => widget.items != null && widget.items!.isNotEmpty && diff --git a/lib/src/buttons/pulldown_button.dart b/lib/src/buttons/pulldown_button.dart index 9d218364..dbe7e0d1 100644 --- a/lib/src/buttons/pulldown_button.dart +++ b/lib/src/buttons/pulldown_button.dart @@ -746,11 +746,6 @@ class _MacosPulldownButtonState extends State late FocusHighlightMode _focusHighlightMode; PulldownButtonState _pullDownButtonState = PulldownButtonState.enabled; - // Only used if needed to create _internalNode. - FocusNode _createFocusNode() { - return FocusNode(debugLabel: '${widget.runtimeType}'); - } - @override void initState() { super.initState(); @@ -772,14 +767,21 @@ class _MacosPulldownButtonState extends State } @override - void dispose() { - WidgetsBinding.instance.removeObserver(this); - _removeMacosPulldownRoute(); - WidgetsBinding.instance.focusManager - .removeHighlightModeListener(_handleFocusHighlightModeChange); - focusNode!.removeListener(_handleFocusChanged); - _internalNode?.dispose(); - super.dispose(); + void didUpdateWidget(MacosPulldownButton oldWidget) { + super.didUpdateWidget(oldWidget); + if (widget.focusNode != oldWidget.focusNode) { + oldWidget.focusNode?.removeListener(_handleFocusChanged); + if (widget.focusNode == null) { + _internalNode ??= _createFocusNode(); + } + _hasPrimaryFocus = focusNode!.hasPrimaryFocus; + focusNode!.addListener(_handleFocusChanged); + } + } + + // Only used if needed to create _internalNode. + FocusNode _createFocusNode() { + return FocusNode(debugLabel: '${widget.runtimeType}'); } void _removeMacosPulldownRoute() { @@ -800,19 +802,6 @@ class _MacosPulldownButtonState extends State setState(() => _focusHighlightMode = mode); } - @override - void didUpdateWidget(MacosPulldownButton oldWidget) { - super.didUpdateWidget(oldWidget); - if (widget.focusNode != oldWidget.focusNode) { - oldWidget.focusNode?.removeListener(_handleFocusChanged); - if (widget.focusNode == null) { - _internalNode ??= _createFocusNode(); - } - _hasPrimaryFocus = focusNode!.hasPrimaryFocus; - focusNode!.addListener(_handleFocusChanged); - } - } - TextStyle? get _textStyle => widget.style ?? MacosTheme.of(context).typography.body; @@ -885,6 +874,17 @@ class _MacosPulldownButtonState extends State } } + @override + void dispose() { + WidgetsBinding.instance.removeObserver(this); + _removeMacosPulldownRoute(); + WidgetsBinding.instance.focusManager + .removeHighlightModeListener(_handleFocusHighlightModeChange); + focusNode!.removeListener(_handleFocusChanged); + _internalNode?.dispose(); + super.dispose(); + } + @override Widget build(BuildContext context) { final buttonHeight = _hasIcon ? 28.0 : 20.0; diff --git a/lib/src/buttons/push_button.dart b/lib/src/buttons/push_button.dart index 27759e1c..6f759c1d 100644 --- a/lib/src/buttons/push_button.dart +++ b/lib/src/buttons/push_button.dart @@ -179,15 +179,6 @@ class PushButtonState extends State _opacityTween.end = widget.pressedOpacity ?? 1.0; } - @override - void dispose() { - _animationController.dispose(); - super.dispose(); - } - - @visibleForTesting - bool buttonHeldDown = false; - void _handleTapDown(TapDownDetails event) { if (!buttonHeldDown) { buttonHeldDown = true; @@ -220,6 +211,15 @@ class PushButtonState extends State }); } + @override + void dispose() { + _animationController.dispose(); + super.dispose(); + } + + @visibleForTesting + bool buttonHeldDown = false; + @override Widget build(BuildContext context) { assert(debugCheckHasMacosTheme(context)); diff --git a/lib/src/fields/search_field.dart b/lib/src/fields/search_field.dart index e550dde4..d7eddb63 100644 --- a/lib/src/fields/search_field.dart +++ b/lib/src/fields/search_field.dart @@ -252,82 +252,6 @@ class _MacosSearchFieldState extends State> { }); } - @override - void dispose() { - suggestionStream.close(); - if (widget.controller == null) { - searchController!.dispose(); - } - if (widget.focusNode == null) { - _focus!.dispose(); - } - super.dispose(); - } - - @override - Widget build(BuildContext context) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - CompositedTransformTarget( - link: _layerLink, - child: MacosTextField( - placeholder: widget.placeholder, - prefix: const Padding( - padding: EdgeInsets.symmetric(), - child: MacosIcon(CupertinoIcons.search), - ), - clearButtonMode: OverlayVisibilityMode.editing, - onTap: () { - suggestionStream.sink.add(widget.results); - if (mounted) { - setState(() { - isResultExpanded = true; - }); - } - widget.onTap?.call(); - }, - controller: widget.controller ?? searchController, - focusNode: _focus, - style: widget.style, - onChanged: (query) { - final searchResult = []; - if (query.isEmpty) { - suggestionStream.sink.add(widget.results); - return; - } - if (widget.results != null) { - for (final suggestion in widget.results!) { - if (suggestion.searchKey - .toLowerCase() - .contains(query.toLowerCase())) { - searchResult.add(suggestion); - } - } - } - suggestionStream.sink.add(searchResult); - widget.onChanged?.call(query); - }, - decoration: widget.decoration, - focusedDecoration: widget.focusedDecoration, - padding: widget.padding, - placeholderStyle: widget.placeholderStyle, - textAlign: widget.textAlign, - autocorrect: widget.autocorrect, - autofocus: widget.autofocus, - maxLines: widget.maxLines, - minLines: widget.minLines, - expands: widget.expands, - maxLength: widget.maxLength, - maxLengthEnforcement: widget.maxLengthEnforcement, - inputFormatters: widget.inputFormatters, - enabled: widget.enabled, - ), - ), - ], - ); - } - OverlayEntry _createOverlay() { final renderBox = context.findRenderObject() as RenderBox; final size = renderBox.size; @@ -442,6 +366,82 @@ class _MacosSearchFieldState extends State> { }, ); } + + @override + void dispose() { + suggestionStream.close(); + if (widget.controller == null) { + searchController!.dispose(); + } + if (widget.focusNode == null) { + _focus!.dispose(); + } + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + CompositedTransformTarget( + link: _layerLink, + child: MacosTextField( + placeholder: widget.placeholder, + prefix: const Padding( + padding: EdgeInsets.symmetric(), + child: MacosIcon(CupertinoIcons.search), + ), + clearButtonMode: OverlayVisibilityMode.editing, + onTap: () { + suggestionStream.sink.add(widget.results); + if (mounted) { + setState(() { + isResultExpanded = true; + }); + } + widget.onTap?.call(); + }, + controller: widget.controller ?? searchController, + focusNode: _focus, + style: widget.style, + onChanged: (query) { + final searchResult = []; + if (query.isEmpty) { + suggestionStream.sink.add(widget.results); + return; + } + if (widget.results != null) { + for (final suggestion in widget.results!) { + if (suggestion.searchKey + .toLowerCase() + .contains(query.toLowerCase())) { + searchResult.add(suggestion); + } + } + } + suggestionStream.sink.add(searchResult); + widget.onChanged?.call(query); + }, + decoration: widget.decoration, + focusedDecoration: widget.focusedDecoration, + padding: widget.padding, + placeholderStyle: widget.placeholderStyle, + textAlign: widget.textAlign, + autocorrect: widget.autocorrect, + autofocus: widget.autofocus, + maxLines: widget.maxLines, + minLines: widget.minLines, + expands: widget.expands, + maxLength: widget.maxLength, + maxLengthEnforcement: widget.maxLengthEnforcement, + inputFormatters: widget.inputFormatters, + enabled: widget.enabled, + ), + ), + ], + ); + } } /// An item to show in the search results of a search field. diff --git a/lib/src/fields/text_field.dart b/lib/src/fields/text_field.dart index 99f390cc..9387b029 100644 --- a/lib/src/fields/text_field.dart +++ b/lib/src/fields/text_field.dart @@ -435,6 +435,15 @@ class MacosTextField extends StatefulWidget { keyboardType = keyboardType ?? (maxLines == 1 ? TextInputType.text : TextInputType.multiline); + static Widget _defaultContextMenuBuilder( + BuildContext context, + EditableTextState editableTextState, + ) { + return CupertinoAdaptiveTextSelectionToolbar.editableText( + editableTextState: editableTextState, + ); + } + /// Controls the text being edited. /// /// If null, this widget will create its own [TextEditingController]. @@ -548,15 +557,6 @@ class MacosTextField extends StatefulWidget { /// * [CupertinoAdaptiveTextSelectionToolbar], which is built by default. final EditableTextContextMenuBuilder? contextMenuBuilder; - static Widget _defaultContextMenuBuilder( - BuildContext context, - EditableTextState editableTextState, - ) { - return CupertinoAdaptiveTextSelectionToolbar.editableText( - editableTextState: editableTextState, - ); - } - /// {@macro flutter.material.InputDecorator.textAlignVertical} final TextAlignVertical? textAlignVertical; @@ -920,10 +920,6 @@ class _MacosTextFieldState extends State _effectiveFocusNode.addListener(_handleFocusChanged); } - void _handleFocusChanged() { - setState(() {}); - } - @override void didUpdateWidget(MacosTextField oldWidget) { super.didUpdateWidget(oldWidget); @@ -937,11 +933,8 @@ class _MacosTextFieldState extends State _effectiveFocusNode.canRequestFocus = widget.enabled ?? true; } - @override - void restoreState(RestorationBucket? oldBucket, bool initialRestore) { - if (_controller != null) { - _registerController(); - } + void _handleFocusChanged() { + setState(() {}); } void _registerController() { @@ -960,18 +953,6 @@ class _MacosTextFieldState extends State } } - @override - String? get restorationId => widget.restorationId; - - @override - void dispose() { - _focusNode?.dispose(); - _controller?.dispose(); - super.dispose(); - } - - EditableTextState get _editableText => editableTextKey.currentState!; - void _requestKeyboard() { _editableText.requestKeyboard(); } @@ -1008,9 +989,6 @@ class _MacosTextFieldState extends State } } - @override - bool get wantKeepAlive => _controller?.value.text.isNotEmpty == true; - bool _shouldShowAttachment({ required OverlayVisibilityMode attachment, required bool hasText, @@ -1050,26 +1028,6 @@ class _MacosTextFieldState extends State ); } - // True if any surrounding decoration widgets will be shown. - bool get _hasDecoration { - return widget.placeholder != null || - widget.clearButtonMode != OverlayVisibilityMode.never || - widget.prefix != null || - widget.suffix != null; - } - - // Provide default behavior if widget.textAlignVertical is not set. - // TextField has top alignment by default, unless it has decoration - // like a prefix or suffix, in which case it's aligned to the center. - TextAlignVertical get _textAlignVertical { - if (widget.textAlignVertical != null) { - return widget.textAlignVertical!; - } - return widget.maxLines == null || widget.maxLines! > 1 - ? TextAlignVertical.center - : TextAlignVertical.top; - } - Widget _addTextDependentAttachments( Widget editableText, TextStyle textStyle, @@ -1185,6 +1143,48 @@ class _MacosTextFieldState extends State ); } + @override + void restoreState(RestorationBucket? oldBucket, bool initialRestore) { + if (_controller != null) { + _registerController(); + } + } + + @override + String? get restorationId => widget.restorationId; + + @override + void dispose() { + _focusNode?.dispose(); + _controller?.dispose(); + super.dispose(); + } + + EditableTextState get _editableText => editableTextKey.currentState!; + + @override + bool get wantKeepAlive => _controller?.value.text.isNotEmpty == true; + + // True if any surrounding decoration widgets will be shown. + bool get _hasDecoration { + return widget.placeholder != null || + widget.clearButtonMode != OverlayVisibilityMode.never || + widget.prefix != null || + widget.suffix != null; + } + + // Provide default behavior if widget.textAlignVertical is not set. + // TextField has top alignment by default, unless it has decoration + // like a prefix or suffix, in which case it's aligned to the center. + TextAlignVertical get _textAlignVertical { + if (widget.textAlignVertical != null) { + return widget.textAlignVertical!; + } + return widget.maxLines == null || widget.maxLines! > 1 + ? TextAlignVertical.center + : TextAlignVertical.top; + } + @override // ignore: code-metrics Widget build(BuildContext context) { diff --git a/lib/src/indicators/scrollbar.dart b/lib/src/indicators/scrollbar.dart deleted file mode 100644 index 98c60b33..00000000 --- a/lib/src/indicators/scrollbar.dart +++ /dev/null @@ -1,148 +0,0 @@ -import 'package:flutter/material.dart' as m; -import 'package:macos_ui/macos_ui.dart'; -import 'package:macos_ui/src/library.dart'; - -/// A Macos Design scrollbar. -/// -/// To add a scrollbar to a [ScrollView], wrap the scroll view -/// widget in a [MacosScrollbar] widget. -/// -/// {@macro flutter.widgets.Scrollbar} -/// -/// The color of the Scrollbar will change when dragged. A hover animation is -/// also triggered when used on web and desktop platforms. A scrollbar track -/// can also been drawn when triggered by a hover event, which is controlled by -/// [trackVisibility]. The thickness of the track and scrollbar thumb will -/// become larger when hovering, unless overridden by [hoverThickness]. -/// -/// See also: -/// -/// * [RawScrollbar], a basic scrollbar that fades in and out, extended -/// by this class to add more animations and behaviors. -/// * [MacosScrollbarTheme], which configures the Scrollbar's appearance. -/// * [m.Scrollbar], a Material style scrollbar. -/// * [CupertinoScrollbar], an iOS style scrollbar. -/// * [ListView], which displays a linear, scrollable list of children. -/// * [GridView], which displays a 2 dimensional, scrollable array of children. -class MacosScrollbar extends StatelessWidget { - /// Creates a macos design scrollbar that by default will connect to the - /// closest Scrollable descendent of [child]. - /// - /// The [child] should be a source of [ScrollNotification] notifications, - /// typically a [Scrollable] widget. - /// - /// If the [controller] is null, the default behavior is to - /// enable scrollbar dragging using the [PrimaryScrollController]. - /// - /// When null, [thickness] defaults to 8.0 pixels on desktop and web, and 4.0 - /// pixels when on mobile platforms. A null [radius] will result in a default - /// of an 8.0 pixel circular radius about the corners of the scrollbar thumb, - /// except for when executing on [TargetPlatform.android], which will render the - /// thumb without a radius. - const MacosScrollbar({ - super.key, - required this.child, - this.controller, - this.isAlwaysShown, - this.trackVisibility, - this.thickness, - this.radius, - this.notificationPredicate, - this.interactive, - }); - - /// {@macro flutter.widgets.Scrollbar.child} - final Widget child; - - /// {@macro flutter.widgets.Scrollbar.controller} - final ScrollController? controller; - - /// {@macro flutter.widgets.Scrollbar.isAlwaysShown} - final bool? isAlwaysShown; - - /// Controls if the track will always be visible or not. - /// - /// If this property is null, then [MacosScrollbarThemeData.showTrackOnHover] of - /// [MacosThemeData.scrollbarTheme] is used. If that is also null, the default value - /// is false. - final bool? trackVisibility; - - /// The thickness of the scrollbar in the cross axis of the scrollable. - /// - /// If null, the default value is platform dependent. On [TargetPlatform.android], - /// the default thickness is 4.0 pixels. On [TargetPlatform.iOS], - /// [CupertinoScrollbar.defaultThickness] is used. The remaining platforms have a - /// default thickness of 8.0 pixels. - final double? thickness; - - /// The [Radius] of the scrollbar thumb's rounded rectangle corners. - /// - /// If null, the default value is platform dependent. On [TargetPlatform.android], - /// no radius is applied to the scrollbar thumb. On [TargetPlatform.iOS], - /// [CupertinoScrollbar.defaultRadius] is used. The remaining platforms have a - /// default [Radius.circular] of 8.0 pixels. - final Radius? radius; - - /// {@macro flutter.widgets.Scrollbar.interactive} - final bool? interactive; - - /// {@macro flutter.widgets.Scrollbar.notificationPredicate} - final ScrollNotificationPredicate? notificationPredicate; - - @override - Widget build(BuildContext context) { - assert(debugCheckHasMacosTheme(context)); - final theme = MacosScrollbarTheme.of(context); - return m.ScrollbarTheme( - data: m.ScrollbarThemeData( - crossAxisMargin: theme.crossAxisMargin, - mainAxisMargin: theme.mainAxisMargin, - interactive: theme.interactive, - thumbVisibility: m.MaterialStateProperty.resolveWith((states) { - return isAlwaysShown; - }), - trackVisibility: m.MaterialStateProperty.resolveWith((states) { - return trackVisibility; - }), - minThumbLength: theme.minThumbLength, - radius: theme.radius, - thickness: m.MaterialStateProperty.resolveWith((states) { - if (states.contains(m.MaterialState.hovered)) { - return theme.hoveringThickness ?? theme.thickness; - } - return theme.thickness; - }), - thumbColor: m.MaterialStateProperty.resolveWith((states) { - if (states.contains(m.MaterialState.hovered)) { - return theme.hoveringThumbColor ?? theme.thumbColor; - } else if (states.contains(m.MaterialState.dragged)) { - return theme.draggingThumbColor ?? theme.thumbColor; - } - return theme.thumbColor; - }), - trackBorderColor: m.MaterialStateProperty.resolveWith((states) { - if (states.contains(m.MaterialState.hovered)) { - return theme.hoveringTrackBorderColor ?? theme.trackBorderColor; - } - return theme.trackBorderColor; - }), - trackColor: m.MaterialStateProperty.resolveWith((states) { - if (states.contains(m.MaterialState.hovered)) { - return theme.hoveringTrackColor ?? theme.trackColor; - } - return theme.trackColor; - }), - ), - child: m.Scrollbar( - controller: controller, - thumbVisibility: isAlwaysShown, - trackVisibility: trackVisibility, - thickness: thickness, - radius: radius, - interactive: interactive, - notificationPredicate: notificationPredicate, - child: child, - ), - ); - } -} diff --git a/lib/src/labels/tooltip.dart b/lib/src/labels/tooltip.dart index 0c42f005..35022953 100644 --- a/lib/src/labels/tooltip.dart +++ b/lib/src/labels/tooltip.dart @@ -236,6 +236,12 @@ class _MacosTooltipState extends State super.deactivate(); } + void _handleLongPress() { + _longPressActivated = true; + final bool tooltipCreated = ensureTooltipVisible(); + if (tooltipCreated) Feedback.forLongPress(context); + } + @override void dispose() { GestureBinding.instance.pointerRouter @@ -247,12 +253,6 @@ class _MacosTooltipState extends State super.dispose(); } - void _handleLongPress() { - _longPressActivated = true; - final bool tooltipCreated = ensureTooltipVisible(); - if (tooltipCreated) Feedback.forLongPress(context); - } - @override Widget build(BuildContext context) { assert(debugCheckHasMacosTheme(context)); diff --git a/lib/src/layout/content_area.dart b/lib/src/layout/content_area.dart index 0012292b..5026e260 100644 --- a/lib/src/layout/content_area.dart +++ b/lib/src/layout/content_area.dart @@ -16,7 +16,7 @@ class ContentArea extends StatelessWidget { }) : super(key: const Key('macos_scaffold_content_area')); /// The builder that creates a child to display in this widget. - final WidgetBuilder? builder; + final ScrollableWidgetBuilder? builder; /// Specifies the minimum width that this [ContentArea] can have. final double minWidth; @@ -30,7 +30,7 @@ class ContentArea extends StatelessWidget { child: SafeArea( left: false, right: false, - child: builder!(context), + child: builder!(context, ScrollController()), ), ); } diff --git a/lib/src/layout/resizable_pane.dart b/lib/src/layout/resizable_pane.dart index 4c6c78f2..c71ff8c2 100644 --- a/lib/src/layout/resizable_pane.dart +++ b/lib/src/layout/resizable_pane.dart @@ -1,7 +1,7 @@ import 'dart:math' as math show max, min; import 'package:flutter/services.dart' show SystemMouseCursor; -import 'package:macos_ui/src/indicators/scrollbar.dart'; +import 'package:macos_ui/src/layout/scrollbar.dart'; import 'package:macos_ui/src/library.dart'; import 'package:macos_ui/src/theme/macos_theme.dart'; @@ -160,12 +160,6 @@ class _ResizablePaneState extends State { _scrollController.addListener(() => setState(() {})); } - @override - void dispose() { - _scrollController.dispose(); - super.dispose(); - } - @override void didUpdateWidget(covariant ResizablePane oldWidget) { super.didUpdateWidget(oldWidget); @@ -180,6 +174,12 @@ class _ResizablePaneState extends State { } } + @override + void dispose() { + _scrollController.dispose(); + super.dispose(); + } + @override Widget build(BuildContext context) { final media = MediaQuery.of(context); diff --git a/lib/src/layout/scrollbar.dart b/lib/src/layout/scrollbar.dart new file mode 100644 index 00000000..334aced5 --- /dev/null +++ b/lib/src/layout/scrollbar.dart @@ -0,0 +1,213 @@ +import 'package:flutter/services.dart'; +import 'package:macos_ui/macos_ui.dart'; +import 'package:macos_ui/src/library.dart'; + +const double _kScrollbarMinLength = 36.0; +const double _kScrollbarMinOverscrollLength = 8.0; +const Duration _kScrollbarTimeToFade = Duration(milliseconds: 1200); +const Duration _kScrollbarFadeDuration = Duration(milliseconds: 250); +const Duration _kScrollbarResizeDuration = Duration(milliseconds: 100); +const double _kScrollbarMainAxisMargin = 3.0; +const double _kScrollbarCrossAxisMargin = 2.0; + +/// A macOS-style scrollbar. +/// +/// Applications built with `macos_ui` will automatically apply this widget +/// to [ScrollView]s as the default scrollbar. +/// +/// To explicitly add a scrollbar to a [ScrollView], wrap the scroll view +/// widget in a [MacosScrollbar] widget. +/// +/// {@macro flutter.widgets.Scrollbar} +/// +/// See also: +/// +/// * [RawScrollbar], a basic scrollbar that fades in and out, extended +/// by this class internally to add animations and behaviors that aim to +/// match the native macOS scrollbar. +/// * [MacosScrollbarTheme], which configures this widget's appearance. +/// * [Scrollbar], a Material style scrollbar. +/// * [CupertinoScrollbar], an iOS style scrollbar. +class MacosScrollbar extends StatelessWidget { + /// Creates a macOS-style scrollbar that by default will connect to the + /// closest Scrollable descendant of [child]. + /// + /// The [child] should be a source of [ScrollNotification] notifications, + /// typically a [Scrollable] widget. + /// + /// If the [controller] is null, the default behavior is to + /// enable scrollbar dragging using the [PrimaryScrollController]. + const MacosScrollbar({ + super.key, + required this.child, + this.controller, + this.thumbVisibility, + this.thickness, + this.thicknessWhileHovering, + this.radius, + this.notificationPredicate, + this.scrollbarOrientation, + }); + + /// {@macro flutter.widgets.Scrollbar.child} + final Widget child; + + /// {@macro flutter.widgets.Scrollbar.controller} + final ScrollController? controller; + + /// {@macro flutter.widgets.Scrollbar.thumbVisibility} + final bool? thumbVisibility; + + /// The thickness of the scrollbar in the cross axis of the scrollable. + /// + /// Defaults to 6.0. + final double? thickness; + + /// The thickness of the scrollbar in the cross axis of the scrollable while + /// the mouse cursor is hovering over the scrollbar. + /// + /// Defaults to 9.0. + final double? thicknessWhileHovering; + + /// The [Radius] of the scrollbar thumb's rounded rectangle corners. + /// + /// Defaults to `const Radius.circular(25)`. + final Radius? radius; + + /// {@macro flutter.widgets.Scrollbar.notificationPredicate} + final ScrollNotificationPredicate? notificationPredicate; + + /// {@macro flutter.widgets.Scrollbar.scrollbarOrientation} + final ScrollbarOrientation? scrollbarOrientation; + + @override + Widget build(BuildContext context) { + assert(debugCheckHasMacosTheme(context)); + final scrollbarTheme = MacosScrollbarTheme.of(context); + assert(scrollbarTheme.thickness != null); + assert(scrollbarTheme.thicknessWhileHovering != null); + + return _RawMacosScrollBar( + controller: controller, + thumbVisibility: thumbVisibility ?? scrollbarTheme.thumbVisibility, + thickness: thickness ?? scrollbarTheme.thickness, + thicknessWhileHovering: + thicknessWhileHovering ?? scrollbarTheme.thicknessWhileHovering!, + notificationPredicate: notificationPredicate, + scrollbarOrientation: scrollbarOrientation, + effectiveThumbColor: scrollbarTheme.thumbColor!, + radius: radius ?? scrollbarTheme.radius, + child: child, + ); + } +} + +class _RawMacosScrollBar extends RawScrollbar { + const _RawMacosScrollBar({ + required super.child, + super.controller, + bool? thumbVisibility, + super.thickness, + required this.thicknessWhileHovering, + ScrollNotificationPredicate? notificationPredicate, + super.scrollbarOrientation, + required this.effectiveThumbColor, + super.radius, + }) : assert(thickness != null && thickness < double.infinity), + assert(thicknessWhileHovering < double.infinity), + super( + thumbVisibility: thumbVisibility ?? false, + fadeDuration: _kScrollbarFadeDuration, + timeToFade: _kScrollbarTimeToFade, + notificationPredicate: + notificationPredicate ?? defaultScrollNotificationPredicate, + ); + + final double thicknessWhileHovering; + final Color effectiveThumbColor; + + @override + RawScrollbarState<_RawMacosScrollBar> createState() => + _RawMacosScrollBarState(); +} + +class _RawMacosScrollBarState extends RawScrollbarState<_RawMacosScrollBar> { + late AnimationController _thumbThicknessAnimationController; + late AnimationController _trackColorAnimationController; + late Animation _trackColorTween; + bool _hoverIsActive = false; + + double get _thickness { + return widget.thickness! + + _thumbThicknessAnimationController.value * + (widget.thicknessWhileHovering - widget.thickness!); + } + + @override + void initState() { + super.initState(); + _thumbThicknessAnimationController = AnimationController( + vsync: this, + duration: _kScrollbarResizeDuration, + ); + _trackColorAnimationController = AnimationController( + vsync: this, + duration: _kScrollbarResizeDuration, + ); + _trackColorTween = ColorTween( + begin: MacosColors.transparent, + end: widget.effectiveThumbColor.withOpacity(.15), + ).animate(_trackColorAnimationController); + _thumbThicknessAnimationController.addListener(() { + updateScrollbarPainter(); + }); + _trackColorAnimationController.addListener(() { + updateScrollbarPainter(); + }); + } + + @override + void updateScrollbarPainter() { + scrollbarPainter + ..color = widget.effectiveThumbColor + ..trackColor = _trackColorTween.value + ..textDirection = Directionality.of(context) + ..thickness = _thickness + ..mainAxisMargin = _kScrollbarMainAxisMargin + ..crossAxisMargin = _kScrollbarCrossAxisMargin + ..radius = widget.radius + ..padding = MediaQuery.of(context).padding + ..minLength = _kScrollbarMinLength + ..minOverscrollLength = _kScrollbarMinOverscrollLength + ..scrollbarOrientation = widget.scrollbarOrientation; + } + + @override + void handleHover(PointerHoverEvent event) { + super.handleHover(event); + if (isPointerOverScrollbar(event.position, event.kind, forHover: true)) { + setState(() => _hoverIsActive = true); + _thumbThicknessAnimationController.forward(); + _trackColorAnimationController.forward(); + } else if (_hoverIsActive) { + setState(() => _hoverIsActive = false); + _thumbThicknessAnimationController.reverse(); + _trackColorAnimationController.reverse(); + } + } + + @override + void handleHoverExit(PointerExitEvent event) { + super.handleHoverExit(event); + setState(() => _hoverIsActive = false); + _thumbThicknessAnimationController.reverse(); + _trackColorAnimationController.reverse(); + } + + @override + void dispose() { + _thumbThicknessAnimationController.dispose(); + _trackColorAnimationController.dispose(); + super.dispose(); + } +} diff --git a/lib/src/layout/sidebar/sidebar_items.dart b/lib/src/layout/sidebar/sidebar_items.dart index f13779d8..aada626f 100644 --- a/lib/src/layout/sidebar/sidebar_items.dart +++ b/lib/src/layout/sidebar/sidebar_items.dart @@ -364,12 +364,6 @@ class __DisclosureSidebarItemState extends State<_DisclosureSidebarItem> _iconTurns = _controller.drive(_halfTween.chain(_easeInTween)); } - @override - void dispose() { - _controller.dispose(); - super.dispose(); - } - void _handleTap() { setState(() { _isExpanded = !_isExpanded; @@ -461,6 +455,12 @@ class __DisclosureSidebarItemState extends State<_DisclosureSidebarItem> ); } + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + @override Widget build(BuildContext context) { assert(debugCheckHasMacosTheme(context)); diff --git a/lib/src/layout/tab_view/tab_view.dart b/lib/src/layout/tab_view/tab_view.dart index 03d9b91b..fa32c734 100644 --- a/lib/src/layout/tab_view/tab_view.dart +++ b/lib/src/layout/tab_view/tab_view.dart @@ -74,6 +74,31 @@ class _MacosTabViewState extends State { late List _childrenWithKey; int? _currentIndex; + @override + void initState() { + super.initState(); + _updateChildren(); + } + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + _updateTabController(); + _currentIndex = widget.controller.index; + } + + @override + void didUpdateWidget(MacosTabView oldWidget) { + super.didUpdateWidget(oldWidget); + if (widget.controller != oldWidget.controller) { + _updateTabController(); + _currentIndex = widget.controller.index; + } + if (widget.children != oldWidget.children) { + _updateChildren(); + } + } + int get _tabRotation { switch (widget.position) { case MacosTabPosition.left: @@ -101,29 +126,8 @@ class _MacosTabViewState extends State { }); } - @override - void initState() { - super.initState(); - _updateChildren(); - } - - @override - void didChangeDependencies() { - super.didChangeDependencies(); - _updateTabController(); - _currentIndex = widget.controller.index; - } - - @override - void didUpdateWidget(MacosTabView oldWidget) { - super.didUpdateWidget(oldWidget); - if (widget.controller != oldWidget.controller) { - _updateTabController(); - _currentIndex = widget.controller.index; - } - if (widget.children != oldWidget.children) { - _updateChildren(); - } + void _updateChildren() { + _childrenWithKey = KeyedSubtree.ensureUniqueKeysForList(widget.children); } @override @@ -132,10 +136,6 @@ class _MacosTabViewState extends State { super.dispose(); } - void _updateChildren() { - _childrenWithKey = KeyedSubtree.ensureUniqueKeysForList(widget.children); - } - @override Widget build(BuildContext context) { assert(() { diff --git a/lib/src/layout/window.dart b/lib/src/layout/window.dart index d8cb563c..9ce38eb0 100644 --- a/lib/src/layout/window.dart +++ b/lib/src/layout/window.dart @@ -2,7 +2,7 @@ import 'dart:math' as math; import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; -import 'package:macos_ui/src/indicators/scrollbar.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'; @@ -82,13 +82,6 @@ class _MacosWindowState extends State { } } - @override - void dispose() { - _sidebarScrollController.dispose(); - _endSidebarScrollController.dispose(); - super.dispose(); - } - @override void didUpdateWidget(covariant MacosWindow old) { super.didUpdateWidget(old); @@ -118,6 +111,13 @@ class _MacosWindowState extends State { }); } + @override + void dispose() { + _sidebarScrollController.dispose(); + _endSidebarScrollController.dispose(); + super.dispose(); + } + @override // ignore: code-metrics Widget build(BuildContext context) { @@ -221,7 +221,7 @@ class _MacosWindowState extends State { _sidebarScrollController.offset > 0.0) Divider(thickness: 1, height: 1, color: dividerColor), if (widget.sidebar!.top != null && - constraints.maxHeight < 81) + constraints.maxHeight > 81) Padding( padding: const EdgeInsets.symmetric(horizontal: 8.0), child: widget.sidebar!.top!, @@ -237,7 +237,7 @@ class _MacosWindowState extends State { ), ), if (widget.sidebar?.bottom != null && - constraints.maxHeight < 141) + constraints.maxHeight > 141) Padding( padding: const EdgeInsets.all(16.0), child: widget.sidebar!.bottom!, diff --git a/lib/src/library.dart b/lib/src/library.dart index b473c28f..9d05ec7e 100644 --- a/lib/src/library.dart +++ b/lib/src/library.dart @@ -27,7 +27,8 @@ export 'package:flutter/material.dart' kElevationToShadow, DateUtils, TimeOfDay, - DayPeriod; + DayPeriod, + MaterialState; export 'package:flutter/widgets.dart'; export 'utils.dart'; diff --git a/lib/src/macos_app.dart b/lib/src/macos_app.dart index fb5fb372..c4068f30 100644 --- a/lib/src/macos_app.dart +++ b/lib/src/macos_app.dart @@ -302,22 +302,6 @@ class MacosApp extends StatefulWidget { class _MacosAppState extends State { bool get _usesRouter => widget.routerDelegate != null; - @override - Widget build(BuildContext context) { - // leaves room for assertions, etc - Widget result = _buildMacosApp(context); - return result; - } - - Iterable> get _localizationsDelegates sync* { - if (widget.localizationsDelegates != null) { - yield* widget.localizationsDelegates!; - } - yield DefaultMaterialLocalizations.delegate; - yield DefaultCupertinoLocalizations.delegate; - yield DefaultWidgetsLocalizations.delegate; - } - Widget _macosBuilder(BuildContext context, Widget? child) { final mode = widget.themeMode ?? ThemeMode.system; final platformBrightness = MediaQuery.platformBrightnessOf(context); @@ -381,6 +365,7 @@ class _MacosAppState extends State { debugShowCheckedModeBanner: widget.debugShowCheckedModeBanner, shortcuts: widget.shortcuts, actions: widget.actions, + scrollBehavior: widget.scrollBehavior, ); } return c.CupertinoApp( @@ -409,8 +394,25 @@ class _MacosAppState extends State { debugShowCheckedModeBanner: widget.debugShowCheckedModeBanner, shortcuts: widget.shortcuts, actions: widget.actions, + scrollBehavior: widget.scrollBehavior, ); } + + @override + Widget build(BuildContext context) { + // leaves room for assertions, etc + Widget result = _buildMacosApp(context); + return result; + } + + Iterable> get _localizationsDelegates sync* { + if (widget.localizationsDelegates != null) { + yield* widget.localizationsDelegates!; + } + yield DefaultMaterialLocalizations.delegate; + yield DefaultCupertinoLocalizations.delegate; + yield DefaultWidgetsLocalizations.delegate; + } } /// Describes how [Scrollable] widgets behave for [MacosApp]s. @@ -428,6 +430,11 @@ class MacosScrollBehavior extends ScrollBehavior { /// [MacosScrollbar]s based on the current platform and provided [ScrollableDetails]. const MacosScrollBehavior(); + /*@override + Set get dragDevices => { + PointerDeviceKind.mouse, + };*/ + @override Widget buildScrollbar(context, child, details) { // When modifying this function, consider modifying the implementation in diff --git a/lib/src/selectors/date_picker.dart b/lib/src/selectors/date_picker.dart index d11d3d4b..4fec86a3 100644 --- a/lib/src/selectors/date_picker.dart +++ b/lib/src/selectors/date_picker.dart @@ -64,6 +64,18 @@ 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; @@ -140,18 +152,6 @@ class _MacosDatePickerState extends State { widget.onDateChanged.call(_formatAsDateTime()); } - // 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', - ]; - // Creates the day headers - Su, Mo, Tu, We, Th, Fr, Sa List _dayHeaders( TextStyle? headerStyle, diff --git a/lib/src/theme/macos_theme.dart b/lib/src/theme/macos_theme.dart index e2c084d8..ac03ea2d 100644 --- a/lib/src/theme/macos_theme.dart +++ b/lib/src/theme/macos_theme.dart @@ -2,6 +2,11 @@ import 'package:flutter/foundation.dart'; 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), +); + /// Applies a macOS-style theme to descendant macOS widgets. /// /// Affects the color and text styles of macOS widgets whose styling @@ -238,7 +243,13 @@ class MacosThemeData with Diagnosticable { brightness: _brightness, textStyle: typography.callout, ); - scrollbarTheme ??= const MacosScrollbarThemeData(); + scrollbarTheme ??= MacosScrollbarThemeData( + thickness: 6.0, + thicknessWhileHovering: 9.0, + thumbColor: isDark ? _kScrollbarColor.darkColor : _kScrollbarColor.color, + radius: const Radius.circular(25), + thumbVisibility: false, + ); macosIconButtonTheme ??= MacosIconButtonThemeData( backgroundColor: MacosColors.transparent, disabledColor: isDark diff --git a/lib/src/theme/scrollbar_theme.dart b/lib/src/theme/scrollbar_theme.dart index a62378a5..2655e4de 100644 --- a/lib/src/theme/scrollbar_theme.dart +++ b/lib/src/theme/scrollbar_theme.dart @@ -70,21 +70,10 @@ class MacosScrollbarThemeData with Diagnosticable { /// Creates a theme that can be used for [MacosThemeData.scrollbarTheme]. const MacosScrollbarThemeData({ this.thickness, - this.hoveringThickness, - this.showTrackOnHover, - this.isAlwaysShown, + this.thicknessWhileHovering, + this.thumbVisibility, this.radius, this.thumbColor, - this.hoveringThumbColor, - this.draggingThumbColor, - this.trackColor, - this.hoveringTrackColor, - this.trackBorderColor, - this.hoveringTrackBorderColor, - this.crossAxisMargin, - this.mainAxisMargin, - this.minThumbLength, - this.interactive, }); /// Overrides the default value of [MacosScrollbar.thickness] in all @@ -93,19 +82,11 @@ class MacosScrollbarThemeData with Diagnosticable { /// Overrides the default value of [MacosScrollbar.hoverThickness] in all /// descendant [MacosScrollbar] widgets when hovering is active. - final double? hoveringThickness; + final double? thicknessWhileHovering; - /// Overrides the default value of [MacosScrollbar.trackVisibility] in all + /// Overrides the default value of [MacosScrollbar.thumbVisibility] in all /// descendant [MacosScrollbar] widgets. - final bool? showTrackOnHover; - - /// Overrides the default value of [MacosScrollbar.isAlwaysShown] in all - /// descendant [MacosScrollbar] widgets. - final bool? isAlwaysShown; - - /// Overrides the default value of [MacosScrollbar.interactive] in all - /// descendant [MacosScrollbar] widgets. - final bool? interactive; + final bool? thumbVisibility; /// Overrides the default value of [MacosScrollbar.radius] in all /// descendant widgets. @@ -115,99 +96,23 @@ class MacosScrollbarThemeData with Diagnosticable { /// descendant [MacosScrollbar] widgets. final Color? thumbColor; - /// Overrides the default [Color] of the [MacosScrollbar] thumb in all - /// descendant [MacosScrollbar] widgets when hovering is active. - final Color? hoveringThumbColor; - - /// Overrides the default [Color] of the [MacosScrollbar] thumb in all - /// descendant [MacosScrollbar] widgets when dragging is active. - final Color? draggingThumbColor; - - /// Overrides the default [Color] of the [MacosScrollbar] track when - /// [showTrackOnHover] is true in all descendant [MacosScrollbar] widgets. - final Color? trackColor; - - /// Overrides the default [Color] of the [MacosScrollbar] track when - /// [showTrackOnHover] is true in all descendant [MacosScrollbar] widgets - /// when hovering is active. - final Color? hoveringTrackColor; - - /// Overrides the default [Color] of the [MacosScrollbar] track border when - /// [showTrackOnHover] is true in all descendant [MacosScrollbar] widgets. - final Color? trackBorderColor; - - /// Overrides the default [Color] of the [MacosScrollbar] track border when - /// [showTrackOnHover] is true in all descendant [MacosScrollbar] widgets - /// when hovering is active. - final Color? hoveringTrackBorderColor; - - /// Overrides the default value of the [ScrollbarPainter.crossAxisMargin] - /// property in all descendant [MacosScrollbar] widgets. - /// - /// See also: - /// - /// * [ScrollbarPainter.crossAxisMargin], which sets the distance from the - /// scrollbar's side to the nearest edge in logical pixels. - final double? crossAxisMargin; - - /// Overrides the default value of the [ScrollbarPainter.mainAxisMargin] - /// property in all descendant [MacosScrollbar] widgets. - /// - /// See also: - /// - /// * [ScrollbarPainter.mainAxisMargin], which sets the distance from the - /// scrollbar's start and end to the edge of the viewport in logical pixels. - final double? mainAxisMargin; - - /// Overrides the default value of the [ScrollbarPainter.minLength] - /// property in all descendant [MacosScrollbar] widgets. - /// - /// See also: - /// - /// * [ScrollbarPainter.minLength], which sets the preferred smallest size - /// the scrollbar can shrink to when the total scrollable extent is large, - /// the current visible viewport is small, and the viewport is not - /// overscrolled. - final double? minThumbLength; - /// Creates a copy of this object with the given fields replaced with the /// new values. MacosScrollbarThemeData copyWith({ double? thickness, - double? hoveringThickness, + double? thicknessWhileHovering, bool? showTrackOnHover, - bool? isAlwaysShown, - bool? interactive, + bool? thumbVisibility, Radius? radius, Color? thumbColor, - Color? hoveringThumbColor, - Color? draggingThumbColor, - Color? trackColor, - Color? hoveringTrackColor, - Color? trackBorderColor, - Color? hoveringTrackBorderColor, - double? crossAxisMargin, - double? mainAxisMargin, - double? minThumbLength, }) { return MacosScrollbarThemeData( thickness: thickness ?? this.thickness, - hoveringThickness: hoveringThickness ?? this.hoveringThickness, - showTrackOnHover: showTrackOnHover ?? this.showTrackOnHover, - isAlwaysShown: isAlwaysShown ?? this.isAlwaysShown, - interactive: interactive ?? this.interactive, + thicknessWhileHovering: + thicknessWhileHovering ?? this.thicknessWhileHovering, + thumbVisibility: thumbVisibility ?? this.thumbVisibility, radius: radius ?? this.radius, thumbColor: thumbColor ?? this.thumbColor, - hoveringThumbColor: hoveringThumbColor ?? this.hoveringThumbColor, - draggingThumbColor: draggingThumbColor ?? this.draggingThumbColor, - trackColor: trackColor ?? this.trackColor, - hoveringTrackColor: hoveringTrackColor ?? this.hoveringTrackColor, - trackBorderColor: trackBorderColor ?? this.trackBorderColor, - hoveringTrackBorderColor: - hoveringTrackBorderColor ?? this.hoveringTrackBorderColor, - crossAxisMargin: crossAxisMargin ?? this.crossAxisMargin, - mainAxisMargin: mainAxisMargin ?? this.mainAxisMargin, - minThumbLength: minThumbLength ?? this.minThumbLength, ); } @@ -224,29 +129,25 @@ class MacosScrollbarThemeData with Diagnosticable { ) { return MacosScrollbarThemeData( thickness: lerpDouble(a?.thickness, b?.thickness, t), - hoveringThickness: - lerpDouble(a?.hoveringThickness, b?.hoveringThickness, t), - showTrackOnHover: t < 0.5 ? a?.showTrackOnHover : b?.showTrackOnHover, - isAlwaysShown: t < 0.5 ? a?.isAlwaysShown : b?.isAlwaysShown, - interactive: t < 0.5 ? a?.interactive : b?.interactive, - radius: Radius.lerp(a?.radius, b?.radius, t), - thumbColor: Color.lerp(a?.thumbColor, b?.thumbColor, t), - hoveringThumbColor: - Color.lerp(a?.hoveringThumbColor, b?.hoveringThumbColor, t), - draggingThumbColor: - Color.lerp(a?.draggingThumbColor, b?.draggingThumbColor, t), - trackColor: Color.lerp(a?.trackColor, b?.trackColor, t), - hoveringTrackColor: - Color.lerp(a?.hoveringThumbColor, b?.hoveringThumbColor, t), - trackBorderColor: Color.lerp(a?.trackBorderColor, b?.trackBorderColor, t), - hoveringTrackBorderColor: Color.lerp( - a?.hoveringTrackBorderColor, - b?.hoveringTrackBorderColor, + thicknessWhileHovering: lerpDouble( + a?.thicknessWhileHovering, + b?.thicknessWhileHovering, t, ), - crossAxisMargin: lerpDouble(a?.crossAxisMargin, b?.crossAxisMargin, t), - mainAxisMargin: lerpDouble(a?.mainAxisMargin, b?.mainAxisMargin, t), - minThumbLength: lerpDouble(a?.minThumbLength, b?.minThumbLength, t), + thumbVisibility: t < 0.5 ? a?.thumbVisibility : b?.thumbVisibility, + radius: Radius.lerp(a?.radius, b?.radius, t), + thumbColor: Color.lerp(a?.thumbColor, b?.thumbColor, t), + ); + } + + /// Merges this [MacosScrollbarThemeData] with another. + MacosScrollbarThemeData merge(MacosScrollbarThemeData? other) { + if (other == null) return this; + return copyWith( + thickness: other.thickness, + thumbVisibility: other.thumbVisibility, + radius: other.radius, + thumbColor: other.thumbColor, ); } @@ -254,21 +155,10 @@ class MacosScrollbarThemeData with Diagnosticable { int get hashCode { return Object.hash( thickness, - hoveringThickness, - showTrackOnHover, - isAlwaysShown, - interactive, + thicknessWhileHovering, + thumbVisibility, radius, thumbColor, - hoveringThumbColor, - draggingThumbColor, - trackColor, - hoveringTrackColor, - trackBorderColor, - hoveringTrackBorderColor, - crossAxisMargin, - mainAxisMargin, - minThumbLength, ); } @@ -278,21 +168,10 @@ class MacosScrollbarThemeData with Diagnosticable { if (other.runtimeType != runtimeType) return false; return other is MacosScrollbarThemeData && other.thickness == thickness && - other.hoveringThickness == hoveringThickness && - other.showTrackOnHover == showTrackOnHover && - other.isAlwaysShown == isAlwaysShown && - other.interactive == interactive && + other.thicknessWhileHovering == thicknessWhileHovering && + other.thumbVisibility == thumbVisibility && other.radius == radius && - other.thumbColor == thumbColor && - other.hoveringThumbColor == hoveringThumbColor && - other.draggingThumbColor == draggingThumbColor && - other.trackColor == trackColor && - other.hoveringTrackColor == hoveringTrackColor && - other.trackBorderColor == trackBorderColor && - other.hoveringTrackBorderColor == hoveringTrackBorderColor && - other.crossAxisMargin == crossAxisMargin && - other.mainAxisMargin == mainAxisMargin && - other.minThumbLength == minThumbLength; + other.thumbColor == thumbColor; } @override @@ -302,90 +181,18 @@ class MacosScrollbarThemeData with Diagnosticable { DiagnosticsProperty('thickness', thickness, defaultValue: null), ); properties.add(DiagnosticsProperty( - 'hoveringThickness', - hoveringThickness, - defaultValue: null, - )); - properties.add(DiagnosticsProperty( - 'showTrackOnHover', - showTrackOnHover, + 'thicknessWhileHovering', + thicknessWhileHovering, defaultValue: null, )); properties.add(DiagnosticsProperty( - 'isAlwaysShown', - isAlwaysShown, + 'thumbVisibility', + thumbVisibility, defaultValue: null, )); - properties.add( - DiagnosticsProperty('interactive', interactive, defaultValue: null), - ); properties.add( DiagnosticsProperty('radius', radius, defaultValue: null), ); properties.add(ColorProperty('thumbColor', thumbColor, defaultValue: null)); - properties.add(ColorProperty( - 'hoveringThumbColor', - hoveringThumbColor, - defaultValue: null, - )); - properties.add(ColorProperty( - 'draggingThumbColor', - draggingThumbColor, - defaultValue: null, - )); - properties.add(ColorProperty('trackColor', trackColor, defaultValue: null)); - properties.add( - ColorProperty( - 'hoveringTrackColor', - hoveringTrackColor, - defaultValue: null, - ), - ); - properties.add( - ColorProperty('trackBorderColor', trackBorderColor, defaultValue: null), - ); - properties.add(ColorProperty( - 'hoveringTrackBorderColor', - hoveringTrackBorderColor, - defaultValue: null, - )); - properties.add(DiagnosticsProperty( - 'crossAxisMargin', - crossAxisMargin, - defaultValue: null, - )); - properties.add(DiagnosticsProperty( - 'mainAxisMargin', - mainAxisMargin, - defaultValue: null, - )); - properties.add(DiagnosticsProperty( - 'minThumbLength', - minThumbLength, - defaultValue: null, - )); - } - - /// Merges this [MacosScrollbarThemeData] with another. - MacosScrollbarThemeData merge(MacosScrollbarThemeData? other) { - if (other == null) return this; - return copyWith( - thickness: other.thickness, - hoveringThickness: other.hoveringThickness, - showTrackOnHover: other.showTrackOnHover, - isAlwaysShown: other.isAlwaysShown, - interactive: other.interactive, - radius: other.radius, - thumbColor: other.thumbColor, - hoveringThumbColor: other.hoveringThumbColor, - draggingThumbColor: other.draggingThumbColor, - trackColor: other.trackColor, - hoveringTrackColor: other.hoveringTrackColor, - trackBorderColor: other.trackBorderColor, - hoveringTrackBorderColor: other.hoveringTrackBorderColor, - crossAxisMargin: other.crossAxisMargin, - mainAxisMargin: other.mainAxisMargin, - minThumbLength: other.minThumbLength, - ); } } diff --git a/pubspec.lock b/pubspec.lock index eb16ba9a..75773850 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -5,18 +5,18 @@ packages: dependency: transitive description: name: _fe_analyzer_shared - sha256: d976d24314f193899a3079b14fe336215a63a3b1e1c3743eabba8f83e049e9a9 + sha256: "0c80aeab9bc807ab10022cd3b2f4cf2ecdf231949dc1ddd9442406a003f19201" url: "https://pub.dev" source: hosted - version: "49.0.0" + version: "52.0.0" analyzer: dependency: transitive description: name: analyzer - sha256: "40ba2c6d2ab41a66476f8f1f099da6be0795c1b47221f5e2c5f8ad6048cdffae" + sha256: cd8ee83568a77f3ae6b913a36093a1c9b1264e7cb7f834d9ddd2311dade9c1f4 url: "https://pub.dev" source: hosted - version: "5.1.0" + version: "5.4.0" analyzer_plugin: dependency: transitive description: @@ -37,10 +37,10 @@ packages: dependency: transitive description: name: args - sha256: "139d809800a412ebb26a3892da228b2d0ba36f0ef5d9a82166e5e52ec8d61611" + sha256: "4cab82a83ffef80b262ddedf47a0a8e56ee6fbf7fe21e6e768b02792034dd440" url: "https://pub.dev" source: hosted - version: "2.3.2" + version: "2.4.0" async: dependency: transitive description: @@ -93,10 +93,10 @@ packages: dependency: transitive description: name: coverage - sha256: "961c4aebd27917269b1896382c7cb1b1ba81629ba669ba09c27a7e5710ec9040" + sha256: "2fb815080e44a09b85e0f2ca8a820b15053982b2e714b59267719e8a9ff17097" url: "https://pub.dev" source: hosted - version: "1.6.2" + version: "1.6.3" crypto: dependency: transitive description: @@ -117,10 +117,18 @@ packages: dependency: "direct dev" description: name: dart_code_metrics - sha256: "219607f5abbf4c0d254ca39ee009f9ff28df91c40aef26718fde15af6b7a6c24" + sha256: "026e28da197a03caeccccc0b174ec98ef03da3c81c4543314d7add121aab4375" + url: "https://pub.dev" + source: hosted + version: "5.6.0" + dart_code_metrics_presets: + dependency: transitive + description: + name: dart_code_metrics_presets + sha256: "9c51724f836aebc4465228954cb5757e5a99737af26a452b5dec0a2d5d0b4d66" url: "https://pub.dev" source: hosted - version: "4.21.3" + version: "1.2.0" dart_style: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 31f12649..7b6b263b 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -15,7 +15,7 @@ dependencies: dev_dependencies: flutter_test: sdk: flutter - dart_code_metrics: ^4.17.1 + dart_code_metrics: ^5.6.0 flutter_lints: ^2.0.1 mocktail: ^0.3.0 diff --git a/test/buttons/checkbox_test.dart b/test/buttons/checkbox_test.dart index 323c3425..9ba9c1a5 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) { + builder: (context, scrollController) { return StatefulBuilder( builder: (context, setState) { return MacosCheckbox( diff --git a/test/buttons/disclosure_button_test.dart b/test/buttons/disclosure_button_test.dart index a56710be..b00087a3 100644 --- a/test/buttons/disclosure_button_test.dart +++ b/test/buttons/disclosure_button_test.dart @@ -18,7 +18,7 @@ void main() { child: MacosScaffold( children: [ ContentArea( - builder: (context) { + builder: (context, scrollController) { return MacosDisclosureButton( onPressed: mockOnPressedFunction.handler, ); diff --git a/test/buttons/help_button_test.dart b/test/buttons/help_button_test.dart index 095acbc6..6124e5c0 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) { + builder: (context, scrollController) { return HelpButton( onPressed: mockOnPressedFunction.handler, ); @@ -55,7 +55,7 @@ void main() { child: MacosScaffold( children: [ ContentArea( - builder: (context) { + builder: (context, _) { return HelpButton( key: helpButtonKey, onPressed: mockOnTapCancelFunction.handler, diff --git a/test/buttons/icon_button_test.dart b/test/buttons/icon_button_test.dart index a6d893de..a4c1e543 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) { + builder: (context, scrollController) { return MacosIconButton( icon: const Icon(CupertinoIcons.add), onPressed: mockOnPressedFunction.handler, @@ -48,7 +48,7 @@ void main() { child: MacosScaffold( children: [ ContentArea( - builder: (context) { + builder: (context, _) { return MacosIconButton( key: iconButtonKey, icon: const Icon(CupertinoIcons.add), diff --git a/test/buttons/popup_button_test.dart b/test/buttons/popup_button_test.dart index a57d1516..d685d3da 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) { + builder: (context, scrollController) { return StatefulBuilder( builder: (context, setState) { return MacosPopupButton( diff --git a/test/buttons/pulldown_button_test.dart b/test/buttons/pulldown_button_test.dart index fd319577..aa42cc83 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) { + builder: (context, scrollController) { return Center( child: MacosPulldownButton( title: "test", @@ -67,7 +67,7 @@ void main() { child: MacosScaffold( children: [ ContentArea( - builder: (context) { + 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 3cd6c5e5..99fe95ae 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) { + builder: (context, scrollController) { return PushButton( buttonSize: ButtonSize.small, onPressed: mockOnPressedFunction.handler, @@ -58,7 +58,7 @@ void main() { child: MacosScaffold( children: [ ContentArea( - builder: (context) { + builder: (context, _) { return PushButton( buttonSize: ButtonSize.small, key: pushButtonKey, diff --git a/test/buttons/radio_button_test.dart b/test/buttons/radio_button_test.dart index f900a867..cb51f3b3 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) { + builder: (context, scrollController) { return Center( child: MacosRadioButton( value: TestOptions.first, @@ -53,7 +53,7 @@ void main() { child: MacosScaffold( children: [ ContentArea( - builder: (context) { + builder: (context, _) { return Center( child: MacosRadioButton( value: TestOptions.second, diff --git a/test/buttons/segmented_control_test.dart b/test/buttons/segmented_control_test.dart index f85c9d89..1ccf4a74 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) { + builder: (context, scrollController) { return Center( child: MacosSegmentedControl( controller: controller, @@ -59,7 +59,7 @@ void main() { child: MacosScaffold( children: [ ContentArea( - builder: (context) { + builder: (context, _) { return Center( child: MacosSegmentedControl( controller: controller, diff --git a/test/buttons/switch_test.dart b/test/buttons/switch_test.dart index 9c32d1b4..75c3e863 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) { + builder: (context, scrollController) { return Center( child: MacosSwitch( value: selected, diff --git a/test/fields/search_field_test.dart b/test/fields/search_field_test.dart index 507b5648..a6f1a750 100644 --- a/test/fields/search_field_test.dart +++ b/test/fields/search_field_test.dart @@ -29,7 +29,7 @@ void main() { child: MacosScaffold( children: [ ContentArea( - builder: (context) { + builder: (context, scrollController) { return Center( child: SizedBox( width: 300.0, diff --git a/test/fields/text_field_test.dart b/test/fields/text_field_test.dart index eff83bc2..d83ce27b 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) { + builder: (context, scrollController) { return Center( child: MacosTextField( controller: controller, diff --git a/test/indicators/scrollbar_test.dart b/test/indicators/scrollbar_test.dart new file mode 100644 index 00000000..f5ffe2c2 --- /dev/null +++ b/test/indicators/scrollbar_test.dart @@ -0,0 +1,66 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/gestures.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:macos_ui/macos_ui.dart'; + +// TODO: Test trackpad scroll +// TODO: Test trackpad fling +// TODO: Test scrollbar track UI when hovering over the scrollbar and dragging to scroll + +void main() { + const double thickness = 6; + const double thicknessWhenDragging = 9; + const double scaleFactor = 2; + final ScrollController scrollController = ScrollController(); + + testWidgets( + 'Scrollbar changes position when scrolled with the mouse wheel', + (tester) async { + final Size screenSize = tester.binding.window.physicalSize / + tester.binding.window.devicePixelRatio; + + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: MediaQuery( + data: const MediaQueryData(), + child: PrimaryScrollController( + controller: scrollController, + child: MacosTheme( + data: MacosThemeData.light(), + child: MacosScrollbar( + thickness: thickness, + thicknessWhileHovering: thicknessWhenDragging, + child: SingleChildScrollView( + child: SizedBox( + width: screenSize.width * scaleFactor, + height: screenSize.height * scaleFactor, + ), + ), + ), + ), + ), + ), + ), + ); + + const Offset scrollAmount = Offset(0.0, 5.0); + const Offset reverseScrollAmount = Offset(0.0, -5.0); + Offset finalPosition = Offset.zero; + final Offset scrollEventLocation = + tester.getCenter(find.byType(SingleChildScrollView)); + final TestPointer testPointer = TestPointer(1, PointerDeviceKind.mouse); + + testPointer.hover(scrollEventLocation); + + // Scroll down + await tester.sendEventToBinding(testPointer.scroll(scrollAmount)); + await tester.pumpAndSettle(); + expect(scrollController.offset, scrollAmount.dy); + // Scroll back up + await tester.sendEventToBinding(testPointer.scroll(reverseScrollAmount)); + await tester.pumpAndSettle(); + expect(scrollController.offset, finalPosition.dy); + }, + ); +} diff --git a/test/layout/macos_list_tile_test.dart b/test/layout/macos_list_tile_test.dart index e021b3b5..6f86b952 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) { + builder: (context, scrollController) { return MacosListTile( title: const Text('List Tile'), onClick: mockOnPressedFunction.handler, @@ -52,7 +52,7 @@ void main() { child: MacosScaffold( children: [ ContentArea( - builder: (context) { + builder: (context, _) { return MacosListTile( title: const Text('List Tile'), subtitle: const Text('Subtitle'), diff --git a/test/layout/resizeable_pane_test.dart b/test/layout/resizeable_pane_test.dart index 26016e84..bae5ac4d 100644 --- a/test/layout/resizeable_pane_test.dart +++ b/test/layout/resizeable_pane_test.dart @@ -26,7 +26,9 @@ void main() { children: [ resizablePane, ContentArea( - builder: (context) => const Text('Hello there'), + builder: (context, scrollController) { + return const Text('Hello there'); + }, ), ], ), diff --git a/test/layout/tab_view_test.dart b/test/layout/tab_view_test.dart index 89a0bad0..3673f4d1 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) { + builder: (context, scrollController) { 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 80e00bf8..e6d36763 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) { + builder: (context, scrollController) { return Center( child: MacosDatePicker( onDateChanged: (date) {}, @@ -53,7 +53,7 @@ void main() { child: MacosScaffold( children: [ ContentArea( - builder: (context) { + builder: (context, _) { return Center( child: MacosDatePicker( onDateChanged: (date) {}, @@ -90,7 +90,7 @@ void main() { child: MacosScaffold( children: [ ContentArea( - builder: (context) { + builder: (context, _) { return Center( child: MacosDatePicker( onDateChanged: (date) {}, @@ -135,7 +135,7 @@ void main() { child: MacosScaffold( children: [ ContentArea( - builder: (context) { + builder: (context, _) { return Center( child: MacosDatePicker( onDateChanged: (date) {}, @@ -180,7 +180,7 @@ void main() { child: MacosScaffold( children: [ ContentArea( - builder: (context) { + builder: (context, _) { return Center( child: MacosDatePicker( onDateChanged: (date) {}, @@ -226,7 +226,7 @@ void main() { child: MacosScaffold( children: [ ContentArea( - builder: (context) { + builder: (context, _) { return Center( child: MacosDatePicker( onDateChanged: (date) { @@ -262,7 +262,7 @@ void main() { child: MacosScaffold( children: [ ContentArea( - builder: (context) { + builder: (context, scrollController) { 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 aa73c2c4..cd93ac74 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) { + builder: (context, scrollController) { capturedContext = context; return const HelpButton(); }, diff --git a/test/theme/icon_button_theme_test.dart b/test/theme/icon_button_theme_test.dart index bc7ff339..92832505 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) { + builder: (context, scrollController) { 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 5e37d2cd..23182817 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) { + builder: (context, scrollController) { 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 96f40400..710f2f49 100644 --- a/test/theme/popup_button_theme_test.dart +++ b/test/theme/popup_button_theme_test.dart @@ -70,9 +70,7 @@ void main() { child: MacosScaffold( children: [ ContentArea( - builder: ( - context, - ) { + builder: (context, scrollController) { 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 9342c46d..44264874 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) { + builder: (context, scrollController) { 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 b163be44..8ced6d85 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) { + builder: (context, scrollController) { capturedContext = context; return const PushButton( buttonSize: ButtonSize.small, diff --git a/test/theme/scrollbar_theme_test.dart b/test/theme/scrollbar_theme_test.dart index c89d0d02..39e3c156 100644 --- a/test/theme/scrollbar_theme_test.dart +++ b/test/theme/scrollbar_theme_test.dart @@ -53,21 +53,9 @@ void main() { } final _scrollbarThemeData = MacosScrollbarThemeData( - draggingThumbColor: Colors.grey.shade600, - hoveringThumbColor: Colors.grey.shade600, - hoveringTrackBorderColor: Colors.grey.shade600, - hoveringTrackColor: Colors.grey.shade600, thumbColor: Colors.grey.shade600, - trackBorderColor: Colors.grey.shade600, - trackColor: Colors.grey.shade600, ); final _scrollbarThemeDataDark = MacosScrollbarThemeData( - draggingThumbColor: Colors.grey.shade300, - hoveringThumbColor: Colors.grey.shade300, - hoveringTrackBorderColor: Colors.grey.shade300, - hoveringTrackColor: Colors.grey.shade300, thumbColor: Colors.grey.shade300, - trackBorderColor: Colors.grey.shade300, - trackColor: Colors.grey.shade300, ); diff --git a/test/theme/search_field_theme_test.dart b/test/theme/search_field_theme_test.dart index cf740bfe..866f83cc 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) { + builder: (context, scrollController) { capturedContext = context; return const Center( child: MacosSearchField(),