diff --git a/.github/workflows/dart_code_metrics.yaml b/.github/workflows/dart_code_metrics.yaml
index a22ecac7..03336a6a 100644
--- a/.github/workflows/dart_code_metrics.yaml
+++ b/.github/workflows/dart_code_metrics.yaml
@@ -8,11 +8,15 @@ jobs:
steps:
- uses: actions/checkout@v3
- - name: Run Dart Code Metrics
- uses: dart-code-checker/dart-code-metrics-action@v3
+ - name: Install Flutter
+ uses: subosito/flutter-action@v2
+ with:
+ channel: stable
+
+ - name: Set Up DCM
+ run: flutter pub get
+ - uses: CQLabs/setup-dcm@v1.0.0
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
- pull_request_comment: true
- fatal_warnings: true
- fatal_performance: true
- fatal_style: true
+
+ - run: dcm analyze --ci-key="${{ secrets.DCM_CI_KEY }}" --email="${{ secrets.DCM_EMAIL }}" lib
diff --git a/.github/workflows/flutter_analysis.yml b/.github/workflows/flutter_analysis.yml
index 4a4b58ff..fba04aa4 100644
--- a/.github/workflows/flutter_analysis.yml
+++ b/.github/workflows/flutter_analysis.yml
@@ -34,7 +34,3 @@ jobs:
- name: Analyze code
run: flutter analyze --fatal-infos .
-
- - name: Test code
- run: flutter test
-
diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml
new file mode 100644
index 00000000..8c64e72e
--- /dev/null
+++ b/.github/workflows/test.yaml
@@ -0,0 +1,19 @@
+name: Flutter Analysis
+on: [pull_request, workflow_dispatch]
+
+jobs:
+ package-analysis:
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v3
+ - name: Install Flutter
+ uses: subosito/flutter-action@v2
+ with:
+ channel: stable
+
+ - name: Install dependencies
+ run: flutter pub get
+
+ - name: Test code
+ run: flutter test
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 50742fa2..0c59bae9 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,63 @@
+## [2.0.0]
+### 🚨 Breaking Changes 🚨
+* `macos_ui` has been migrated to utilize [macos_window_utils](https://pub.dev/packages/macos_window_utils) under the hood, which provides the following benefits:
+ * Window animation smoothness is drastically improved, particularly when miniaturizing and deminiaturizing the application window.
+ * Some visual artifacts that occurred while the window was being (de)miniaturized (such as the application's shadow going missing) no longer occur.
+ * The sidebar remains transparent when the app's brightness setting mismatches the OS setting.
+ * Wallpaper tinting is now supported.
+ * To migrate an existing application, please refer to the “Modern window look” section in the README.
+
+* Support for Flutter 3.10 and Dart 3
+* `PushButton` has been updated to support the `ControlSize` enum.
+ * The `buttonSize` property has been changed to `controlSize`.
+ * Buttons can now be any of the following sizes: mini, small, regular, or large.
+* `PushButton.isSecondary` is now `PushButton.secondary`.
+* `MacosAlertDialog`: `primaryButton` and `secondaryButton` are now declared to be of type `PushButton`.
+* `RelevanceIndicator` has been deprecated
+* `MacosTypography` white and black are now factory constructors called `darkOpaque()` and `lightOpaque()` to reflect
+ Apple's naming conventions.
+
+### ✨ New ✨
+* `MacosSwitch` has been completely rewritten and now matches the native macOS switch in appearance and behavior.
+* A `ControlSize` enum has been introduced, which will allow widgets to more closely match their native counterparts.
+* `MacosTypography`
+ * You can now call `MacosTypography.of(context)` as a shorthand for retrieving the typography used in your `MacosTheme`.
+ * `MacosFontWeight` allows using Apple-specific font weights like `w510`, `w590`, and `w860`.
+* Localization
+ * Added support for `weekdayAbbreviations` and `monthAbbreviations` to `MacosDatePicker`.
+ * Added support for `dateFormat` to `MacosDatePicker`.
+ * Added support for `startWeekOnMonday` to `MacosDatePicker`.
+
+### 🔄 Updated 🔄
+* `MacosColor` has been updated with some previously missing elements.
+* `PushButton`
+ * Now uses the correct `body` text style instead of the incorrect `headline`
+* `PushButton`'s secondary and disabled colors more closely match their native counterparts.
+* `MacosCheckbox` appearance more closely matches its native counterpart.
+* `MacosAlertDialog`
+ * `primaryButton` and `secondaryButton` are now required to have `controlSize`s of `ControlSize.large`.
+ * Docs now suggest that `appIcon` should be of size 64x64.
+* `Toolbar` now uses the correct `title3` text style instead of the incorrect `headline`
+* `MacosTheme` sets the global typography more efficiently
+* `HelpButton` now sizes itself according to specification
+* `ResizablePane` can now disallow the usage of its internal scrollbar via the `ReziablePane.noScrollBar` constructor.
+
+### 🛠️ Fixed 🛠️
+* Clicking on the calendar elements in `MacosDatePicker` has better UX
+* `ToolBar`s in use where a `SideBar` is not present will now have their title's avoid the traffic lights (native window controls).
+* `MacosTypography.darkOpaque()` and `MacosTypography.lightOpaque()` now conform to specification by using `MacosColors.labelColor`
+* Ensure builds targeting web do not utilize any `macos_window_utils` code
+* Ensure builds targeting web are themed correctly
+
+## [1.12.5]
+* Fixed a bug where the `Sidebar.key` parameter wasn't used, which caused certain layouts to be unachievable.
+
+## [1.12.4]
+* Default the `_selectedDay` state variable to be 1 when selecting the previous/next month from widget to ensure new date is valid for `_formatAsDateTime()` method (https://github.com/flutter/flutter/issues/123669 & https://github.com/macosui/macos_ui/pull/402)
+
+## [1.12.3]
+* Added support for `routerConfig` to `MacosApp.router`. ([#388](https://github.com/macosui/macos_ui/issues/388))
+
## [1.12.2]
* Fixed a bug where clicking on a overflowed toolbar item with a navigation callback wouldn't work ([#346](https://github.com/GroovinChip/macos_ui/issues/346)).
diff --git a/README.md b/README.md
index b0925894..23a54c9a 100644
--- a/README.md
+++ b/README.md
@@ -2,7 +2,7 @@
Flutter widgets and themes implementing the current macOS design language.
-Check out our **interactive widget gallery** online at https://groovinchip.github.io/macos_ui/#/
+Check out our **interactive widget gallery** online at https://macosui.github.io/macos_ui/#/
Guides, codelabs, and other documentation can be found at https://macosui.dev
@@ -12,9 +12,9 @@ Guides, codelabs, and other documentation can be found at https://macosui.dev
[![Flutter Analysis](https://github.com/GroovinChip/macos_ui/actions/workflows/flutter_analysis.yml/badge.svg?branch=stable)](https://github.com/GroovinChip/macos_ui/actions/workflows/flutter_analysis.yml)
[![Pana Analysis](https://github.com/GroovinChip/macos_ui/actions/workflows/pana_analysis.yml/badge.svg)](https://github.com/GroovinChip/macos_ui/actions/workflows/pana_analysis.yml)
[![codecov](https://github.com/GroovinChip/macos_ui/actions/workflows/codecov.yaml/badge.svg)](https://github.com/GroovinChip/macos_ui/actions/workflows/codecov.yaml)
-[![codecov](https://codecov.io/gh/GroovinChip/macos_ui/branch/dev/graph/badge.svg?token=1SZGEVVMCH)](https://codecov.io/gh/GroovinChip/macos_ui)
+[![codecov](https://codecov.io/gh/macosui/macos_ui/branch/dev/graph/badge.svg?token=1SZGEVVMCH)](https://codecov.io/gh/macosui/macos_ui)
-
+
## 🚨 Usage notes
### Flutter channel
@@ -23,10 +23,13 @@ Guides, codelabs, and other documentation can be found at https://macosui.dev
### Platform Compatibility
pub.dev shows that `macos_ui` only supports macOS. This is because `macos_ui` calls some native code, and therefore
-specifies macOS as a plugin platform in the `pubspec.yaml` file. `macos_ui` _will_ work on any platform that
-Flutter supports, **but you will get best results on macOS**.
+specifies macOS as a plugin platform in the `pubspec.yaml` file.
+
+`macos_ui` _technically_ will work on any platform that
+Flutter supports, **but you will get best results on macOS**. non-macOS platform support is ***not*** guaranteed.
The features of `macos_ui` that will _not_ work on platforms other than macOS due to calling native code are:
+* Anything related to `macos_window_utils`
* The `MacosColors.controlAccentColor()` function
* The `MacosColorWell` widget
@@ -43,9 +46,8 @@ should avoid allowing your application window to be resized below the height of
Contributing & Resources
-- [macos_ui](#macos_ui)
- - [Contributing](#contributing)
- - [Resources](#resources)
+- [Contributing](#contributing)
+- [Resources](#resources)
@@ -111,7 +113,6 @@ should avoid allowing your application window to be resized below the height of
- [Level Indicators](#level-indicators)
- [CapacityIndicator](#capacityindicator)
- [RatingIndicator](#ratingindicator)
- - [RelevanceIndicator](#relevanceindicator)
@@ -131,9 +132,9 @@ should avoid allowing your application window to be resized below the height of
## Resources
+- [macOS Sonoma Figma kit](https://www.figma.com/file/M6K5L3GK0WJh6pnsASyVeE/macOS-Big-Sur-UI-Kit?node-id=1%3A2)
+- [macOS Human Interface Guidelines](https://developer.apple.com/design/human-interface-guidelines/designing-for-macos)
- [macOS Design Resources](https://developer.apple.com/design/resources/)
-- [macOS Human Interface Guidelines](https://developer.apple.com/design/human-interface-guidelines/macos)
-- [macOS Big Sur Figma kit](https://www.figma.com/file/M6K5L3GK0WJh6pnsASyVeE/macOS-Big-Sur-UI-Kit?node-id=1%3A2)
# Layout
@@ -158,7 +159,7 @@ A sidebar enables app navigation and provides quick access to top-level collecti
Sidebars may be placed at the left or right of your app. To place a sidebar on the left, use the `MacosWindow.sidebar` property. To place a sidebar on the right, use the `MacosWindow.endSidebar` property.
-
+
Example usage:
@@ -217,105 +218,50 @@ covering the entire window. To push a route outside a `MacosScaffold` wrapped in
See the documentation for customizations and `ToolBar` examples.
-
-
-
+
-
+
## Modern window look
-A new look for macOS apps was introduced in Big Sur (macOS 11). To match that look
-in your Flutter app, like our screenshots, your `macos/Runner/MainFlutterWindow.swift`
-file should look like this:
-
-```swift
-import Cocoa
-import FlutterMacOS
+A new look for macOS apps was introduced in Big Sur (macOS 11). To match that look in your Flutter app, macos_ui relies on [macos_window_utils](https://pub.dev/packages/macos_window_utils), which requires a minimum macOS deployment target of 10.14.6. Therefore, make sure to open the `macos/Runner.xcworkspace` folder of your project using Xcode and search for `Runner.xcodeproj`. Go to `Info` > `Deployment Target` and set the `macOS Deployment Target` to `10.14.6` or above. Then, open your project's `Podfile` (if it doesn't show up in Xcode, you can find it in your project's `macos` directory via VS Code) and set the minimum deployment version in the first line to `10.14.6` or above:
-class BlurryContainerViewController: NSViewController {
- let flutterViewController = FlutterViewController()
-
- init() {
- super.init(nibName: nil, bundle: nil)
- }
+```podspec
+platform :osx, '10.14.6'
+```
- required init?(coder: NSCoder) {
- fatalError()
- }
+You may also need to open up your app's `Runner.xcodeproj` in XCode and set the minimum deployment version there.
- override func loadView() {
- let blurView = NSVisualEffectView()
- blurView.autoresizingMask = [.width, .height]
- blurView.blendingMode = .behindWindow
- blurView.state = .active
- if #available(macOS 10.14, *) {
- blurView.material = .sidebar
- }
- self.view = blurView
- }
+Now, configure your window inside your `main()` as follows:
- override func viewDidLoad() {
- super.viewDidLoad()
+```dart
+/// This method initializes macos_window_utils and styles the window.
+Future _configureMacosWindowUtils() async {
+ const config = MacosWindowUtilsConfig(
+ toolbarStyle: NSWindowToolbarStyle.unified,
+ );
+ await config.apply();
+}
- self.addChild(flutterViewController)
+void main() async {
+ await _configureMacosWindowUtils();
- flutterViewController.view.frame = self.view.bounds
- flutterViewController.backgroundColor = .clear // **Required post-Flutter 3.7.0**
- flutterViewController.view.autoresizingMask = [.width, .height]
- self.view.addSubview(flutterViewController.view)
- }
+ runApp(const YourAppHere());
}
+```
-class MainFlutterWindow: NSWindow, NSWindowDelegate {
- override func awakeFromNib() {
- delegate = self
- let blurryContainerViewController = BlurryContainerViewController()
- let windowFrame = self.frame
- self.contentViewController = blurryContainerViewController
- self.setFrame(windowFrame, display: true)
-
- if #available(macOS 10.13, *) {
- let customToolbar = NSToolbar()
- customToolbar.showsBaselineSeparator = false
- self.toolbar = customToolbar
- }
- self.titleVisibility = .hidden
- self.titlebarAppearsTransparent = true
- if #available(macOS 11.0, *) {
- // Use .expanded if the app will have a title bar, else use .unified
- self.toolbarStyle = .unified
- }
-
- self.isMovableByWindowBackground = true
- self.styleMask.insert(NSWindow.StyleMask.fullSizeContentView)
-
- self.isOpaque = false
- self.backgroundColor = .clear
-
- RegisterGeneratedPlugins(registry: blurryContainerViewController.flutterViewController)
-
- super.awakeFromNib()
- }
-
- func window(_ window: NSWindow, willUseFullScreenPresentationOptions proposedOptions: NSApplication.PresentationOptions = []) -> NSApplication.PresentationOptions {
- return [.autoHideToolbar, .autoHideMenuBar, .fullScreen]
- }
+Please note that if you are using a title bar (`TitleBar`) in your `MacosWindow`, you should set the `toolbarStyle` of your window to `NSWindowToolbarStyle.expanded`, in order to properly align the close, minimize, zoom window buttons:
- func windowWillEnterFullScreen(_ notification: Notification) {
- self.toolbar?.isVisible = false
- }
-
- func windowDidExitFullScreen(_ notification: Notification) {
- self.toolbar?.isVisible = true
- }
+```dart
+Future _configureMacosWindowUtils() async {
+ const config = MacosWindowUtilsConfig(
+ toolbarStyle: NSWindowToolbarStyle.expanded,
+ );
+ await config.apply();
}
-
```
-See [this issue comment](https://github.com/flutter/flutter/issues/59969#issuecomment-916682559) for more details on the new look and explanations for how it works.
-
-Please note that if you are using a title bar (`TitleBar`) in your `MacosWindow`, you should set the `toolbarStyle` of NSWindow to `.expanded`, in order to properly align the close, minimize, zoom window buttons. In any other case, you should keep it as `.unified`. This must be set beforehand, i.e. it cannot be switched in runtime.
+In any other case, you should keep it as `NSWindowToolbarStyle.unified`.
## ToolBar
@@ -389,7 +335,7 @@ Other toolbar examples:
- Toolbar with a pulldown button open:
-
+
- Toolbar with title bar above (also see [the note above](#modern-window-look)):
@@ -462,7 +408,7 @@ MacosListTile(
## MacosTabView
A multipage interface that displays one page at a time. Must be used in a `StatefulWidget`.
-
+
You can control the placement of the tabs using the `position` property.
@@ -526,9 +472,9 @@ A checkbox is a type of button that lets the user choose between two opposite st
checkbox is considered on when it contains a checkmark and off when it's empty. A checkbox is almost always followed
by a title unless it appears in a checklist. [Learn more](https://developer.apple.com/design/human-interface-guidelines/macos/buttons/checkboxes/)
-| Off | On | Mixed |
+| Unchecked | Checked | Mixed |
| --------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------ |
-| ![Off Checkbox](https://developer.apple.com/design/human-interface-guidelines/macos/images/CheckBoxes_Deselected.svg) | ![On Checkbox](https://developer.apple.com/design/human-interface-guidelines/macos/images/CheckBoxes_Selected.svg) | ![Mixed Checkbox](https://developer.apple.com/design/human-interface-guidelines/macos/images/CheckBoxes_Mixed.svg) |
+| ![Unchecked Checkbox](https://imgur.com/Pu4EDAE.png) | ![Checked Checkbox](https://imgur.com/CB3Kmwo.png) | ![Mixed Checkbox](https://imgur.com/T44rV38.png) |
Here's an example of how to create a basic checkbox:
@@ -550,7 +496,7 @@ To make a checkbox in the `mixed` state, set `value` to `null`.
A help button appears within a view and opens app-specific help documentation when clicked. All help buttons are
circular, consistently sized buttons that contain a question mark icon. [Learn more](https://developer.apple.com/design/human-interface-guidelines/macos/buttons/help-buttons/)
-![HelpButton Example](https://developer.apple.com/design/human-interface-guidelines/macos/images/buttonsHelp.png)
+![HelpButton Example](https://imgur.com/DlP7uLV.png)
Here's an example of how to create a help button:
@@ -571,7 +517,7 @@ A radio button is a small, circular button followed by a title. Typically presen
buttons provide the user a set of related but mutually exclusive choices. A radio button’s state is either on
(a filled circle) or off (an empty circle). [Learn more](https://developer.apple.com/design/human-interface-guidelines/macos/buttons/radio-buttons/)
-![RadioButton Preview](https://developer.apple.com/design/human-interface-guidelines/macos/images/radioButtons.png)
+![RadioButton Preview](https://imgur.com/HI0eQsU.png)
Here's an example of how to create a basic radio button:
@@ -686,23 +632,22 @@ MacosPopupButton(
## PushButton
-A push button appears within a view and initiates an instantaneous app-specific action, such as printing a document or
-deleting a file. Push buttons contain text—not icons—and often open a separate window, dialog, or app so the user can
+Push buttons are the standard button type in macOS. Push buttons contain text—not icons—and often open a separate window, dialog, or app so the user can
complete a task. [Learn more](https://developer.apple.com/design/human-interface-guidelines/macos/buttons/push-buttons/)
| Dark Theme | Light Theme |
| ------------------------------------------ | ------------------------------------------ |
-| | |
-| | |
-| | |
-| | |
+| | |
+
+ℹ️ **Note** ℹ️
+Native push buttons can be styled as text-only, text with an icon, or icon-only. Currently, text-only push buttons are supported. To create an icon-only button, use the `MacosIconButton` widget.
Here's an example of how to create a basic push button:
```dart
PushButton(
child: Text('button'),
- buttonSize: ButtonSize.large,
+ controlSize: ControlSize.regular,
onPressed: () {
print('button pressed');
},
@@ -711,32 +656,40 @@ PushButton(
## MacosSwitch
-A switch is a visual toggle between two mutually exclusive states — on and off. A switch shows that it's on when the
-accent color is visible and off when the switch appears colorless. [Learn more](https://developer.apple.com/design/human-interface-guidelines/macos/buttons/switches/)
+A switch (also known as a toggle) is a control that offers a binary choice between two mutually exclusive states — on and off. A switch shows that it's on when the
+accent color is visible and off when the switch appears colorless.
+
+The `ContolSize` enum can be passed to the `size` property to control the size of the switch. `MacosSwitch` supports the following
+control sizes:
+* `mini`
+* `small`
+* `regular`
-| On | Off |
+| Off | On |
| ------------------------------------------ | ------------------------------------------ |
-| | |
+| | |
Here's an example of how to create a basic toggle switch:
```dart
-bool selected = false;
+bool switchValue = false;
MacosSwitch(
- value: selected,
+ value: switchValue,
onChanged: (value) {
- setState(() => selected = value);
+ setState(() => switchValue = value);
},
),
```
+Learn more about switches [here](https://developer.apple.com/design/human-interface-guidelines/toggles).
+
## MacosSegmentedControl
Displays one or more navigational tabs in a single horizontal group. Used by `MacosTabView` to navigate between the
different tabs of the tab bar.
-
+
The typical usage of this widget is by `MacosTabView`, to control the navigation of its children. You do not need to
specify a `MacosSegmentedControl` with your `MacosTabView`, as it is built by that widget.
@@ -750,9 +703,7 @@ Usage:
showMacosAlertDialog(
context: context,
builder: (_) => MacosAlertDialog(
- appIcon: FlutterLogo(
- size: 56,
- ),
+ appIcon: FlutterLogo(size: 64),
title: Text(
'Alert Dialog with Primary Action',
style: MacosTheme.of(context).typography.headline,
@@ -760,10 +711,10 @@ showMacosAlertDialog(
message: Text(
'This is an alert dialog with a primary action and no secondary action',
textAlign: TextAlign.center,
- style: MacosTheme.of(context).typography.headline,
+ style: MacosTypography.of(context).headline,
),
primaryButton: PushButton(
- buttonSize: ButtonSize.large,
+ controlSize: ControlSize.large,
child: Text('Primary'),
onPressed: () {},
),
@@ -771,9 +722,10 @@ showMacosAlertDialog(
);
```
-![](https://imgur.com/G3dcjew.png)
-![](https://imgur.com/YHtgv59.png)
-![](https://imgur.com/xuBR5qK.png)
+![](https://imgur.com/4zbGsFi.png)
+![](https://imgur.com/5fgkRU9.png)
+![](https://imgur.com/jOyJrZO.png)
+![](https://imgur.com/NX9taPj.png)
## MacosSheet
@@ -785,7 +737,7 @@ showMacosSheet(
);
```
-![](https://imgur.com/NV0o5Ws.png)
+![](https://imgur.com/Mnw2ywm.png)
# Fields
@@ -872,8 +824,6 @@ Progress indicators have two distinct styles:
People don't interact with progress indicators; however, they are often accompanied by a button for canceling the
corresponding operation. [Learn more](https://developer.apple.com/design/human-interface-guidelines/macos/indicators/progress-indicators/)
-![Progress Indicator Example](https://developer.apple.com/design/human-interface-guidelines/macos/images/ProgressIndicators_Lead.png)
-
### ProgressCircle
A `ProgressCircle` can be either determinate or indeterminate.
@@ -919,10 +869,8 @@ indicator styles, each with a different appearance, for communicating capacity,
A capacity indicator illustrates the current level in relation to a finite capacity. Capacity indicators are often used
when communicating factors like disk and battery usage. [Learn more](https://developer.apple.com/design/human-interface-guidelines/macos/indicators/level-indicators#capacity-indicators)
-| Continuous | Discrete |
-| ---------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| ![Continuous CapacityIndicator Example](https://developer.apple.com/design/human-interface-guidelines/macos/images/indicators-continous.png) | ![Discrete CapacityIndicator Example](https://developer.apple.com/design/human-interface-guidelines/macos/images/indicators-discrete.png) |
-| A horizontal translucent track that fills with a colored bar to indicate the current value. Tick marks are often displayed to provide context. | A horizontal row of separate, equally sized, rectangular segments. The number of segments matches the total capacity, and the segments fill completely—never partially—with color to indicate the current value. |
+
+
Here's an example of how to create an interactive continuous capacity indicator:
@@ -968,7 +916,7 @@ MacosSlider(
A rating indicator uses a series of horizontally arranged graphical symbols to communicate a ranking level. The default
symbol is a star.
-![RatingIndicator Example](https://developer.apple.com/design/human-interface-guidelines/macos/images/indicator-rating.png)
+![RatingIndicator Example](https://imgur.com/ySQBpL6.png)
A rating indicator doesn’t display partial symbols—its value is rounded in order to display complete symbols only.
Within a rating indicator, symbols are always the same distance apart and don't expand or shrink to fit the control.
@@ -988,22 +936,6 @@ RatingIndicator(
)
```
-### RelevanceIndicator
-
-A relevance indicator communicates relevancy using a series of vertical bars. It often appears in a list of search
-results for reference when sorting and comparing multiple items. [Learn more](https://developer.apple.com/design/human-interface-guidelines/macos/indicators/level-indicators#relevance-indicators)
-
-![RelevanceIndicator Example](https://developer.apple.com/design/human-interface-guidelines/macos/images/indicator-relevance.png)
-
-Here's an example of how to create a relevance indicator:
-
-```dart
-RelevanceIndicator(
- value: 15,
- amount: 20,
-)
-```
-
# Selectors
## MacosDatePicker
@@ -1020,6 +952,22 @@ There are three styles of `MacosDatePickers`:
calendar-like interface to select a date.
* `combined`: provides both `textual` and `graphical` interfaces.
+Localization of the time picker is supported by the `weekdayAbbreviations` and `monthAbbreviations` parameters (instead of e.g. standard `localizations.narrowWeekdays()` in order to match Apple's spec).
+* `weekdayAbbreviations` should be a list of 7 strings, one for each day of the week, starting with Sunday
+* `monthAbbreviations` should be a list of 12 strings, one for each month of the year, starting with January
+
+You can also define the `dateFormat` to change the way dates are displayed in the textual interface.
+It takes a string of tokens (case-insensitive) and replaces them with their corresponding values.
+The following tokens are supported:
+* `D`: day of the month (1-31)
+* `DD`: day of the month (01-31)
+* `M`: month of the year (1-12)
+* `MM`: month of the year (01-12)
+* `YYYY`: year (0000-9999)
+* Any separator between tokens is preserved (e.g. `/`, `-`, `.`)
+
+The default format is `M/D/YYYY`.
+
Example usage:
```dart
MacosDatePicker(
diff --git a/analysis_options.yaml b/analysis_options.yaml
index d4a906dd..7ed9076e 100644
--- a/analysis_options.yaml
+++ b/analysis_options.yaml
@@ -5,8 +5,6 @@ linter:
- use_super_parameters
analyzer:
- plugins:
- - dart_code_metrics
exclude:
- test/mock_canvas.dart
- test/recording_canvas.dart
@@ -14,13 +12,23 @@ analyzer:
dart_code_metrics:
metrics:
cyclomatic-complexity: 20
- number-of-parameters: 4
maximum-nesting-level: 5
metrics-exclude:
- test/**
- example/test/**
rules:
- prefer-trailing-comma
+ - double-literal-format
+ - prefer-first
+ - prefer-last
+ - prefer-immediate-return
+ - avoid-global-state
+ - always-remove-listener
+ - avoid-unnecessary-setstate
+ - avoid-wrapping-in-padding
+ - prefer-const-border-radius
+ - prefer-correct-edge-insets-constructor
+ - use-setstate-synchronously
- member-ordering:
alphabetize: false
order:
diff --git a/example/.metadata b/example/.metadata
index 140b9294..53830e36 100644
--- a/example/.metadata
+++ b/example/.metadata
@@ -1,10 +1,30 @@
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
-# This file should be version controlled and should not be manually edited.
+# This file should be version controlled.
version:
- revision: 4d7946a68d26794349189cf21b3f68cc6fe61dcb
+ revision: 796c8ef79279f9c774545b3771238c3098dbefab
channel: stable
project_type: app
+
+# Tracks metadata for the flutter migrate command
+migration:
+ platforms:
+ - platform: root
+ create_revision: 796c8ef79279f9c774545b3771238c3098dbefab
+ base_revision: 796c8ef79279f9c774545b3771238c3098dbefab
+ - platform: macos
+ create_revision: 796c8ef79279f9c774545b3771238c3098dbefab
+ base_revision: 796c8ef79279f9c774545b3771238c3098dbefab
+
+ # User provided section
+
+ # List of Local paths (relative to this file) that should be
+ # ignored by the migrate tool.
+ #
+ # Files that are not part of the templates will be ignored by default.
+ unmanaged_files:
+ - 'lib/main.dart'
+ - 'ios/Runner.xcodeproj/project.pbxproj'
diff --git a/example/lib/main.dart b/example/lib/main.dart
index 3d1a24e7..193bef1f 100644
--- a/example/lib/main.dart
+++ b/example/lib/main.dart
@@ -1,19 +1,38 @@
+import 'dart:io';
+
import 'package:example/pages/buttons_page.dart';
import 'package:example/pages/colors_page.dart';
import 'package:example/pages/dialogs_page.dart';
import 'package:example/pages/fields_page.dart';
import 'package:example/pages/indicators_page.dart';
+import 'package:example/pages/resizable_pane_page.dart';
import 'package:example/pages/selectors_page.dart';
import 'package:example/pages/sliver_toolbar_page.dart';
import 'package:example/pages/tabview_page.dart';
import 'package:example/pages/toolbar_page.dart';
+import 'package:example/pages/typography_page.dart';
+import 'package:example/platform_menus.dart';
import 'package:flutter/cupertino.dart';
+import 'package:flutter/foundation.dart';
import 'package:macos_ui/macos_ui.dart';
import 'package:provider/provider.dart';
+import 'package:url_launcher/url_launcher.dart';
import 'theme.dart';
-void main() {
+/// This method initializes macos_window_utils and styles the window.
+Future _configureMacosWindowUtils() async {
+ const config = MacosWindowUtilsConfig();
+ await config.apply();
+}
+
+Future main() async {
+ if (!kIsWeb) {
+ if (Platform.isMacOS) {
+ await _configureMacosWindowUtils();
+ }
+ }
+
runApp(const MacosUIGalleryApp());
}
@@ -47,68 +66,14 @@ class WidgetGallery extends StatefulWidget {
}
class _WidgetGalleryState extends State {
- double ratingValue = 0;
- double sliderValue = 0;
- bool value = false;
-
int pageIndex = 0;
late final searchFieldController = TextEditingController();
- final List pages = [
- CupertinoTabView(
- builder: (_) => const ButtonsPage(),
- ),
- const IndicatorsPage(),
- const FieldsPage(),
- const ColorsPage(),
- const Center(
- child: MacosIcon(
- CupertinoIcons.add,
- ),
- ),
- const DialogsPage(),
- const ToolbarPage(),
- const SliverToolbarPage(),
- const TabViewPage(),
- const SelectorsPage(),
- ];
-
@override
Widget build(BuildContext context) {
return PlatformMenuBar(
- menus: const [
- PlatformMenu(
- label: 'macos_ui Widget Gallery',
- menus: [
- PlatformProvidedMenuItem(
- type: PlatformProvidedMenuItemType.about,
- ),
- PlatformProvidedMenuItem(
- type: PlatformProvidedMenuItemType.quit,
- ),
- ],
- ),
- PlatformMenu(
- label: 'View',
- menus: [
- PlatformProvidedMenuItem(
- type: PlatformProvidedMenuItemType.toggleFullScreen,
- ),
- ],
- ),
- PlatformMenu(
- label: 'Window',
- menus: [
- PlatformProvidedMenuItem(
- type: PlatformProvidedMenuItemType.minimizeWindow,
- ),
- PlatformProvidedMenuItem(
- type: PlatformProvidedMenuItemType.zoomWindow,
- ),
- ],
- ),
- ],
+ menus: menuBarItems(),
child: MacosWindow(
sidebar: Sidebar(
top: MacosSearchField(
@@ -142,7 +107,7 @@ class _WidgetGalleryState extends State {
break;
case 'Dialogs and Sheets':
setState(() {
- pageIndex = 5;
+ pageIndex = 4;
searchFieldController.clear();
});
break;
@@ -152,12 +117,18 @@ class _WidgetGalleryState extends State {
searchFieldController.clear();
});
break;
- case 'Selectors':
+ case 'ResizablePane':
setState(() {
pageIndex = 7;
searchFieldController.clear();
});
break;
+ case 'Selectors':
+ setState(() {
+ pageIndex = 8;
+ searchFieldController.clear();
+ });
+ break;
default:
searchFieldController.clear();
}
@@ -169,6 +140,7 @@ class _WidgetGalleryState extends State {
SearchResultItem('Colors'),
SearchResultItem('Dialogs and Sheets'),
SearchResultItem('Toolbar'),
+ SearchResultItem('ResizablePane'),
SearchResultItem('Selectors'),
],
),
@@ -176,20 +148,27 @@ class _WidgetGalleryState extends State {
builder: (context, scrollController) {
return SidebarItems(
currentIndex: pageIndex,
- onChanged: (i) => setState(() => pageIndex = i),
+ onChanged: (i) {
+ if (kIsWeb && i == 10) {
+ launchUrl(
+ Uri.parse(
+ 'https://www.figma.com/file/IX6ph2VWrJiRoMTI1Byz0K/Apple-Design-Resources---macOS-(Community)?node-id=0%3A1745&mode=dev',
+ ),
+ );
+ } else {
+ setState(() => pageIndex = i);
+ }
+ },
scrollController: scrollController,
itemSize: SidebarItemSize.large,
- items: [
- const SidebarItem(
- // leading: MacosIcon(CupertinoIcons.square_on_circle),
+ items: const [
+ SidebarItem(
leading: MacosImageIcon(
- AssetImage(
- 'assets/sf_symbols/button_programmable_2x.png',
- ),
+ AssetImage('assets/sf_symbols/button_programmable_2x.png'),
),
label: Text('Buttons'),
),
- const SidebarItem(
+ SidebarItem(
leading: MacosImageIcon(
AssetImage(
'assets/sf_symbols/lines_measurement_horizontal_2x.png',
@@ -197,7 +176,7 @@ class _WidgetGalleryState extends State {
),
label: Text('Indicators'),
),
- const SidebarItem(
+ SidebarItem(
leading: MacosImageIcon(
AssetImage(
'assets/sf_symbols/character_cursor_ibeam_2x.png',
@@ -206,36 +185,16 @@ class _WidgetGalleryState extends State {
label: Text('Fields'),
),
SidebarItem(
- leading: const MacosIcon(CupertinoIcons.folder),
- label: const Text('Disclosure'),
- trailing: Text(
- '2',
- style: TextStyle(
- color: MacosTheme.brightnessOf(context) == Brightness.dark
- ? MacosColors.tertiaryLabelColor.darkColor
- : MacosColors.tertiaryLabelColor,
- ),
+ leading: MacosImageIcon(
+ AssetImage('assets/sf_symbols/rectangle_3_group_2x.png'),
),
- disclosureItems: [
- const SidebarItem(
- leading: MacosImageIcon(
- AssetImage(
- 'assets/sf_symbols/rectangle_3_group_2x.png',
- ),
- ),
- label: Text('Colors'),
- ),
- const SidebarItem(
- leading: MacosIcon(CupertinoIcons.infinite),
- label: Text('Item 3'),
- ),
- ],
+ label: Text('Colors'),
),
- const SidebarItem(
+ SidebarItem(
leading: MacosIcon(CupertinoIcons.square_on_square),
label: Text('Dialogs & Sheets'),
),
- const SidebarItem(
+ SidebarItem(
leading: MacosImageIcon(
AssetImage(
'assets/sf_symbols/macwindow.on.rectangle_2x.png',
@@ -259,16 +218,23 @@ class _WidgetGalleryState extends State {
leading: MacosIcon(CupertinoIcons.uiwindow_split_2x1),
label: Text('TabView'),
),
+ SidebarItem(
+ leading: MacosIcon(CupertinoIcons.rectangle_split_3x1),
+ label: Text('ResizablePane'),
+ ),
],
),
- const SidebarItem(
+ SidebarItem(
leading: MacosImageIcon(
AssetImage(
- 'assets/sf_symbols/filemenu_and_selection_2x.png',
- ),
+ 'assets/sf_symbols/filemenu_and_selection_2x.png'),
),
label: Text('Selectors'),
),
+ SidebarItem(
+ leading: MacosIcon(CupertinoIcons.textformat_size),
+ label: Text('Typography'),
+ ),
],
);
},
@@ -289,10 +255,19 @@ class _WidgetGalleryState extends State {
);
},
),
- child: IndexedStack(
- index: pageIndex,
- children: pages,
- ),
+ child: [
+ CupertinoTabView(builder: (_) => const ButtonsPage()),
+ const IndicatorsPage(),
+ const FieldsPage(),
+ const ColorsPage(),
+ const DialogsPage(),
+ const ToolbarPage(),
+ const SliverToolbarPage(isVisible: !kIsWeb),
+ const TabViewPage(),
+ const ResizablePanePage(),
+ const SelectorsPage(),
+ const TypographyPage(),
+ ][pageIndex],
),
);
}
diff --git a/example/lib/pages/buttons_page.dart b/example/lib/pages/buttons_page.dart
index 6c373f5f..fd61774f 100644
--- a/example/lib/pages/buttons_page.dart
+++ b/example/lib/pages/buttons_page.dart
@@ -1,3 +1,5 @@
+import 'package:example/widgets/widget_text_title1.dart';
+import 'package:example/widgets/widget_text_title2.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:macos_ui/macos_ui.dart';
@@ -59,412 +61,719 @@ class _ButtonsPageState extends State {
],
),
children: [
- ResizablePane(
- minSize: 180,
- startSize: 200,
- windowBreakpoint: 700,
- resizableSide: ResizableSide.right,
- builder: (_, __) {
- return const Center(
- child: Text('Resizable Pane'),
- );
- },
- ),
ContentArea(
builder: (context, scrollController) {
- return Column(
- children: [
- Flexible(
- fit: FlexFit.loose,
- child: SingleChildScrollView(
- controller: scrollController,
- padding: const EdgeInsets.all(20),
- child: Column(
- children: [
- const Text('MacosBackButton'),
- const SizedBox(height: 8),
- Row(
- mainAxisAlignment: MainAxisAlignment.center,
- children: [
- MacosBackButton(
- onPressed: () => debugPrint('click'),
- fillColor: Colors.transparent,
- ),
- const SizedBox(width: 16.0),
- MacosBackButton(
- onPressed: () => debugPrint('click'),
- ),
- ],
- ),
- const SizedBox(height: 20),
- const Text('MacosDisclosureButton'),
- const SizedBox(height: 8),
- Row(
- mainAxisAlignment: MainAxisAlignment.center,
- children: [
- MacosDisclosureButton(
- isPressed: isDisclosureButtonPressed,
- onPressed: () {
- debugPrint('click');
- setState(() {
- isDisclosureButtonPressed =
- !isDisclosureButtonPressed;
- });
- }),
- ],
- ),
- const SizedBox(height: 20),
- const Text('MacosIconButton'),
- const SizedBox(height: 8),
- Row(
- mainAxisAlignment: MainAxisAlignment.center,
- children: [
- MacosIconButton(
- icon: const MacosIcon(
- CupertinoIcons.star_fill,
- ),
- shape: BoxShape.rectangle,
- borderRadius: BorderRadius.circular(7),
- onPressed: () {},
- ),
- const SizedBox(width: 8),
- const MacosIconButton(
- icon: MacosIcon(
- CupertinoIcons.plus_app,
- ),
- shape: BoxShape.circle,
- //onPressed: () {},
- ),
- const SizedBox(width: 8),
- MacosIconButton(
- icon: const MacosIcon(
- CupertinoIcons.minus_square,
- ),
- backgroundColor: Colors.transparent,
- onPressed: () {},
- ),
- ],
- ),
- const SizedBox(height: 20),
- const Text('PushButton'),
- const SizedBox(height: 8),
- Row(
- mainAxisAlignment: MainAxisAlignment.center,
- children: [
- PushButton(
- buttonSize: ButtonSize.large,
- child: const Text('Large'),
- onPressed: () {
- MacosWindowScope.of(context).toggleSidebar();
+ return SingleChildScrollView(
+ controller: scrollController,
+ padding: const EdgeInsets.all(20),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ const WidgetTextTitle1(widgetName: 'PushButton'),
+ Divider(color: MacosTheme.of(context).dividerColor),
+ Text(
+ 'Primary',
+ style: MacosTypography.of(context).title2,
+ ),
+ Row(
+ children: [
+ PushButton(
+ controlSize: ControlSize.mini,
+ child: const Text('Mini'),
+ onPressed: () {
+ MacosWindowScope.of(context).toggleSidebar();
+ },
+ ),
+ const SizedBox(width: 8),
+ PushButton(
+ controlSize: ControlSize.small,
+ child: const Text('Small'),
+ onPressed: () {
+ Navigator.of(context).push(
+ MaterialPageRoute(
+ builder: (_) {
+ return MacosScaffold(
+ toolBar: const ToolBar(
+ title: Text('New page'),
+ ),
+ children: [
+ ContentArea(
+ builder: (context, _) {
+ return Center(
+ child: PushButton(
+ controlSize: ControlSize.regular,
+ child: const Text('Go Back'),
+ onPressed: () {
+ Navigator.of(context).maybePop();
+ },
+ ),
+ );
+ },
+ ),
+ ResizablePane(
+ minSize: 180,
+ startSize: 200,
+ windowBreakpoint: 700,
+ resizableSide: ResizableSide.left,
+ builder: (_, __) {
+ return const Center(
+ child: Text('Resizable Pane'),
+ );
+ },
+ ),
+ ],
+ );
},
),
- const SizedBox(width: 20),
- PushButton(
- buttonSize: ButtonSize.small,
- child: const Text('Small'),
- onPressed: () {
- Navigator.of(context).push(
- MaterialPageRoute(
- builder: (_) {
- return MacosScaffold(
- toolBar: const ToolBar(
- title: Text('New page'),
- ),
- children: [
- ContentArea(
- builder: (context, _) {
- return Center(
- child: PushButton(
- buttonSize: ButtonSize.large,
- child: const Text('Go Back'),
- onPressed: () {
- Navigator.of(context)
- .maybePop();
- },
- ),
- );
+ );
+ },
+ ),
+ const SizedBox(width: 8),
+ PushButton(
+ controlSize: ControlSize.regular,
+ child: const Text('Regular'),
+ onPressed: () {
+ Navigator.of(context).push(
+ MaterialPageRoute(
+ builder: (_) {
+ return MacosScaffold(
+ toolBar: const ToolBar(
+ title: Text('New page'),
+ ),
+ children: [
+ ContentArea(
+ builder: (context, _) {
+ return Center(
+ child: PushButton(
+ controlSize: ControlSize.regular,
+ child: const Text('Go Back'),
+ onPressed: () {
+ Navigator.of(context).maybePop();
},
),
- ResizablePane(
- minSize: 180,
- startSize: 200,
- windowBreakpoint: 700,
- resizableSide: ResizableSide.left,
- builder: (_, __) {
- return const Center(
- child: Text('Resizable Pane'),
- );
+ );
+ },
+ ),
+ ResizablePane(
+ minSize: 180,
+ startSize: 200,
+ windowBreakpoint: 700,
+ resizableSide: ResizableSide.left,
+ builder: (_, __) {
+ return const Center(
+ child: Text('Resizable Pane'),
+ );
+ },
+ ),
+ ],
+ );
+ },
+ ),
+ );
+ },
+ ),
+ const SizedBox(width: 8),
+ PushButton(
+ controlSize: ControlSize.large,
+ child: const Text('Large'),
+ onPressed: () {
+ Navigator.of(context).push(
+ MaterialPageRoute(
+ builder: (_) {
+ return MacosScaffold(
+ toolBar: const ToolBar(
+ title: Text('New page'),
+ ),
+ children: [
+ ContentArea(
+ builder: (context, _) {
+ return Center(
+ child: PushButton(
+ controlSize: ControlSize.regular,
+ child: const Text('Go Back'),
+ onPressed: () {
+ Navigator.of(context).maybePop();
},
),
- ],
- );
- },
- ),
+ );
+ },
+ ),
+ ResizablePane(
+ minSize: 180,
+ startSize: 200,
+ windowBreakpoint: 700,
+ resizableSide: ResizableSide.left,
+ builder: (_, __) {
+ return const Center(
+ child: Text('Resizable Pane'),
+ );
+ },
+ ),
+ ],
);
},
),
- const SizedBox(width: 20),
- PushButton(
- buttonSize: ButtonSize.large,
- isSecondary: true,
- child: const Text('Secondary'),
- onPressed: () {
- MacosWindowScope.of(context).toggleSidebar();
+ );
+ },
+ ),
+ ],
+ ),
+ const SizedBox(height: 16),
+ Text(
+ 'Disabled Primary',
+ style: MacosTypography.of(context).title2,
+ ),
+ const Row(
+ children: [
+ PushButton(
+ controlSize: ControlSize.mini,
+ child: Text('Mini'),
+ ),
+ SizedBox(width: 8),
+ PushButton(
+ controlSize: ControlSize.small,
+ child: Text('Small'),
+ ),
+ SizedBox(width: 8),
+ PushButton(
+ controlSize: ControlSize.regular,
+ child: Text('Regular'),
+ ),
+ SizedBox(width: 8),
+ PushButton(
+ controlSize: ControlSize.large,
+ child: Text('Large'),
+ ),
+ ],
+ ),
+ const SizedBox(height: 16),
+ Text(
+ 'Secondary',
+ style: MacosTypography.of(context).title2,
+ ),
+ Row(
+ children: [
+ PushButton(
+ controlSize: ControlSize.mini,
+ secondary: true,
+ child: const Text('Mini'),
+ onPressed: () {
+ MacosWindowScope.of(context).toggleSidebar();
+ },
+ ),
+ const SizedBox(width: 8),
+ PushButton(
+ controlSize: ControlSize.small,
+ secondary: true,
+ child: const Text('Small'),
+ onPressed: () {
+ Navigator.of(context).push(
+ MaterialPageRoute(
+ builder: (_) {
+ return MacosScaffold(
+ toolBar: const ToolBar(
+ title: Text('New page'),
+ ),
+ children: [
+ ContentArea(
+ builder: (context, _) {
+ return Center(
+ child: PushButton(
+ controlSize: ControlSize.regular,
+ child: const Text('Go Back'),
+ onPressed: () {
+ Navigator.of(context).maybePop();
+ },
+ ),
+ );
+ },
+ ),
+ ResizablePane(
+ minSize: 180,
+ startSize: 200,
+ windowBreakpoint: 700,
+ resizableSide: ResizableSide.left,
+ builder: (_, __) {
+ return const Center(
+ child: Text('Resizable Pane'),
+ );
+ },
+ ),
+ ],
+ );
},
),
- ],
- ),
- const SizedBox(height: 20),
- const Text('MacosSwitch'),
- const SizedBox(height: 8),
- MacosSwitch(
- value: switchValue,
- onChanged: (value) {
- setState(() => switchValue = value);
- },
- ),
- const SizedBox(height: 20),
- const Text('MacosPulldownButton'),
- const SizedBox(height: 8),
- Row(
- mainAxisAlignment: MainAxisAlignment.center,
- children: [
- MacosPulldownButton(
- title: 'PDF',
- items: [
- MacosPulldownMenuItem(
- title: const Text('Open in Preview'),
- onTap: () =>
- debugPrint('Opening in preview...'),
- ),
- MacosPulldownMenuItem(
- title: const Text('Save as PDF...'),
- onTap: () => debugPrint('Saving as PDF...'),
- ),
- MacosPulldownMenuItem(
- enabled: false,
- title: const Text('Save as Postscript'),
- onTap: () =>
- debugPrint('Saving as Postscript...'),
- ),
- const MacosPulldownMenuDivider(),
- MacosPulldownMenuItem(
- enabled: false,
- title: const Text('Save to iCloud Drive'),
- onTap: () =>
- debugPrint('Saving to iCloud...'),
- ),
- MacosPulldownMenuItem(
- enabled: false,
- title: const Text('Save to Web Receipts'),
- onTap: () =>
- debugPrint('Saving to Web Receipts...'),
- ),
- MacosPulldownMenuItem(
- title: const Text('Send in Mail...'),
- onTap: () =>
- debugPrint('Sending via Mail...'),
- ),
- const MacosPulldownMenuDivider(),
- MacosPulldownMenuItem(
- title: const Text('Edit Menu...'),
- onTap: () => debugPrint('Editing menu...'),
- ),
- ],
- ),
- const SizedBox(width: 20),
- const MacosPulldownButton(
- title: 'PDF',
- disabledTitle: 'Disabled',
- items: [],
- ),
- ],
- ),
- const SizedBox(height: 8),
- Row(
- mainAxisAlignment: MainAxisAlignment.center,
- children: [
- MacosPulldownButton(
- icon: CupertinoIcons.ellipsis_circle,
- items: [
- MacosPulldownMenuItem(
- title: const Text('New Folder'),
- onTap: () =>
- debugPrint('Creating new folder...'),
- ),
- MacosPulldownMenuItem(
- title: const Text('Open'),
- onTap: () => debugPrint('Opening...'),
- ),
- MacosPulldownMenuItem(
- title: const Text('Open with...'),
- onTap: () => debugPrint('Opening with...'),
- ),
- MacosPulldownMenuItem(
- title: const Text('Import from iPhone...'),
- onTap: () => debugPrint('Importing...'),
- ),
- const MacosPulldownMenuDivider(),
- MacosPulldownMenuItem(
- enabled: false,
- title: const Text('Remove'),
- onTap: () => debugPrint('Deleting...'),
- ),
- MacosPulldownMenuItem(
- title: const Text('Move to Bin'),
- onTap: () => debugPrint('Moving to Bin...'),
- ),
- const MacosPulldownMenuDivider(),
- MacosPulldownMenuItem(
- title: const Text('Tags...'),
- onTap: () => debugPrint('Tags...'),
- ),
- ],
- ),
- const SizedBox(width: 20),
- const MacosPulldownButton(
- icon: CupertinoIcons.square_grid_3x2,
- items: [],
- ),
- ],
- ),
- const SizedBox(height: 20),
- const Text('MacosPopupButton'),
- const SizedBox(height: 8),
- Row(
- mainAxisAlignment: MainAxisAlignment.center,
- children: [
- MacosPopupButton(
- value: popupValue,
- onChanged: (String? newValue) {
- setState(() => popupValue = newValue!);
- },
- items: [
- 'One',
- 'Two',
- 'Three',
- 'Four'
- ].map>((String value) {
- return MacosPopupMenuItem(
- value: value,
- child: Text(value),
+ );
+ },
+ ),
+ const SizedBox(width: 8),
+ PushButton(
+ controlSize: ControlSize.regular,
+ secondary: true,
+ child: const Text('Regular'),
+ onPressed: () {
+ Navigator.of(context).push(
+ MaterialPageRoute(
+ builder: (_) {
+ return MacosScaffold(
+ toolBar: const ToolBar(
+ title: Text('New page'),
+ ),
+ children: [
+ ContentArea(
+ builder: (context, _) {
+ return Center(
+ child: PushButton(
+ controlSize: ControlSize.regular,
+ child: const Text('Go Back'),
+ onPressed: () {
+ Navigator.of(context).maybePop();
+ },
+ ),
+ );
+ },
+ ),
+ ResizablePane(
+ minSize: 180,
+ startSize: 200,
+ windowBreakpoint: 700,
+ resizableSide: ResizableSide.left,
+ builder: (_, __) {
+ return const Center(
+ child: Text('Resizable Pane'),
+ );
+ },
+ ),
+ ],
);
- }).toList(),
- ),
- const SizedBox(width: 20),
- MacosPopupButton(
- disabledHint: const Text('Disabled'),
- onChanged: null,
- items: null,
- ),
- ],
- ),
- const SizedBox(height: 20),
- MacosPopupButton(
- value: languagePopupValue,
- onChanged: (String? newValue) {
- setState(() => languagePopupValue = newValue!);
- },
- items: languages
- .map>((String value) {
- return MacosPopupMenuItem(
- value: value,
- child: Text(value),
- );
- }).toList(),
- ),
- const SizedBox(height: 20),
- Row(
- mainAxisAlignment: MainAxisAlignment.center,
- children: [
- const Text('System Theme'),
- const SizedBox(width: 8),
- MacosRadioButton(
- groupValue: context.watch().mode,
- value: ThemeMode.system,
- onChanged: (value) {
- context.read().mode = value!;
},
),
- ],
- ),
- const SizedBox(height: 8),
- Row(
- mainAxisAlignment: MainAxisAlignment.center,
- children: [
- const Text('Light Theme'),
- const SizedBox(width: 24),
- MacosRadioButton(
- groupValue: context.watch().mode,
- value: ThemeMode.light,
- onChanged: (value) {
- context.read().mode = value!;
+ );
+ },
+ ),
+ const SizedBox(width: 8),
+ PushButton(
+ controlSize: ControlSize.large,
+ secondary: true,
+ child: const Text('Large'),
+ onPressed: () {
+ Navigator.of(context).push(
+ MaterialPageRoute(
+ builder: (_) {
+ return MacosScaffold(
+ toolBar: const ToolBar(
+ title: Text('New page'),
+ ),
+ children: [
+ ContentArea(
+ builder: (context, _) {
+ return Center(
+ child: PushButton(
+ controlSize: ControlSize.regular,
+ child: const Text('Go Back'),
+ onPressed: () {
+ Navigator.of(context).maybePop();
+ },
+ ),
+ );
+ },
+ ),
+ ResizablePane(
+ minSize: 180,
+ startSize: 200,
+ windowBreakpoint: 700,
+ resizableSide: ResizableSide.left,
+ builder: (_, __) {
+ return const Center(
+ child: Text('Resizable Pane'),
+ );
+ },
+ ),
+ ],
+ );
},
),
- ],
+ );
+ },
+ ),
+ ],
+ ),
+ const SizedBox(height: 16),
+ Text(
+ 'Disabled Secondary',
+ style: MacosTypography.of(context).title2,
+ ),
+ const Row(
+ children: [
+ PushButton(
+ controlSize: ControlSize.mini,
+ secondary: true,
+ child: Text('Mini'),
+ ),
+ SizedBox(width: 8),
+ PushButton(
+ controlSize: ControlSize.small,
+ secondary: true,
+ child: Text('Small'),
+ ),
+ SizedBox(width: 8),
+ PushButton(
+ controlSize: ControlSize.regular,
+ secondary: true,
+ child: Text('Regular'),
+ ),
+ SizedBox(width: 8),
+ PushButton(
+ controlSize: ControlSize.large,
+ secondary: true,
+ child: Text('Large'),
+ ),
+ ],
+ ),
+ const SizedBox(height: 16),
+ const WidgetTextTitle1(widgetName: 'HelpButton'),
+ Divider(color: MacosTheme.of(context).dividerColor),
+ HelpButton(onPressed: () {}),
+ const SizedBox(height: 16),
+ Text(
+ 'Icon Buttons',
+ style: MacosTypography.of(context).title1,
+ ),
+ Divider(color: MacosTheme.of(context).dividerColor),
+ const WidgetTextTitle2(widgetName: 'MacosBackButton'),
+ const SizedBox(height: 8),
+ Row(
+ children: [
+ MacosBackButton(
+ onPressed: () => debugPrint('click'),
+ fillColor: Colors.transparent,
+ ),
+ const SizedBox(width: 16.0),
+ MacosBackButton(
+ onPressed: () => debugPrint('click'),
+ ),
+ ],
+ ),
+ const SizedBox(height: 20),
+ const WidgetTextTitle2(widgetName: 'MacosDisclosureButton'),
+ const SizedBox(height: 8),
+ Row(
+ children: [
+ MacosDisclosureButton(
+ isPressed: isDisclosureButtonPressed,
+ onPressed: () {
+ debugPrint('click');
+ setState(() {
+ isDisclosureButtonPressed =
+ !isDisclosureButtonPressed;
+ });
+ },
+ ),
+ ],
+ ),
+ const SizedBox(height: 20),
+ const WidgetTextTitle2(widgetName: 'MacosIconButton'),
+ const SizedBox(height: 8),
+ Row(
+ children: [
+ MacosIconButton(
+ icon: const MacosIcon(
+ CupertinoIcons.star_fill,
),
- const SizedBox(height: 8),
- Row(
- mainAxisAlignment: MainAxisAlignment.center,
- children: [
- const Text('Dark Theme'),
- const SizedBox(width: 26),
- MacosRadioButton(
- groupValue: context.watch().mode,
- value: ThemeMode.dark,
- onChanged: (value) {
- context.read().mode = value!;
- },
- ),
- ],
+ shape: BoxShape.rectangle,
+ borderRadius: BorderRadius.circular(7),
+ onPressed: () {},
+ ),
+ const SizedBox(width: 8),
+ const MacosIconButton(
+ icon: MacosIcon(
+ CupertinoIcons.plus_app,
),
- const SizedBox(height: 20),
- const Text('MacosSegmentedControl'),
- const SizedBox(height: 8),
- MacosSegmentedControl(
- controller: _tabController,
- tabs: [
- MacosTab(
- label: 'Tab 1',
- active: _tabController.index == 0,
- ),
- MacosTab(
- label: 'Tab 2',
- active: _tabController.index == 1,
- ),
- MacosTab(
- label: 'Tab 3',
- active: _tabController.index == 2,
- ),
- ],
+ shape: BoxShape.circle,
+ //onPressed: () {},
+ ),
+ const SizedBox(width: 8),
+ MacosIconButton(
+ icon: const MacosIcon(
+ CupertinoIcons.minus_square,
),
- ],
- ),
+ backgroundColor: Colors.transparent,
+ onPressed: () {},
+ ),
+ ],
),
- ),
- ResizablePane(
- minSize: 50,
- startSize: 200,
- //windowBreakpoint: 600,
- builder: (_, __) {
- return const Center(
- child: Text('Resizable Pane'),
- );
- },
- resizableSide: ResizableSide.top,
- )
- ],
- );
- },
- ),
- ResizablePane(
- minSize: 180,
- startSize: 200,
- windowBreakpoint: 800,
- resizableSide: ResizableSide.left,
- builder: (_, __) {
- return const Center(
- child: Text('Resizable Pane'),
+ const SizedBox(height: 20),
+ Text(
+ 'Switches, Checkboxes, & Radios',
+ style: MacosTypography.of(context).title1,
+ ),
+ Divider(color: MacosTheme.of(context).dividerColor),
+ const WidgetTextTitle2(widgetName: 'MacosSwitch'),
+ const SizedBox(height: 8),
+ Column(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ Row(
+ children: [
+ const Text('Mini'),
+ const SizedBox(width: 8),
+ MacosSwitch(
+ value: switchValue,
+ size: ControlSize.mini,
+ onChanged: (value) {
+ setState(() => switchValue = value);
+ },
+ ),
+ ],
+ ),
+ const SizedBox(height: 8.0),
+ Row(
+ children: [
+ const Text('Small'),
+ const SizedBox(width: 8),
+ MacosSwitch(
+ value: switchValue,
+ size: ControlSize.small,
+ onChanged: (value) {
+ setState(() => switchValue = value);
+ },
+ ),
+ ],
+ ),
+ const SizedBox(height: 8.0),
+ Row(
+ children: [
+ const Text('Regular'),
+ const SizedBox(width: 8),
+ MacosSwitch(
+ value: switchValue,
+ onChanged: (value) {
+ setState(() => switchValue = value);
+ },
+ ),
+ ],
+ ),
+ ],
+ ),
+ const SizedBox(height: 16),
+ const WidgetTextTitle2(widgetName: 'MacosCheckbox'),
+ const SizedBox(height: 8),
+ MacosCheckbox(
+ value: switchValue,
+ onChanged: (value) {
+ setState(() => switchValue = value);
+ },
+ ),
+ const SizedBox(height: 16),
+ const WidgetTextTitle2(widgetName: 'MacosRadioButton'),
+ const SizedBox(height: 8),
+ Row(
+ children: [
+ const Text('System Theme'),
+ const SizedBox(width: 8),
+ MacosRadioButton(
+ groupValue: context.watch().mode,
+ value: ThemeMode.system,
+ onChanged: (value) {
+ context.read().mode = value!;
+ },
+ ),
+ ],
+ ),
+ const SizedBox(height: 8),
+ Row(
+ children: [
+ const Text('Light Theme'),
+ const SizedBox(width: 24),
+ MacosRadioButton(
+ groupValue: context.watch().mode,
+ value: ThemeMode.light,
+ onChanged: (value) {
+ context.read().mode = value!;
+ },
+ ),
+ ],
+ ),
+ const SizedBox(height: 8),
+ Row(
+ children: [
+ const Text('Dark Theme'),
+ const SizedBox(width: 26),
+ MacosRadioButton(
+ groupValue: context.watch().mode,
+ value: ThemeMode.dark,
+ onChanged: (value) {
+ context.read().mode = value!;
+ },
+ ),
+ ],
+ ),
+ const SizedBox(height: 20),
+ Text(
+ 'Pulldown & Popup Buttons',
+ style: MacosTypography.of(context).title1,
+ ),
+ Divider(color: MacosTheme.of(context).dividerColor),
+ const WidgetTextTitle2(widgetName: 'MacosPulldownButton'),
+ const SizedBox(height: 8),
+ Row(
+ children: [
+ MacosPulldownButton(
+ title: 'PDF',
+ items: [
+ MacosPulldownMenuItem(
+ title: const Text('Open in Preview'),
+ onTap: () => debugPrint('Opening in preview...'),
+ ),
+ MacosPulldownMenuItem(
+ title: const Text('Save as PDF...'),
+ onTap: () => debugPrint('Saving as PDF...'),
+ ),
+ MacosPulldownMenuItem(
+ enabled: false,
+ title: const Text('Save as Postscript'),
+ onTap: () => debugPrint('Saving as Postscript...'),
+ ),
+ const MacosPulldownMenuDivider(),
+ MacosPulldownMenuItem(
+ enabled: false,
+ title: const Text('Save to iCloud Drive'),
+ onTap: () => debugPrint('Saving to iCloud...'),
+ ),
+ MacosPulldownMenuItem(
+ enabled: false,
+ title: const Text('Save to Web Receipts'),
+ onTap: () =>
+ debugPrint('Saving to Web Receipts...'),
+ ),
+ MacosPulldownMenuItem(
+ title: const Text('Send in Mail...'),
+ onTap: () => debugPrint('Sending via Mail...'),
+ ),
+ const MacosPulldownMenuDivider(),
+ MacosPulldownMenuItem(
+ title: const Text('Edit Menu...'),
+ onTap: () => debugPrint('Editing menu...'),
+ ),
+ ],
+ ),
+ const SizedBox(width: 20),
+ const MacosPulldownButton(
+ title: 'PDF',
+ disabledTitle: 'Disabled',
+ items: [],
+ ),
+ ],
+ ),
+ const SizedBox(height: 8),
+ Row(
+ children: [
+ MacosPulldownButton(
+ icon: CupertinoIcons.ellipsis_circle,
+ items: [
+ MacosPulldownMenuItem(
+ title: const Text('New Folder'),
+ onTap: () => debugPrint('Creating new folder...'),
+ ),
+ MacosPulldownMenuItem(
+ title: const Text('Open'),
+ onTap: () => debugPrint('Opening...'),
+ ),
+ MacosPulldownMenuItem(
+ title: const Text('Open with...'),
+ onTap: () => debugPrint('Opening with...'),
+ ),
+ MacosPulldownMenuItem(
+ title: const Text('Import from iPhone...'),
+ onTap: () => debugPrint('Importing...'),
+ ),
+ const MacosPulldownMenuDivider(),
+ MacosPulldownMenuItem(
+ enabled: false,
+ title: const Text('Remove'),
+ onTap: () => debugPrint('Deleting...'),
+ ),
+ MacosPulldownMenuItem(
+ title: const Text('Move to Bin'),
+ onTap: () => debugPrint('Moving to Bin...'),
+ ),
+ const MacosPulldownMenuDivider(),
+ MacosPulldownMenuItem(
+ title: const Text('Tags...'),
+ onTap: () => debugPrint('Tags...'),
+ ),
+ ],
+ ),
+ const SizedBox(width: 20),
+ const MacosPulldownButton(
+ icon: CupertinoIcons.square_grid_3x2,
+ items: [],
+ ),
+ ],
+ ),
+ const SizedBox(height: 20),
+ const WidgetTextTitle2(widgetName: 'MacosPopupButton'),
+ const SizedBox(height: 8),
+ Row(
+ children: [
+ MacosPopupButton(
+ value: popupValue,
+ onChanged: (String? newValue) {
+ setState(() => popupValue = newValue!);
+ },
+ items: ['One', 'Two', 'Three', 'Four']
+ .map>((String value) {
+ return MacosPopupMenuItem(
+ value: value,
+ child: Text(value),
+ );
+ }).toList(),
+ ),
+ const SizedBox(width: 20),
+ MacosPopupButton(
+ disabledHint: const Text('Disabled'),
+ onChanged: null,
+ items: null,
+ ),
+ ],
+ ),
+ const SizedBox(height: 20),
+ MacosPopupButton(
+ value: languagePopupValue,
+ onChanged: (String? newValue) {
+ setState(() => languagePopupValue = newValue!);
+ },
+ items: languages
+ .map>((String value) {
+ return MacosPopupMenuItem(
+ value: value,
+ child: Text(value),
+ );
+ }).toList(),
+ ),
+ const SizedBox(height: 20),
+ const WidgetTextTitle1(widgetName: 'MacosSegmentedControl'),
+ Divider(color: MacosTheme.of(context).dividerColor),
+ const SizedBox(height: 8),
+ MacosSegmentedControl(
+ controller: _tabController,
+ tabs: [
+ MacosTab(
+ label: 'Tab 1',
+ active: _tabController.index == 0,
+ ),
+ MacosTab(
+ label: 'Tab 2',
+ active: _tabController.index == 1,
+ ),
+ MacosTab(
+ label: 'Tab 3',
+ active: _tabController.index == 2,
+ ),
+ ],
+ ),
+ ],
+ ),
);
},
),
diff --git a/example/lib/pages/colors_page.dart b/example/lib/pages/colors_page.dart
index 7a9df4fe..6eb3eac8 100644
--- a/example/lib/pages/colors_page.dart
+++ b/example/lib/pages/colors_page.dart
@@ -15,16 +15,27 @@ class _ColorsPageState extends State {
toolBar: ToolBar(
title: const Text('Colors'),
titleWidth: 150.0,
- actions: [
- ToolBarIconButton(
- label: 'Toggle Sidebar',
- icon: const MacosIcon(
+ leading: MacosTooltip(
+ message: 'Toggle Sidebar',
+ useMousePosition: false,
+ child: MacosIconButton(
+ icon: MacosIcon(
CupertinoIcons.sidebar_left,
+ color: MacosTheme.brightnessOf(context).resolve(
+ const Color.fromRGBO(0, 0, 0, 0.5),
+ const Color.fromRGBO(255, 255, 255, 0.5),
+ ),
+ size: 20.0,
+ ),
+ boxConstraints: const BoxConstraints(
+ minHeight: 20,
+ minWidth: 20,
+ maxWidth: 48,
+ maxHeight: 38,
),
onPressed: () => MacosWindowScope.of(context).toggleSidebar(),
- showLabel: false,
),
- ],
+ ),
),
children: [
ContentArea(
diff --git a/example/lib/pages/dialogs_page.dart b/example/lib/pages/dialogs_page.dart
index b7645978..33764bda 100644
--- a/example/lib/pages/dialogs_page.dart
+++ b/example/lib/pages/dialogs_page.dart
@@ -2,6 +2,9 @@ import 'package:macos_ui/macos_ui.dart';
// ignore: implementation_imports
import 'package:macos_ui/src/library.dart';
+const dialogMessage =
+ 'Description text about this alert is shown here, explaining to users what the options underneath are about and what to do.';
+
class DialogsPage extends StatefulWidget {
const DialogsPage({super.key});
@@ -16,16 +19,27 @@ class _DialogsPageState extends State {
toolBar: ToolBar(
title: const Text('Dialogs and Sheets'),
titleWidth: 150.0,
- actions: [
- ToolBarIconButton(
- label: 'Toggle Sidebar',
- icon: const MacosIcon(
+ leading: MacosTooltip(
+ message: 'Toggle Sidebar',
+ useMousePosition: false,
+ child: MacosIconButton(
+ icon: MacosIcon(
CupertinoIcons.sidebar_left,
+ color: MacosTheme.brightnessOf(context).resolve(
+ const Color.fromRGBO(0, 0, 0, 0.5),
+ const Color.fromRGBO(255, 255, 255, 0.5),
+ ),
+ size: 20.0,
+ ),
+ boxConstraints: const BoxConstraints(
+ minHeight: 20,
+ minWidth: 20,
+ maxWidth: 48,
+ maxHeight: 38,
),
onPressed: () => MacosWindowScope.of(context).toggleSidebar(),
- showLabel: false,
),
- ],
+ ),
),
children: [
ContentArea(
@@ -36,120 +50,101 @@ class _DialogsPageState extends State {
child: Column(
children: [
PushButton(
- buttonSize: ButtonSize.large,
+ controlSize: ControlSize.regular,
child: const Text('Show Alert Dialog 1'),
onPressed: () => showMacosAlertDialog(
context: context,
builder: (context) => MacosAlertDialog(
- appIcon: const FlutterLogo(
- size: 56,
- ),
- title: const Text(
- 'Alert Dialog with Primary Action',
- ),
- message: const Text(
- 'This is an alert dialog with a primary action and no secondary action',
- ),
+ appIcon: const FlutterLogo(size: 64),
+ title: const Text('Title'),
+ message: const Text(dialogMessage),
//horizontalActions: false,
primaryButton: PushButton(
- buttonSize: ButtonSize.large,
+ controlSize: ControlSize.large,
onPressed: Navigator.of(context).pop,
- child: const Text('Primary'),
+ child: const Text('Label'),
),
),
),
),
const SizedBox(height: 16),
PushButton(
- buttonSize: ButtonSize.large,
+ controlSize: ControlSize.regular,
child: const Text('Show Alert Dialog 2'),
onPressed: () => showMacosAlertDialog(
context: context,
builder: (context) => MacosAlertDialog(
- appIcon: const FlutterLogo(
- size: 56,
- ),
- title: const Text(
- 'Alert Dialog with Secondary Action',
- ),
+ appIcon: const FlutterLogo(size: 64),
+ title: const Text('Title'),
message: const Text(
- 'This is an alert dialog with primary action and secondary action laid out horizontally',
+ dialogMessage,
textAlign: TextAlign.center,
),
//horizontalActions: false,
primaryButton: PushButton(
- buttonSize: ButtonSize.large,
+ controlSize: ControlSize.large,
onPressed: Navigator.of(context).pop,
- child: const Text('Primary'),
+ child: const Text('Label'),
),
secondaryButton: PushButton(
- buttonSize: ButtonSize.large,
- isSecondary: true,
+ controlSize: ControlSize.large,
+ secondary: true,
onPressed: Navigator.of(context).pop,
- child: const Text('Secondary'),
+ child: const Text('Label'),
),
),
),
),
const SizedBox(height: 16),
PushButton(
- buttonSize: ButtonSize.large,
+ controlSize: ControlSize.regular,
child: const Text('Show Alert Dialog 3'),
onPressed: () => showMacosAlertDialog(
context: context,
builder: (context) => MacosAlertDialog(
- appIcon: const FlutterLogo(
- size: 56,
- ),
- title: const Text(
- 'Alert Dialog with Secondary Action',
- ),
+ appIcon: const FlutterLogo(size: 64),
+ title: const Text('Title'),
message: const Text(
- 'This is an alert dialog with primary action and secondary action laid out vertically',
+ dialogMessage,
textAlign: TextAlign.center,
),
horizontalActions: false,
primaryButton: PushButton(
- buttonSize: ButtonSize.large,
+ controlSize: ControlSize.large,
onPressed: Navigator.of(context).pop,
- child: const Text('Primary'),
+ child: const Text('Label'),
),
secondaryButton: PushButton(
- buttonSize: ButtonSize.large,
- isSecondary: true,
+ controlSize: ControlSize.large,
+ secondary: true,
onPressed: Navigator.of(context).pop,
- child: const Text('Secondary'),
+ child: const Text('Label'),
),
),
),
),
const SizedBox(height: 16),
PushButton(
- buttonSize: ButtonSize.large,
+ controlSize: ControlSize.regular,
child: const Text('Show Alert Dialog 4'),
onPressed: () => showMacosAlertDialog(
context: context,
builder: (context) => MacosAlertDialog(
- appIcon: const FlutterLogo(
- size: 56,
- ),
- title: const Text(
- 'Alert Dialog with Secondary Action',
- ),
+ appIcon: const FlutterLogo(size: 64),
+ title: const Text('Title'),
message: const Text(
- 'This is an alert dialog with primary action and secondary '
- 'action laid out vertically. It also contains a "suppress" option.',
+ dialogMessage,
textAlign: TextAlign.center,
),
horizontalActions: false,
primaryButton: PushButton(
- buttonSize: ButtonSize.large,
+ controlSize: ControlSize.large,
onPressed: Navigator.of(context).pop,
child: const Text('Primary'),
),
secondaryButton: PushButton(
- buttonSize: ButtonSize.large,
- isSecondary: true,
+ controlSize: ControlSize.large,
+ secondary: true,
onPressed: Navigator.of(context).pop,
child: const Text('Secondary'),
),
@@ -159,7 +154,7 @@ class _DialogsPageState extends State {
),
const SizedBox(height: 16),
PushButton(
- buttonSize: ButtonSize.large,
+ controlSize: ControlSize.regular,
child: const Text('Show sheet'),
onPressed: () {
showMacosSheet(
@@ -214,47 +209,50 @@ class DemoSheet extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MacosSheet(
- child: Center(
- child: Column(
- children: [
- const SizedBox(height: 50),
- const FlutterLogo(
- size: 56,
- ),
- const SizedBox(height: 24),
- Text(
- 'Welcome to macos_ui',
- style: MacosTheme.of(context).typography.largeTitle.copyWith(
- fontWeight: FontWeight.bold,
- ),
- ),
- const SizedBox(height: 24),
- const MacosListTile(
- leading: MacosIcon(CupertinoIcons.lightbulb),
- title: Text(
- 'A robust library of Flutter components for macOS',
- //style: MacosTheme.of(context).typography.headline,
+ child: Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 8.0),
+ child: Center(
+ child: Column(
+ children: [
+ const SizedBox(height: 50),
+ const FlutterLogo(
+ size: 56,
),
- subtitle: Text(
- 'Create native looking macOS applications using Flutter',
+ const SizedBox(height: 24),
+ Text(
+ 'Welcome to macos_ui',
+ style: MacosTheme.of(context).typography.largeTitle.copyWith(
+ fontWeight: FontWeight.bold,
+ ),
),
- ),
- const SizedBox(height: 16),
- const MacosListTile(
- leading: MacosIcon(CupertinoIcons.bolt),
- title: Text(
- 'Create beautiful macOS applications in minutes',
- //style: MacosTheme.of(context).typography.headline,
+ const SizedBox(height: 24),
+ const MacosListTile(
+ leading: MacosIcon(CupertinoIcons.lightbulb),
+ title: Text(
+ 'A robust library of Flutter components for macOS',
+ //style: MacosTheme.of(context).typography.headline,
+ ),
+ subtitle: Text(
+ 'Create native looking macOS applications using Flutter',
+ ),
),
- ),
- const Spacer(),
- PushButton(
- buttonSize: ButtonSize.large,
- child: const Text('Get started'),
- onPressed: () => Navigator.of(context).pop(),
- ),
- const SizedBox(height: 50),
- ],
+ const SizedBox(height: 16),
+ const MacosListTile(
+ leading: MacosIcon(CupertinoIcons.bolt),
+ title: Text(
+ 'Create beautiful macOS applications in minutes',
+ //style: MacosTheme.of(context).typography.headline,
+ ),
+ ),
+ const Spacer(),
+ PushButton(
+ controlSize: ControlSize.regular,
+ child: const Text('Get started'),
+ onPressed: () => Navigator.of(context).pop(),
+ ),
+ const SizedBox(height: 50),
+ ],
+ ),
),
),
);
diff --git a/example/lib/pages/fields_page.dart b/example/lib/pages/fields_page.dart
index 94785708..fd9183bb 100644
--- a/example/lib/pages/fields_page.dart
+++ b/example/lib/pages/fields_page.dart
@@ -1,4 +1,6 @@
+import 'package:example/widgets/widget_text_title1.dart';
import 'package:flutter/cupertino.dart' hide OverlayVisibilityMode;
+import 'package:flutter/material.dart';
import 'package:macos_ui/macos_ui.dart';
class FieldsPage extends StatefulWidget {
@@ -15,16 +17,27 @@ class _FieldsPageState extends State {
toolBar: ToolBar(
title: const Text('Fields'),
titleWidth: 150.0,
- actions: [
- ToolBarIconButton(
- label: 'Toggle Sidebar',
- icon: const MacosIcon(
+ leading: MacosTooltip(
+ message: 'Toggle Sidebar',
+ useMousePosition: false,
+ child: MacosIconButton(
+ icon: MacosIcon(
CupertinoIcons.sidebar_left,
+ color: MacosTheme.brightnessOf(context).resolve(
+ const Color.fromRGBO(0, 0, 0, 0.5),
+ const Color.fromRGBO(255, 255, 255, 0.5),
+ ),
+ size: 20.0,
+ ),
+ boxConstraints: const BoxConstraints(
+ minHeight: 20,
+ minWidth: 20,
+ maxWidth: 48,
+ maxHeight: 38,
),
onPressed: () => MacosWindowScope.of(context).toggleSidebar(),
- showLabel: false,
),
- ],
+ ),
),
children: [
ContentArea(
@@ -32,7 +45,10 @@ class _FieldsPageState extends State {
return SingleChildScrollView(
padding: const EdgeInsets.all(20),
child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
children: [
+ const WidgetTextTitle1(widgetName: 'MacosTextField'),
+ Divider(color: MacosTheme.of(context).dividerColor),
const SizedBox(
width: 300.0,
child: MacosTextField(
@@ -84,6 +100,8 @@ class _FieldsPageState extends State {
),
),
const SizedBox(height: 20),
+ const WidgetTextTitle1(widgetName: 'MacosSearchField'),
+ Divider(color: MacosTheme.of(context).dividerColor),
SizedBox(
width: 300.0,
child: MacosSearchField(
@@ -125,17 +143,6 @@ class _FieldsPageState extends State {
);
},
),
- ResizablePane(
- minSize: 180,
- startSize: 200,
- windowBreakpoint: 800,
- resizableSide: ResizableSide.left,
- builder: (_, __) {
- return const Center(
- child: Text('Resizable Pane'),
- );
- },
- ),
],
);
}
@@ -352,8 +359,8 @@ const countries = [
var actionResults = [
SearchResultItem(
'Build project',
- child: Row(
- children: const [
+ child: const Row(
+ children: [
Padding(
padding: EdgeInsets.symmetric(horizontal: 8.0),
child: MacosIcon(CupertinoIcons.hammer),
@@ -365,8 +372,8 @@ var actionResults = [
),
SearchResultItem(
'Debug project',
- child: Row(
- children: const [
+ child: const Row(
+ children: [
Padding(
padding: EdgeInsets.symmetric(horizontal: 8.0),
child: MacosIcon(CupertinoIcons.tickets),
@@ -378,8 +385,8 @@ var actionResults = [
),
SearchResultItem(
'Open containing folder',
- child: Row(
- children: const [
+ child: const Row(
+ children: [
Padding(
padding: EdgeInsets.symmetric(horizontal: 8.0),
child: MacosIcon(CupertinoIcons.folder),
diff --git a/example/lib/pages/indicators_page.dart b/example/lib/pages/indicators_page.dart
index 1dd4caf5..511b40c6 100644
--- a/example/lib/pages/indicators_page.dart
+++ b/example/lib/pages/indicators_page.dart
@@ -1,3 +1,5 @@
+import 'package:example/widgets/widget_text_title1.dart';
+import 'package:example/widgets/widget_text_title2.dart';
import 'package:macos_ui/macos_ui.dart';
// ignore: implementation_imports
import 'package:macos_ui/src/library.dart';
@@ -20,16 +22,27 @@ class _IndicatorsPageState extends State {
toolBar: ToolBar(
title: const Text('Indicators'),
titleWidth: 150.0,
- actions: [
- ToolBarIconButton(
- label: 'Toggle Sidebar',
- icon: const MacosIcon(
+ leading: MacosTooltip(
+ message: 'Toggle Sidebar',
+ useMousePosition: false,
+ child: MacosIconButton(
+ icon: MacosIcon(
CupertinoIcons.sidebar_left,
+ color: MacosTheme.brightnessOf(context).resolve(
+ const Color.fromRGBO(0, 0, 0, 0.5),
+ const Color.fromRGBO(255, 255, 255, 0.5),
+ ),
+ size: 20.0,
+ ),
+ boxConstraints: const BoxConstraints(
+ minHeight: 20,
+ minWidth: 20,
+ maxWidth: 48,
+ maxHeight: 38,
),
onPressed: () => MacosWindowScope.of(context).toggleSidebar(),
- showLabel: false,
),
- ],
+ ),
),
children: [
ContentArea(
@@ -37,47 +50,103 @@ class _IndicatorsPageState extends State {
return SingleChildScrollView(
padding: const EdgeInsets.all(20),
child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
children: [
- CapacityIndicator(
- value: capacitorValue,
- onChanged: (v) => setState(() => capacitorValue = v),
- splits: 20,
- discrete: true,
+ const WidgetTextTitle1(widgetName: 'CapacityIndicator'),
+ Divider(color: MacosTheme.of(context).dividerColor),
+ Row(
+ children: [
+ const Text('Standard'),
+ const SizedBox(width: 8),
+ Expanded(
+ child: CapacityIndicator(
+ value: capacitorValue,
+ onChanged: (v) => setState(() => capacitorValue = v),
+ ),
+ ),
+ ],
),
- const SizedBox(height: 20),
- CapacityIndicator(
- value: capacitorValue,
- onChanged: (v) => setState(() => capacitorValue = v),
+ const SizedBox(height: 16),
+ Row(
+ children: [
+ const Text('Discrete'),
+ const SizedBox(width: 8),
+ Expanded(
+ child: CapacityIndicator(
+ value: capacitorValue,
+ onChanged: (v) => setState(() => capacitorValue = v),
+ splits: 20,
+ discrete: true,
+ ),
+ ),
+ ],
),
const SizedBox(height: 20),
- MacosSlider(
- value: sliderValue,
- onChanged: (v) => setState(() => sliderValue = v),
+ const WidgetTextTitle1(widgetName: 'MacosSlider'),
+ Divider(color: MacosTheme.of(context).dividerColor),
+ Row(
+ children: [
+ const Text('Standard'),
+ const SizedBox(width: 8),
+ Expanded(
+ child: MacosSlider(
+ value: sliderValue,
+ onChanged: (v) => setState(() => sliderValue = v),
+ ),
+ ),
+ ],
),
- const SizedBox(height: 20),
- MacosSlider(
- value: sliderValue,
- discrete: true,
- onChanged: (v) => setState(() => sliderValue = v),
+ const SizedBox(height: 16),
+ Row(
+ children: [
+ const Text('Discrete'),
+ const SizedBox(width: 8),
+ Expanded(
+ child: MacosSlider(
+ value: sliderValue,
+ discrete: true,
+ onChanged: (v) => setState(() => sliderValue = v),
+ ),
+ ),
+ ],
),
const SizedBox(height: 20),
+ const WidgetTextTitle1(widgetName: 'RatingIndicator'),
+ Divider(color: MacosTheme.of(context).dividerColor),
RatingIndicator(
value: ratingValue,
onChanged: (v) => setState(() => ratingValue = v),
),
const SizedBox(height: 20),
- const ProgressCircle(),
- const SizedBox(height: 20),
- const RelevanceIndicator(
- value: 25,
- amount: 50,
+ Text(
+ 'Progress Indicators',
+ style: MacosTypography.of(context).title1,
),
- const SizedBox(height: 20),
- const Label(
- icon: MacosIcon(CupertinoIcons.tag),
- text: SelectableText('A determinate progress circle: '),
- child: ProgressCircle(value: 50),
+ Divider(color: MacosTheme.of(context).dividerColor),
+ const WidgetTextTitle2(widgetName: 'ProgressBar'),
+ const SizedBox(height: 8),
+ const ProgressBar(value: 50),
+ const SizedBox(height: 16),
+ const WidgetTextTitle2(widgetName: 'ProgressCircle'),
+ const SizedBox(height: 8),
+ const Row(
+ children: [
+ Text('Indeterminate'),
+ SizedBox(width: 8),
+ ProgressCircle(),
+ ],
),
+ const Row(
+ children: [
+ Text('Determinate'),
+ SizedBox(width: 8),
+ ProgressCircle(value: 50),
+ ],
+ ),
+ const SizedBox(height: 20),
+ const WidgetTextTitle1(widgetName: 'RelevanceIndicator'),
+ Divider(color: MacosTheme.of(context).dividerColor),
+ const SizedBox(height: 8),
],
),
);
diff --git a/example/lib/pages/resizable_pane_page.dart b/example/lib/pages/resizable_pane_page.dart
new file mode 100644
index 00000000..78ed3320
--- /dev/null
+++ b/example/lib/pages/resizable_pane_page.dart
@@ -0,0 +1,86 @@
+import 'package:flutter/cupertino.dart';
+import 'package:macos_ui/macos_ui.dart';
+
+class ResizablePanePage extends StatefulWidget {
+ const ResizablePanePage({super.key});
+
+ @override
+ State createState() => _ResizablePanePageState();
+}
+
+class _ResizablePanePageState extends State {
+ @override
+ Widget build(BuildContext context) {
+ return MacosScaffold(
+ toolBar: ToolBar(
+ title: const Text('Resizable Pane'),
+ leading: MacosTooltip(
+ message: 'Toggle Sidebar',
+ useMousePosition: false,
+ child: MacosIconButton(
+ icon: MacosIcon(
+ CupertinoIcons.sidebar_left,
+ color: MacosTheme.brightnessOf(context).resolve(
+ const Color.fromRGBO(0, 0, 0, 0.5),
+ const Color.fromRGBO(255, 255, 255, 0.5),
+ ),
+ size: 20.0,
+ ),
+ boxConstraints: const BoxConstraints(
+ minHeight: 20,
+ minWidth: 20,
+ maxWidth: 48,
+ maxHeight: 38,
+ ),
+ onPressed: () => MacosWindowScope.of(context).toggleSidebar(),
+ ),
+ ),
+ ),
+ children: [
+ ResizablePane(
+ minSize: 180,
+ startSize: 200,
+ windowBreakpoint: 700,
+ resizableSide: ResizableSide.right,
+ builder: (_, __) {
+ return const Center(
+ child: Text('Left Resizable Pane'),
+ );
+ },
+ ),
+ ContentArea(
+ builder: (_, __) {
+ return Column(
+ children: [
+ const Flexible(
+ fit: FlexFit.loose,
+ child: Center(
+ child: Text('Content Area'),
+ ),
+ ),
+ ResizablePane(
+ minSize: 50,
+ startSize: 200,
+ //windowBreakpoint: 600,
+ builder: (_, __) {
+ return const Center(
+ child: Text('Bottom Resizable Pane'),
+ );
+ },
+ resizableSide: ResizableSide.top,
+ ),
+ ],
+ );
+ },
+ ),
+ const ResizablePane.noScrollBar(
+ minSize: 180,
+ startSize: 200,
+ windowBreakpoint: 700,
+ resizableSide: ResizableSide.right,
+ child: Center(child: Text('Right non-scrollable Resizable Pane')),
+ ),
+ ],
+ );
+ }
+}
diff --git a/example/lib/pages/selectors_page.dart b/example/lib/pages/selectors_page.dart
index 8256a33e..74ed4b47 100644
--- a/example/lib/pages/selectors_page.dart
+++ b/example/lib/pages/selectors_page.dart
@@ -1,4 +1,8 @@
+import 'package:example/widgets/widget_text_title1.dart';
+import 'package:example/widgets/widget_text_title2.dart';
import 'package:flutter/cupertino.dart';
+import 'package:flutter/foundation.dart';
+import 'package:flutter/material.dart';
import 'package:macos_ui/macos_ui.dart';
class SelectorsPage extends StatefulWidget {
@@ -15,16 +19,27 @@ class _SelectorsPageState extends State {
toolBar: ToolBar(
title: const Text('Selectors'),
titleWidth: 150.0,
- actions: [
- ToolBarIconButton(
- label: 'Toggle Sidebar',
- icon: const MacosIcon(
+ leading: MacosTooltip(
+ message: 'Toggle Sidebar',
+ useMousePosition: false,
+ child: MacosIconButton(
+ icon: MacosIcon(
CupertinoIcons.sidebar_left,
+ color: MacosTheme.brightnessOf(context).resolve(
+ const Color.fromRGBO(0, 0, 0, 0.5),
+ const Color.fromRGBO(255, 255, 255, 0.5),
+ ),
+ size: 20.0,
+ ),
+ boxConstraints: const BoxConstraints(
+ minHeight: 20,
+ minWidth: 20,
+ maxWidth: 48,
+ maxHeight: 38,
),
onPressed: () => MacosWindowScope.of(context).toggleSidebar(),
- showLabel: false,
),
- ],
+ ),
),
children: [
ContentArea(
@@ -33,23 +48,45 @@ class _SelectorsPageState extends State {
controller: scrollController,
padding: const EdgeInsets.all(20),
child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
children: [
+ Text(
+ 'Date & Time Pickers',
+ style: MacosTypography.of(context).title1,
+ ),
+ Divider(color: MacosTheme.of(context).dividerColor),
Row(
- mainAxisAlignment: MainAxisAlignment.spaceEvenly,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
- MacosDatePicker(
- onDateChanged: (date) => debugPrint('$date'),
+ Column(
+ children: [
+ const WidgetTextTitle2(widgetName: 'MacosDatePicker'),
+ const SizedBox(height: 12),
+ MacosDatePicker(
+ onDateChanged: (date) => debugPrint('$date'),
+ ),
+ ],
),
- MacosTimePicker(
- onTimeChanged: (time) => debugPrint('$time'),
+ const SizedBox(width: 50),
+ Column(
+ children: [
+ const WidgetTextTitle2(widgetName: 'MacosTimePicker'),
+ const SizedBox(height: 12),
+ MacosTimePicker(
+ onTimeChanged: (time) => debugPrint('$time'),
+ ),
+ ],
),
],
),
- const SizedBox(height: 50),
- MacosColorWell(
- onColorSelected: (color) => debugPrint('$color'),
- ),
+ const SizedBox(height: 20),
+ if (!kIsWeb) ...[
+ const WidgetTextTitle1(widgetName: 'MacosColorWell'),
+ Divider(color: MacosTheme.of(context).dividerColor),
+ MacosColorWell(
+ onColorSelected: (color) => debugPrint('$color'),
+ ),
+ ],
],
),
);
diff --git a/example/lib/pages/sliver_toolbar_page.dart b/example/lib/pages/sliver_toolbar_page.dart
index 8c2039a5..be2e680c 100644
--- a/example/lib/pages/sliver_toolbar_page.dart
+++ b/example/lib/pages/sliver_toolbar_page.dart
@@ -3,7 +3,27 @@ import 'package:flutter/material.dart';
import 'package:macos_ui/macos_ui.dart';
class SliverToolbarPage extends StatefulWidget {
- const SliverToolbarPage({super.key});
+ const SliverToolbarPage({super.key, required this.isVisible});
+
+ /// Whether this [SliverToolbarPage] is currently visible on the screen
+ /// (that is, not e.g. hidden by an [IndexedStack]).
+ ///
+ /// By default, macos_ui applies wallpaper tinting to the application's
+ /// window to match macOS' native appearance:
+ ///
+ ///
+ ///
+ /// However, this effect is realized by inserting `NSVisualEffectView`s behind
+ /// Flutter's canvas and turning the background of areas that are meant to be
+ /// affected by wallpaper tinting transparent. Since Flutter's
+ /// [`ImageFilter.blur`](https://api.flutter.dev/flutter/dart-ui/ImageFilter/ImageFilter.blur.html)
+ /// does not support transparency, wallpaper tinting is disabled automatically
+ /// when this widget's [isVisible] is true.
+ ///
+ /// This is meant to be a temporary solution until
+ /// [#16296](https://github.com/flutter/flutter/issues/16296) is resolved in
+ /// the Flutter project.
+ final bool isVisible;
@override
State createState() => _SliverToolbarPageState();
@@ -27,6 +47,7 @@ class _SliverToolbarPageState extends State {
floating: floating,
pinned: pinned,
toolbarOpacity: opacity,
+ allowWallpaperTintingOverrides: widget.isVisible,
actions: [
ToolBarIconButton(
label: 'Pinned',
diff --git a/example/lib/pages/tabview_page.dart b/example/lib/pages/tabview_page.dart
index 64d4d8ff..5e224429 100644
--- a/example/lib/pages/tabview_page.dart
+++ b/example/lib/pages/tabview_page.dart
@@ -17,8 +17,29 @@ class _TabViewPageState extends State {
@override
Widget build(BuildContext context) {
return MacosScaffold(
- toolBar: const ToolBar(
- title: Text('TabView'),
+ toolBar: ToolBar(
+ title: const Text('TabView'),
+ leading: MacosTooltip(
+ message: 'Toggle Sidebar',
+ useMousePosition: false,
+ child: MacosIconButton(
+ icon: MacosIcon(
+ CupertinoIcons.sidebar_left,
+ color: MacosTheme.brightnessOf(context).resolve(
+ const Color.fromRGBO(0, 0, 0, 0.5),
+ const Color.fromRGBO(255, 255, 255, 0.5),
+ ),
+ size: 20.0,
+ ),
+ boxConstraints: const BoxConstraints(
+ minHeight: 20,
+ minWidth: 20,
+ maxWidth: 48,
+ maxHeight: 38,
+ ),
+ onPressed: () => MacosWindowScope.of(context).toggleSidebar(),
+ ),
+ ),
),
children: [
ContentArea(
diff --git a/example/lib/pages/toolbar_page.dart b/example/lib/pages/toolbar_page.dart
index 6e1754ce..104bd7c1 100644
--- a/example/lib/pages/toolbar_page.dart
+++ b/example/lib/pages/toolbar_page.dart
@@ -160,9 +160,9 @@ class _ToolbarPageState extends State {
return SingleChildScrollView(
controller: scrollController,
padding: const EdgeInsets.all(30),
- child: Center(
+ child: const Center(
child: Column(
- children: const [
+ children: [
Text(
'A toolbar provides convenient access to frequently used commands and controls that perform actions relevant to the current view.',
textAlign: TextAlign.center,
diff --git a/example/lib/pages/typography_page.dart b/example/lib/pages/typography_page.dart
new file mode 100644
index 00000000..3779cb57
--- /dev/null
+++ b/example/lib/pages/typography_page.dart
@@ -0,0 +1,394 @@
+import 'package:flutter/cupertino.dart';
+import 'package:macos_ui/macos_ui.dart';
+
+class TypographyPage extends StatelessWidget {
+ const TypographyPage({super.key});
+
+ @override
+ Widget build(BuildContext context) {
+ final typography = MacosTypography.of(context);
+ final secondaryTypography = MacosTypography(
+ color: MacosTheme.brightnessOf(context).isDark
+ ? MacosColors.secondaryLabelColor.darkColor
+ : MacosColors.secondaryLabelColor,
+ );
+ final tertiaryTypography = MacosTypography(
+ color: MacosTheme.brightnessOf(context).isDark
+ ? MacosColors.tertiaryLabelColor.darkColor
+ : MacosColors.tertiaryLabelColor,
+ );
+
+ return MacosScaffold(
+ toolBar: const ToolBar(
+ title: Text('Typography'),
+ ),
+ children: [
+ ContentArea(
+ builder: (context, scrollController) {
+ return ListView(
+ children: [
+ Padding(
+ padding: const EdgeInsets.all(16.0),
+ child: Row(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ const Text('Label Color'),
+ const SizedBox(height: 42.0),
+ Text('LargeTitle', style: typography.largeTitle),
+ const SizedBox(height: 8.0),
+ Text(
+ 'LargeTitle',
+ style: typography.largeTitle
+ .copyWith(fontWeight: FontWeight.w700),
+ ),
+ const SizedBox(height: 24.0),
+ Text('Title1', style: typography.title1),
+ const SizedBox(height: 8.0),
+ Text(
+ 'Title1',
+ style: typography.title1
+ .copyWith(fontWeight: FontWeight.w700),
+ ),
+ const SizedBox(height: 24.0),
+ Text('Title2', style: typography.title2),
+ const SizedBox(height: 8.0),
+ Text(
+ 'Title2',
+ style: typography.title2
+ .copyWith(fontWeight: FontWeight.w700),
+ ),
+ const SizedBox(height: 24.0),
+ Text('Title3', style: typography.title3),
+ const SizedBox(height: 8.0),
+ Text(
+ 'Title3',
+ style: typography.title3
+ .copyWith(fontWeight: FontWeight.w600),
+ ),
+ const SizedBox(height: 24.0),
+ Text('Headline', style: typography.headline),
+ const SizedBox(height: 8.0),
+ Text(
+ 'Headline',
+ style: typography.headline
+ .copyWith(fontWeight: MacosFontWeight.w860),
+ ),
+ const SizedBox(height: 24.0),
+ Text('Body', style: typography.body),
+ const SizedBox(height: 8.0),
+ Text(
+ 'Body',
+ style: typography.body
+ .copyWith(fontWeight: MacosFontWeight.w590),
+ ),
+ const SizedBox(height: 24.0),
+ Text('Callout', style: typography.callout),
+ const SizedBox(height: 8.0),
+ Text(
+ 'Callout',
+ style: typography.callout
+ .copyWith(fontWeight: MacosFontWeight.w590),
+ ),
+ const SizedBox(height: 24.0),
+ Text('Subheadline', style: typography.subheadline),
+ const SizedBox(height: 8.0),
+ Text(
+ 'Subheadline',
+ style: typography.subheadline
+ .copyWith(fontWeight: MacosFontWeight.w590),
+ ),
+ const SizedBox(height: 24.0),
+ Text('Footnote', style: typography.subheadline),
+ const SizedBox(height: 8.0),
+ Text(
+ 'Footnote',
+ style: typography.subheadline
+ .copyWith(fontWeight: MacosFontWeight.w590),
+ ),
+ const SizedBox(height: 24.0),
+ Text('Caption1', style: typography.caption1),
+ const SizedBox(height: 8.0),
+ Text(
+ 'Caption1',
+ style: typography.caption1
+ .copyWith(fontWeight: MacosFontWeight.w510),
+ ),
+ const SizedBox(height: 24.0),
+ Text('Caption2', style: typography.caption2),
+ const SizedBox(height: 8.0),
+ Text(
+ 'Caption2',
+ style: typography.caption2
+ .copyWith(fontWeight: MacosFontWeight.w590),
+ ),
+ ],
+ ),
+ Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ const Text('Secondary Label Color'),
+ const SizedBox(height: 42.0),
+ Text(
+ 'LargeTitle',
+ style: secondaryTypography.largeTitle,
+ ),
+ const SizedBox(height: 8.0),
+ Text(
+ 'LargeTitle',
+ style: secondaryTypography.largeTitle
+ .copyWith(fontWeight: FontWeight.w700),
+ ),
+ const SizedBox(height: 24.0),
+ Text(
+ 'Title1',
+ style: secondaryTypography.title1,
+ ),
+ const SizedBox(height: 8.0),
+ Text(
+ 'Title1',
+ style: secondaryTypography.title1
+ .copyWith(fontWeight: FontWeight.w700),
+ ),
+ const SizedBox(height: 24.0),
+ Text(
+ 'Title2',
+ style: secondaryTypography.title2,
+ ),
+ const SizedBox(height: 8.0),
+ Text(
+ 'Title2',
+ style: secondaryTypography.title2
+ .copyWith(fontWeight: FontWeight.w700),
+ ),
+ const SizedBox(height: 24.0),
+ Text(
+ 'Title3',
+ style: secondaryTypography.title3,
+ ),
+ const SizedBox(height: 8.0),
+ Text(
+ 'Title3',
+ style: secondaryTypography.title3
+ .copyWith(fontWeight: FontWeight.w600),
+ ),
+ const SizedBox(height: 24.0),
+ Text(
+ 'Headline',
+ style: secondaryTypography.headline,
+ ),
+ const SizedBox(height: 8.0),
+ Text(
+ 'Headline',
+ style: secondaryTypography.headline
+ .copyWith(fontWeight: MacosFontWeight.w860),
+ ),
+ const SizedBox(height: 24.0),
+ Text(
+ 'Body',
+ style: secondaryTypography.body,
+ ),
+ const SizedBox(height: 8.0),
+ Text(
+ 'Body',
+ style: secondaryTypography.body
+ .copyWith(fontWeight: MacosFontWeight.w590),
+ ),
+ const SizedBox(height: 24.0),
+ Text(
+ 'Callout',
+ style: secondaryTypography.callout,
+ ),
+ const SizedBox(height: 8.0),
+ Text(
+ 'Callout',
+ style: secondaryTypography.callout
+ .copyWith(fontWeight: MacosFontWeight.w590),
+ ),
+ const SizedBox(height: 24.0),
+ Text(
+ 'Subheadline',
+ style: secondaryTypography.subheadline,
+ ),
+ const SizedBox(height: 8.0),
+ Text(
+ 'Subheadline',
+ style: secondaryTypography.subheadline
+ .copyWith(fontWeight: FontWeight.w600),
+ ),
+ const SizedBox(height: 24.0),
+ Text(
+ 'Footnote',
+ style: secondaryTypography.footnote,
+ ),
+ const SizedBox(height: 8.0),
+ Text(
+ 'Footnote',
+ style: secondaryTypography.footnote
+ .copyWith(fontWeight: MacosFontWeight.w590),
+ ),
+ const SizedBox(height: 24.0),
+ Text(
+ 'Caption1',
+ style: secondaryTypography.caption1,
+ ),
+ const SizedBox(height: 8.0),
+ Text(
+ 'Caption1',
+ style: secondaryTypography.caption1
+ .copyWith(fontWeight: MacosFontWeight.w510),
+ ),
+ const SizedBox(height: 24.0),
+ Text(
+ 'Caption2',
+ style: secondaryTypography.caption2,
+ ),
+ const SizedBox(height: 8.0),
+ Text(
+ 'Caption2',
+ style: secondaryTypography.caption2
+ .copyWith(fontWeight: MacosFontWeight.w590),
+ ),
+ ],
+ ),
+ Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ const Text('Tertiary Label Color'),
+ const SizedBox(height: 42.0),
+ Text(
+ 'LargeTitle',
+ style: tertiaryTypography.largeTitle,
+ ),
+ const SizedBox(height: 8.0),
+ Text(
+ 'LargeTitle',
+ style: tertiaryTypography.largeTitle
+ .copyWith(fontWeight: FontWeight.w700),
+ ),
+ const SizedBox(height: 24.0),
+ Text(
+ 'Title1',
+ style: tertiaryTypography.title1,
+ ),
+ const SizedBox(height: 8.0),
+ Text(
+ 'Title1',
+ style: tertiaryTypography.title1
+ .copyWith(fontWeight: FontWeight.w700),
+ ),
+ const SizedBox(height: 24.0),
+ Text(
+ 'Title2',
+ style: tertiaryTypography.title2,
+ ),
+ const SizedBox(height: 8.0),
+ Text(
+ 'Title2',
+ style: tertiaryTypography.title2
+ .copyWith(fontWeight: FontWeight.w700),
+ ),
+ const SizedBox(height: 24.0),
+ Text(
+ 'Title3',
+ style: tertiaryTypography.title3,
+ ),
+ const SizedBox(height: 8.0),
+ Text(
+ 'Title3',
+ style: tertiaryTypography.title3
+ .copyWith(fontWeight: FontWeight.w600),
+ ),
+ const SizedBox(height: 24.0),
+ Text(
+ 'Headline',
+ style: tertiaryTypography.headline,
+ ),
+ const SizedBox(height: 8.0),
+ Text(
+ 'Headline',
+ style: tertiaryTypography.headline
+ .copyWith(fontWeight: MacosFontWeight.w860),
+ ),
+ const SizedBox(height: 24.0),
+ Text(
+ 'Body',
+ style: tertiaryTypography.body,
+ ),
+ const SizedBox(height: 8.0),
+ Text(
+ 'Body',
+ style: tertiaryTypography.body
+ .copyWith(fontWeight: MacosFontWeight.w590),
+ ),
+ const SizedBox(height: 24.0),
+ Text(
+ 'Callout',
+ style: tertiaryTypography.callout,
+ ),
+ const SizedBox(height: 8.0),
+ Text(
+ 'Callout',
+ style: tertiaryTypography.callout
+ .copyWith(fontWeight: MacosFontWeight.w590),
+ ),
+ const SizedBox(height: 24.0),
+ Text(
+ 'Subheadline',
+ style: tertiaryTypography.subheadline,
+ ),
+ const SizedBox(height: 8.0),
+ Text(
+ 'Subheadline',
+ style: tertiaryTypography.subheadline
+ .copyWith(fontWeight: MacosFontWeight.w590),
+ ),
+ const SizedBox(height: 24.0),
+ Text(
+ 'Footnote',
+ style: tertiaryTypography.footnote,
+ ),
+ const SizedBox(height: 8.0),
+ Text(
+ 'Footnote',
+ style: tertiaryTypography.footnote
+ .copyWith(fontWeight: MacosFontWeight.w590),
+ ),
+ const SizedBox(height: 24.0),
+ Text(
+ 'Caption1',
+ style: tertiaryTypography.caption1,
+ ),
+ const SizedBox(height: 8.0),
+ Text(
+ 'Caption1',
+ style: tertiaryTypography.caption1
+ .copyWith(fontWeight: MacosFontWeight.w510),
+ ),
+ const SizedBox(height: 24.0),
+ Text(
+ 'Caption2',
+ style: tertiaryTypography.caption2,
+ ),
+ const SizedBox(height: 8.0),
+ Text(
+ 'Caption2',
+ style: tertiaryTypography.caption2
+ .copyWith(fontWeight: MacosFontWeight.w590),
+ ),
+ ],
+ ),
+ ],
+ ),
+ ),
+ ],
+ );
+ },
+ ),
+ ],
+ );
+ }
+}
diff --git a/example/lib/platform_menus.dart b/example/lib/platform_menus.dart
new file mode 100644
index 00000000..91ba6a7c
--- /dev/null
+++ b/example/lib/platform_menus.dart
@@ -0,0 +1,52 @@
+import 'package:flutter/foundation.dart' show kIsWeb;
+import 'dart:io' as io;
+
+import 'package:flutter/widgets.dart'
+ show
+ PlatformMenu,
+ PlatformMenuItem,
+ PlatformProvidedMenuItem,
+ PlatformProvidedMenuItemType;
+
+List menuBarItems() {
+ if (kIsWeb) {
+ return [];
+ } else {
+ if (io.Platform.isMacOS) {
+ return const [
+ PlatformMenu(
+ label: 'macos_ui Widget Gallery',
+ menus: [
+ PlatformProvidedMenuItem(
+ type: PlatformProvidedMenuItemType.about,
+ ),
+ PlatformProvidedMenuItem(
+ type: PlatformProvidedMenuItemType.quit,
+ ),
+ ],
+ ),
+ PlatformMenu(
+ label: 'View',
+ menus: [
+ PlatformProvidedMenuItem(
+ type: PlatformProvidedMenuItemType.toggleFullScreen,
+ ),
+ ],
+ ),
+ PlatformMenu(
+ label: 'Window',
+ menus: [
+ PlatformProvidedMenuItem(
+ type: PlatformProvidedMenuItemType.minimizeWindow,
+ ),
+ PlatformProvidedMenuItem(
+ type: PlatformProvidedMenuItemType.zoomWindow,
+ ),
+ ],
+ ),
+ ];
+ } else {
+ return [];
+ }
+ }
+}
diff --git a/example/lib/widgets/widget_text_title1.dart b/example/lib/widgets/widget_text_title1.dart
new file mode 100644
index 00000000..77ab7314
--- /dev/null
+++ b/example/lib/widgets/widget_text_title1.dart
@@ -0,0 +1,30 @@
+import 'package:flutter/cupertino.dart';
+import 'package:google_fonts/google_fonts.dart';
+import 'package:macos_ui/macos_ui.dart';
+
+class WidgetTextTitle1 extends StatelessWidget {
+ const WidgetTextTitle1({super.key, required this.widgetName});
+
+ final String widgetName;
+
+ @override
+ Widget build(BuildContext context) {
+ return DecoratedBox(
+ decoration: BoxDecoration(
+ color: MacosColors.systemGrayColor.withOpacity(0.5),
+ borderRadius: BorderRadius.circular(4.0),
+ ),
+ child: Padding(
+ padding: const EdgeInsets.symmetric(
+ horizontal: 6.0,
+ ),
+ child: Text(
+ widgetName,
+ style: MacosTypography.of(context)
+ .title1
+ .copyWith(fontFamily: GoogleFonts.jetBrainsMono().fontFamily),
+ ),
+ ),
+ );
+ }
+}
diff --git a/example/lib/widgets/widget_text_title2.dart b/example/lib/widgets/widget_text_title2.dart
new file mode 100644
index 00000000..8da1135d
--- /dev/null
+++ b/example/lib/widgets/widget_text_title2.dart
@@ -0,0 +1,30 @@
+import 'package:flutter/cupertino.dart';
+import 'package:google_fonts/google_fonts.dart';
+import 'package:macos_ui/macos_ui.dart';
+
+class WidgetTextTitle2 extends StatelessWidget {
+ const WidgetTextTitle2({super.key, required this.widgetName});
+
+ final String widgetName;
+
+ @override
+ Widget build(BuildContext context) {
+ return DecoratedBox(
+ decoration: BoxDecoration(
+ color: MacosColors.systemGrayColor.withOpacity(0.5),
+ borderRadius: BorderRadius.circular(4.0),
+ ),
+ child: Padding(
+ padding: const EdgeInsets.symmetric(
+ horizontal: 6.0,
+ ),
+ child: Text(
+ widgetName,
+ style: MacosTypography.of(context)
+ .title2
+ .copyWith(fontFamily: GoogleFonts.jetBrainsMono().fontFamily),
+ ),
+ ),
+ );
+ }
+}
diff --git a/example/macos/Flutter/GeneratedPluginRegistrant.swift b/example/macos/Flutter/GeneratedPluginRegistrant.swift
index 722b1dd3..0179c12c 100644
--- a/example/macos/Flutter/GeneratedPluginRegistrant.swift
+++ b/example/macos/Flutter/GeneratedPluginRegistrant.swift
@@ -6,7 +6,13 @@ import FlutterMacOS
import Foundation
import macos_ui
+import macos_window_utils
+import path_provider_foundation
+import url_launcher_macos
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
MacOSUiPlugin.register(with: registry.registrar(forPlugin: "MacOSUiPlugin"))
+ MacOSWindowUtilsPlugin.register(with: registry.registrar(forPlugin: "MacOSWindowUtilsPlugin"))
+ PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
+ UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
}
diff --git a/example/macos/Podfile b/example/macos/Podfile
index 049abe29..7ed4260c 100644
--- a/example/macos/Podfile
+++ b/example/macos/Podfile
@@ -1,4 +1,4 @@
-platform :osx, '10.14'
+platform :osx, '10.14.6'
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
diff --git a/example/macos/Podfile.lock b/example/macos/Podfile.lock
index 67acca3c..2fa81174 100644
--- a/example/macos/Podfile.lock
+++ b/example/macos/Podfile.lock
@@ -2,21 +2,40 @@ PODS:
- FlutterMacOS (1.0.0)
- macos_ui (0.1.0):
- FlutterMacOS
+ - macos_window_utils (1.0.0):
+ - FlutterMacOS
+ - path_provider_foundation (0.0.1):
+ - Flutter
+ - FlutterMacOS
+ - url_launcher_macos (0.0.1):
+ - FlutterMacOS
DEPENDENCIES:
- FlutterMacOS (from `Flutter/ephemeral`)
- macos_ui (from `Flutter/ephemeral/.symlinks/plugins/macos_ui/macos`)
+ - macos_window_utils (from `Flutter/ephemeral/.symlinks/plugins/macos_window_utils/macos`)
+ - path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`)
+ - url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`)
EXTERNAL SOURCES:
FlutterMacOS:
:path: Flutter/ephemeral
macos_ui:
:path: Flutter/ephemeral/.symlinks/plugins/macos_ui/macos
+ macos_window_utils:
+ :path: Flutter/ephemeral/.symlinks/plugins/macos_window_utils/macos
+ path_provider_foundation:
+ :path: Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin
+ url_launcher_macos:
+ :path: Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos
SPEC CHECKSUMS:
FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24
macos_ui: 6229a8922cd97bafb7d9636c8eb8dfb0744183ca
+ macos_window_utils: 933f91f64805e2eb91a5bd057cf97cd097276663
+ path_provider_foundation: eaf5b3e458fc0e5fbb9940fb09980e853fe058b8
+ url_launcher_macos: d2691c7dd33ed713bf3544850a623080ec693d95
-PODFILE CHECKSUM: 353c8bcc5d5b0994e508d035b5431cfe18c1dea7
+PODFILE CHECKSUM: ff0a9a3ce75ee73f200ca7e2f47745698c917ef9
COCOAPODS: 1.11.3
diff --git a/example/macos/Runner.xcodeproj/project.pbxproj b/example/macos/Runner.xcodeproj/project.pbxproj
index e7c1634c..2c562a4e 100644
--- a/example/macos/Runner.xcodeproj/project.pbxproj
+++ b/example/macos/Runner.xcodeproj/project.pbxproj
@@ -61,7 +61,7 @@
33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = ""; };
33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; };
33CC10F72044A3C60003C045 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = Runner/Info.plist; sourceTree = ""; };
- 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainFlutterWindow.swift; sourceTree = ""; };
+ 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */ = {isa = PBXFileReference; indentWidth = 2; lastKnownFileType = sourcecode.swift; path = MainFlutterWindow.swift; sourceTree = ""; tabWidth = 2; };
33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Debug.xcconfig"; sourceTree = ""; };
33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Release.xcconfig"; sourceTree = ""; };
33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "Flutter-Generated.xcconfig"; path = "ephemeral/Flutter-Generated.xcconfig"; sourceTree = ""; };
@@ -94,7 +94,6 @@
94ECD9F878BC8EB5F0E7094E /* Pods-Runner.release.xcconfig */,
AFB798A3289226D0E5AB9985 /* Pods-Runner.profile.xcconfig */,
);
- name = Pods;
path = Pods;
sourceTree = "";
};
@@ -405,7 +404,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
- MACOSX_DEPLOYMENT_TARGET = 10.14;
+ MACOSX_DEPLOYMENT_TARGET = 10.14.6;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = macosx;
SWIFT_COMPILATION_MODE = wholemodule;
@@ -484,7 +483,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
- MACOSX_DEPLOYMENT_TARGET = 10.14;
+ MACOSX_DEPLOYMENT_TARGET = 10.14.6;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = macosx;
@@ -531,7 +530,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
- MACOSX_DEPLOYMENT_TARGET = 10.14;
+ MACOSX_DEPLOYMENT_TARGET = 10.14.6;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = macosx;
SWIFT_COMPILATION_MODE = wholemodule;
diff --git a/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json
index a2ec33f1..0a0928ee 100644
--- a/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json
+++ b/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json
@@ -1,68 +1,68 @@
{
"images" : [
{
- "size" : "16x16",
- "idiom" : "mac",
"filename" : "app_icon_16.png",
- "scale" : "1x"
+ "idiom" : "mac",
+ "scale" : "1x",
+ "size" : "16x16"
},
{
- "size" : "16x16",
+ "filename" : "app_icon_32 1.png",
"idiom" : "mac",
- "filename" : "app_icon_32.png",
- "scale" : "2x"
+ "scale" : "2x",
+ "size" : "16x16"
},
{
- "size" : "32x32",
- "idiom" : "mac",
"filename" : "app_icon_32.png",
- "scale" : "1x"
+ "idiom" : "mac",
+ "scale" : "1x",
+ "size" : "32x32"
},
{
- "size" : "32x32",
- "idiom" : "mac",
"filename" : "app_icon_64.png",
- "scale" : "2x"
+ "idiom" : "mac",
+ "scale" : "2x",
+ "size" : "32x32"
},
{
- "size" : "128x128",
- "idiom" : "mac",
"filename" : "app_icon_128.png",
- "scale" : "1x"
+ "idiom" : "mac",
+ "scale" : "1x",
+ "size" : "128x128"
},
{
- "size" : "128x128",
+ "filename" : "app_icon_256 1.png",
"idiom" : "mac",
- "filename" : "app_icon_256.png",
- "scale" : "2x"
+ "scale" : "2x",
+ "size" : "128x128"
},
{
- "size" : "256x256",
- "idiom" : "mac",
"filename" : "app_icon_256.png",
- "scale" : "1x"
+ "idiom" : "mac",
+ "scale" : "1x",
+ "size" : "256x256"
},
{
- "size" : "256x256",
+ "filename" : "app_icon_512 1.png",
"idiom" : "mac",
- "filename" : "app_icon_512.png",
- "scale" : "2x"
+ "scale" : "2x",
+ "size" : "256x256"
},
{
- "size" : "512x512",
- "idiom" : "mac",
"filename" : "app_icon_512.png",
- "scale" : "1x"
+ "idiom" : "mac",
+ "scale" : "1x",
+ "size" : "512x512"
},
{
- "size" : "512x512",
- "idiom" : "mac",
"filename" : "app_icon_1024.png",
- "scale" : "2x"
+ "idiom" : "mac",
+ "scale" : "2x",
+ "size" : "512x512"
}
],
"info" : {
- "version" : 1,
- "author" : "xcode"
+ "author" : "xcode",
+ "version" : 1
}
}
diff --git a/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png b/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png
index 3c4935a7..82b6f9d9 100644
Binary files a/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png and b/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png differ
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 ed4cc164..13b35eba 100644
Binary files a/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png and b/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png differ
diff --git a/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png b/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png
index 483be613..0a3f5fa4 100644
Binary files a/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png and b/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png differ
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 00000000..bdb57226
Binary files /dev/null and b/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256 1.png differ
diff --git a/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png b/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png
index bcbf36df..bdb57226 100644
Binary files a/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png and b/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png differ
diff --git a/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32 1.png b/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32 1.png
new file mode 100644
index 00000000..f083318e
Binary files /dev/null and b/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32 1.png differ
diff --git a/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png b/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png
index 9c0a6528..f083318e 100644
Binary files a/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png and b/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png differ
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 00000000..326c0e72
Binary files /dev/null and b/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512 1.png differ
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 e71a7261..326c0e72 100644
Binary files a/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png and b/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png differ
diff --git a/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png b/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png
index 8a31fe2d..2f1632cf 100644
Binary files a/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png and b/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png differ
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/macos/Runner/MainFlutterWindow.swift b/example/macos/Runner/MainFlutterWindow.swift
index 22fb7aee..2722837e 100644
--- a/example/macos/Runner/MainFlutterWindow.swift
+++ b/example/macos/Runner/MainFlutterWindow.swift
@@ -1,82 +1,15 @@
import Cocoa
import FlutterMacOS
-class BlurryContainerViewController: NSViewController {
- let flutterViewController = FlutterViewController()
-
- init() {
- super.init(nibName: nil, bundle: nil)
- }
-
- required init?(coder: NSCoder) {
- fatalError()
- }
-
- override func loadView() {
- let blurView = NSVisualEffectView()
- blurView.autoresizingMask = [.width, .height]
- blurView.blendingMode = .behindWindow
- blurView.state = .active
- if #available(macOS 10.14, *) {
- blurView.material = .sidebar
- }
- self.view = blurView
- }
-
- override func viewDidLoad() {
- super.viewDidLoad()
-
- self.addChild(flutterViewController)
-
- flutterViewController.view.frame = self.view.bounds
- flutterViewController.backgroundColor = .clear
- flutterViewController.view.autoresizingMask = [.width, .height]
- self.view.addSubview(flutterViewController.view)
- }
-}
-
-class MainFlutterWindow: NSWindow, NSWindowDelegate {
+class MainFlutterWindow: NSWindow {
override func awakeFromNib() {
- delegate = self
- let blurryContainerViewController = BlurryContainerViewController()
+ let flutterViewController = FlutterViewController.init()
let windowFrame = self.frame
- self.contentViewController = blurryContainerViewController
+ self.contentViewController = flutterViewController
self.setFrame(windowFrame, display: true)
- if #available(macOS 10.13, *) {
- let customToolbar = NSToolbar()
- customToolbar.showsBaselineSeparator = false
- self.toolbar = customToolbar
- }
- self.titleVisibility = .hidden
- self.titlebarAppearsTransparent = true
- if #available(macOS 11.0, *) {
- // Use .expanded if the app will have a title bar, else use .unified
- self.toolbarStyle = .unified
- }
-
- self.isMovableByWindowBackground = true
- self.styleMask.insert(NSWindow.StyleMask.fullSizeContentView)
-
- self.isOpaque = false
- self.backgroundColor = .clear
-
- RegisterGeneratedPlugins(registry: blurryContainerViewController.flutterViewController)
+ RegisterGeneratedPlugins(registry: flutterViewController)
super.awakeFromNib()
}
-
- // Hides the toolbar when in fullscreen mode
- func window(_ window: NSWindow, willUseFullScreenPresentationOptions proposedOptions: NSApplication.PresentationOptions = []) -> NSApplication.PresentationOptions {
-
- return [.autoHideToolbar, .autoHideMenuBar, .fullScreen]
- }
-
- func windowWillEnterFullScreen(_ notification: Notification) {
- self.toolbar?.isVisible = false
- }
-
- func windowDidExitFullScreen(_ notification: Notification) {
- self.toolbar?.isVisible = true
- }
}
diff --git a/example/macos/RunnerTests/RunnerTests.swift b/example/macos/RunnerTests/RunnerTests.swift
new file mode 100644
index 00000000..5418c9f5
--- /dev/null
+++ b/example/macos/RunnerTests/RunnerTests.swift
@@ -0,0 +1,12 @@
+import FlutterMacOS
+import Cocoa
+import XCTest
+
+class RunnerTests: XCTestCase {
+
+ func testExample() {
+ // If you add code to the Runner application, consider adding tests here.
+ // See https://developer.apple.com/documentation/xctest for more information about using XCTest.
+ }
+
+}
diff --git a/example/pubspec.lock b/example/pubspec.lock
index cf8e59b8..dc586a2a 100644
--- a/example/pubspec.lock
+++ b/example/pubspec.lock
@@ -5,10 +5,10 @@ packages:
dependency: transitive
description:
name: async
- sha256: bfe67ef28df125b7dddcea62755991f807aa39a2492a23e1550161692950bbe0
+ sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c"
url: "https://pub.dev"
source: hosted
- version: "2.10.0"
+ version: "2.11.0"
boolean_selector:
dependency: transitive
description:
@@ -21,10 +21,10 @@ packages:
dependency: transitive
description:
name: characters
- sha256: e6a326c8af69605aec75ed6c187d06b349707a27fbff8222ca9cc2cff167975c
+ sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605"
url: "https://pub.dev"
source: hosted
- version: "1.2.1"
+ version: "1.3.0"
clock:
dependency: transitive
description:
@@ -37,10 +37,18 @@ packages:
dependency: transitive
description:
name: collection
- sha256: cfc915e6923fe5ce6e153b0723c753045de46de1b4d63771530504004a45fae0
+ sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.17.1"
+ crypto:
+ dependency: transitive
+ description:
+ name: crypto
+ sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab
url: "https://pub.dev"
source: hosted
- version: "1.17.0"
+ version: "3.0.3"
cupertino_icons:
dependency: "direct main"
description:
@@ -57,6 +65,22 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.3.1"
+ ffi:
+ dependency: transitive
+ description:
+ name: ffi
+ sha256: ed5337a5660c506388a9f012be0288fb38b49020ce2b45fe1f8b8323fe429f99
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.0.2"
+ file:
+ dependency: transitive
+ description:
+ name: file
+ sha256: "1b92bec4fc2a72f59a8e15af5f52cd441e4a7860b49499d69dfa817af20e925d"
+ url: "https://pub.dev"
+ source: hosted
+ version: "6.1.4"
flutter:
dependency: "direct main"
description: flutter
@@ -66,46 +90,83 @@ packages:
dependency: "direct dev"
description:
name: flutter_lints
- sha256: aeb0b80a8b3709709c9cc496cdc027c5b3216796bc0af0ce1007eaf24464fd4c
+ sha256: "2118df84ef0c3ca93f96123a616ae8540879991b8b57af2f81b76a7ada49b2a4"
url: "https://pub.dev"
source: hosted
- version: "2.0.1"
+ version: "2.0.2"
flutter_test:
dependency: "direct dev"
description: flutter
source: sdk
version: "0.0.0"
+ flutter_web_plugins:
+ dependency: transitive
+ description: flutter
+ source: sdk
+ version: "0.0.0"
+ google_fonts:
+ dependency: "direct main"
+ description:
+ name: google_fonts
+ sha256: e20ff62b158b96f392bfc8afe29dee1503c94fbea2cbe8186fd59b756b8ae982
+ url: "https://pub.dev"
+ source: hosted
+ version: "5.1.0"
+ http:
+ dependency: transitive
+ description:
+ name: http
+ sha256: "759d1a329847dd0f39226c688d3e06a6b8679668e350e2891a6474f8b4bb8525"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.1.0"
+ http_parser:
+ dependency: transitive
+ description:
+ name: http_parser
+ sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b"
+ url: "https://pub.dev"
+ source: hosted
+ version: "4.0.2"
js:
dependency: transitive
description:
name: js
- sha256: "5528c2f391ededb7775ec1daa69e65a2d61276f7552de2b5f7b8d34ee9fd4ab7"
+ sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3
url: "https://pub.dev"
source: hosted
- version: "0.6.5"
+ version: "0.6.7"
lints:
dependency: transitive
description:
name: lints
- sha256: "5e4a9cd06d447758280a8ac2405101e0e2094d2a1dbdd3756aec3fe7775ba593"
+ sha256: "6b0206b0bf4f04961fc5438198ccb3a885685cd67d4d4a32cc20ad7f8adbe015"
url: "https://pub.dev"
source: hosted
- version: "2.0.1"
+ version: "2.1.0"
macos_ui:
dependency: "direct main"
description:
path: ".."
relative: true
source: path
- version: "1.12.2"
+ version: "2.0.0"
+ macos_window_utils:
+ dependency: transitive
+ description:
+ name: macos_window_utils
+ sha256: b78a210aa70ca7ccad6e7b7b810fb4689c507f4a46e299214900b2a1eb70ea23
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.1.3"
matcher:
dependency: transitive
description:
name: matcher
- sha256: "16db949ceee371e9b99d22f88fa3a73c4e59fd0afed0bd25fc336eb76c198b72"
+ sha256: "6501fbd55da300384b768785b83e5ce66991266cec21af89ab9ae7f5ce1c4cbb"
url: "https://pub.dev"
source: hosted
- version: "0.12.13"
+ version: "0.12.15"
material_color_utilities:
dependency: transitive
description:
@@ -118,10 +179,10 @@ packages:
dependency: transitive
description:
name: meta
- sha256: "6c268b42ed578a53088d834796959e4a1814b5e9e164f147f580a386e5decf42"
+ sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3"
url: "https://pub.dev"
source: hosted
- version: "1.8.0"
+ version: "1.9.1"
nested:
dependency: transitive
description:
@@ -134,10 +195,82 @@ packages:
dependency: transitive
description:
name: path
- sha256: db9d4f58c908a4ba5953fcee2ae317c94889433e5024c27ce74a37f94267945b
+ sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.8.3"
+ path_provider:
+ dependency: transitive
+ description:
+ name: path_provider
+ sha256: "3087813781ab814e4157b172f1a11c46be20179fcc9bea043e0fba36bc0acaa2"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.0.15"
+ path_provider_android:
+ dependency: transitive
+ description:
+ name: path_provider_android
+ sha256: "2cec049d282c7f13c594b4a73976b0b4f2d7a1838a6dd5aaf7bd9719196bee86"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.0.27"
+ path_provider_foundation:
+ dependency: transitive
+ description:
+ name: path_provider_foundation
+ sha256: "1995d88ec2948dac43edf8fe58eb434d35d22a2940ecee1a9fefcd62beee6eb3"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.2.3"
+ path_provider_linux:
+ dependency: transitive
+ description:
+ name: path_provider_linux
+ sha256: ffbb8cc9ed2c9ec0e4b7a541e56fd79b138e8f47d2fb86815f15358a349b3b57
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.1.11"
+ path_provider_platform_interface:
+ dependency: transitive
+ description:
+ name: path_provider_platform_interface
+ sha256: "57585299a729335f1298b43245842678cb9f43a6310351b18fb577d6e33165ec"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.0.6"
+ path_provider_windows:
+ dependency: transitive
+ description:
+ name: path_provider_windows
+ sha256: "1cb68ba4cd3a795033de62ba1b7b4564dace301f952de6bfb3cd91b202b6ee96"
url: "https://pub.dev"
source: hosted
- version: "1.8.2"
+ version: "2.1.7"
+ platform:
+ dependency: transitive
+ description:
+ name: platform
+ sha256: "4a451831508d7d6ca779f7ac6e212b4023dd5a7d08a27a63da33756410e32b76"
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.1.0"
+ plugin_platform_interface:
+ dependency: transitive
+ description:
+ name: plugin_platform_interface
+ sha256: "6a2128648c854906c53fa8e33986fc0247a1116122f9534dd20e3ab9e16a32bc"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.1.4"
+ process:
+ dependency: transitive
+ description:
+ name: process
+ sha256: "53fd8db9cec1d37b0574e12f07520d582019cb6c44abf5479a01505099a34a09"
+ url: "https://pub.dev"
+ source: hosted
+ version: "4.2.4"
provider:
dependency: "direct main"
description:
@@ -195,10 +328,82 @@ packages:
dependency: transitive
description:
name: test_api
- sha256: ad540f65f92caa91bf21dfc8ffb8c589d6e4dc0c2267818b4cc2792857706206
+ sha256: eb6ac1540b26de412b3403a163d919ba86f6a973fe6cc50ae3541b80092fdcfb
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.5.1"
+ typed_data:
+ dependency: transitive
+ description:
+ name: typed_data
+ sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.3.2"
+ url_launcher:
+ dependency: "direct main"
+ description:
+ name: url_launcher
+ sha256: "781bd58a1eb16069412365c98597726cd8810ae27435f04b3b4d3a470bacd61e"
+ url: "https://pub.dev"
+ source: hosted
+ version: "6.1.12"
+ url_launcher_android:
+ dependency: transitive
+ description:
+ name: url_launcher_android
+ sha256: "15f5acbf0dce90146a0f5a2c4a002b1814a6303c4c5c075aa2623b2d16156f03"
+ url: "https://pub.dev"
+ source: hosted
+ version: "6.0.36"
+ url_launcher_ios:
+ dependency: transitive
+ description:
+ name: url_launcher_ios
+ sha256: "9af7ea73259886b92199f9e42c116072f05ff9bea2dcb339ab935dfc957392c2"
url: "https://pub.dev"
source: hosted
- version: "0.4.16"
+ version: "6.1.4"
+ url_launcher_linux:
+ dependency: transitive
+ description:
+ name: url_launcher_linux
+ sha256: "207f4ddda99b95b4d4868320a352d374b0b7e05eefad95a4a26f57da413443f5"
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.0.5"
+ url_launcher_macos:
+ dependency: transitive
+ description:
+ name: url_launcher_macos
+ sha256: "1c4fdc0bfea61a70792ce97157e5cc17260f61abbe4f39354513f39ec6fd73b1"
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.0.6"
+ url_launcher_platform_interface:
+ dependency: transitive
+ description:
+ name: url_launcher_platform_interface
+ sha256: bfdfa402f1f3298637d71ca8ecfe840b4696698213d5346e9d12d4ab647ee2ea
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.1.3"
+ url_launcher_web:
+ dependency: transitive
+ description:
+ name: url_launcher_web
+ sha256: cc26720eefe98c1b71d85f9dc7ef0cada5132617046369d9dc296b3ecaa5cbb4
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.0.18"
+ url_launcher_windows:
+ dependency: transitive
+ description:
+ name: url_launcher_windows
+ sha256: "7967065dd2b5fccc18c653b97958fdf839c5478c28e767c61ee879f4e7882422"
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.0.7"
vector_math:
dependency: transitive
description:
@@ -207,6 +412,22 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.1.4"
+ win32:
+ dependency: transitive
+ description:
+ name: win32
+ sha256: dfdf0136e0aa7a1b474ea133e67cb0154a0acd2599c4f3ada3b49d38d38793ee
+ url: "https://pub.dev"
+ source: hosted
+ version: "5.0.5"
+ xdg_directories:
+ dependency: transitive
+ description:
+ name: xdg_directories
+ sha256: ee1505df1426458f7f60aac270645098d318a8b4766d85fde75f76f2e21807d1
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.0.0"
sdks:
- dart: ">=2.18.0 <3.0.0"
- flutter: ">=1.20.0"
+ dart: ">=3.0.0 <4.0.0"
+ flutter: ">=3.10.0"
diff --git a/example/pubspec.yaml b/example/pubspec.yaml
index e68c5535..0a008b51 100644
--- a/example/pubspec.yaml
+++ b/example/pubspec.yaml
@@ -4,7 +4,7 @@ publish_to: 'none'
version: 1.0.0+1
environment:
- sdk: '>=2.17.0 <3.0.0'
+ sdk: '>=3.0.0 <4.0.0'
dependencies:
flutter:
@@ -13,12 +13,14 @@ dependencies:
cupertino_icons: ^1.0.5
macos_ui:
path: ..
- provider: ^6.0.3
+ provider: ^6.0.5
+ google_fonts: ^5.1.0
+ url_launcher: ^6.1.12
dev_dependencies:
flutter_test:
sdk: flutter
- flutter_lints: ^2.0.1
+ flutter_lints: ^2.0.2
flutter:
assets:
diff --git a/lib/macos_ui.dart b/lib/macos_ui.dart
index 6a71ceec..62dcc602 100644
--- a/lib/macos_ui.dart
+++ b/lib/macos_ui.dart
@@ -14,6 +14,9 @@
library macos_ui;
+export 'package:macos_window_utils/macos/ns_window_delegate.dart';
+export 'package:macos_window_utils/macos_window_utils.dart';
+
export 'src/buttons/back_button.dart';
export 'src/buttons/checkbox.dart';
export 'src/buttons/disclosure_button.dart';
@@ -29,6 +32,7 @@ export 'src/buttons/toolbar/toolbar_icon_button.dart';
export 'src/buttons/toolbar/toolbar_overflow_button.dart';
export 'src/buttons/toolbar/toolbar_pulldown_button.dart';
export 'src/dialogs/macos_alert_dialog.dart';
+export 'src/enums/control_size.dart';
export 'src/fields/search_field.dart';
export 'src/fields/text_field.dart';
export 'src/icon/image_icon.dart';
@@ -37,7 +41,6 @@ export 'src/indicators/capacity_indicators.dart';
export 'src/indicators/progress_indicators.dart';
export 'src/indicators/rating_indicator.dart';
export 'src/indicators/relevance_indicator.dart';
-export 'src/layout/scrollbar.dart';
export 'src/indicators/slider.dart';
export 'src/labels/label.dart';
export 'src/labels/tooltip.dart';
@@ -45,6 +48,7 @@ export 'src/layout/content_area.dart';
export 'src/layout/macos_list_tile.dart';
export 'src/layout/resizable_pane.dart';
export 'src/layout/scaffold.dart';
+export 'src/layout/scrollbar.dart';
export 'src/layout/sidebar/sidebar.dart';
export 'src/layout/sidebar/sidebar_item.dart';
export 'src/layout/sidebar/sidebar_items.dart';
@@ -60,8 +64,10 @@ export 'src/layout/toolbar/toolbar_overflow_menu.dart';
export 'src/layout/toolbar/toolbar_overflow_menu_item.dart';
export 'src/layout/toolbar/toolbar_popup.dart';
export 'src/layout/toolbar/toolbar_spacer.dart';
+export 'src/layout/wallpaper_tinted_area.dart';
export 'src/layout/window.dart';
export 'src/macos_app.dart';
+export 'src/macos_window_utils_config.dart';
export 'src/selectors/color_well.dart';
export 'src/selectors/date_picker.dart';
export 'src/selectors/time_picker.dart';
diff --git a/lib/src/buttons/back_button.dart b/lib/src/buttons/back_button.dart
index 5bce067b..605356a4 100644
--- a/lib/src/buttons/back_button.dart
+++ b/lib/src/buttons/back_button.dart
@@ -196,7 +196,7 @@ class MacosBackButtonState extends State
: _isHovered
? hoverColor
: fillColor,
- borderRadius: BorderRadius.circular(7),
+ borderRadius: const BorderRadius.all(Radius.circular(7)),
),
child: Icon(
CupertinoIcons.back,
diff --git a/lib/src/buttons/checkbox.dart b/lib/src/buttons/checkbox.dart
index 93e0b1c0..d7f8bcc3 100644
--- a/lib/src/buttons/checkbox.dart
+++ b/lib/src/buttons/checkbox.dart
@@ -15,7 +15,7 @@ class MacosCheckbox extends StatelessWidget {
super.key,
required this.value,
required this.onChanged,
- this.size = 16.0,
+ this.size = 14.0,
this.activeColor,
this.disabledColor = CupertinoColors.quaternaryLabel,
this.offBorderColor = CupertinoColors.tertiaryLabel,
@@ -106,20 +106,57 @@ class MacosCheckbox extends StatelessWidget {
: activeColor ?? theme.primaryColor,
context,
),
- borderRadius: BorderRadius.circular(4.0),
+ borderRadius: const BorderRadius.all(Radius.circular(4.0)),
)
- : BoxDecoration(
- color: isLight ? null : CupertinoColors.tertiaryLabel,
- border: Border.all(
- style: isLight ? BorderStyle.solid : BorderStyle.none,
- width: 0.5,
- color: MacosDynamicColor.resolve(
- offBorderColor,
- context,
+ : isLight
+ ? ShapeDecoration(
+ gradient: LinearGradient(
+ begin: const Alignment(0.0, -1.0),
+ end: const Alignment(0, 0),
+ colors: [
+ Colors.white.withOpacity(0.85),
+ Colors.white.withOpacity(1.0),
+ ],
+ ),
+ shadows: const [
+ BoxShadow(
+ color: Color(0x3F000000),
+ blurRadius: 1,
+ blurStyle: BlurStyle.inner,
+ offset: Offset(0, 0),
+ spreadRadius: 0.0,
+ ),
+ ],
+ shape: RoundedRectangleBorder(
+ side: BorderSide(
+ width: 0.25,
+ color: Colors.black.withOpacity(0.35000000596046448),
+ ),
+ borderRadius:
+ const BorderRadius.all(Radius.circular(3.5)),
+ ),
+ )
+ : ShapeDecoration(
+ gradient: LinearGradient(
+ begin: const Alignment(0.0, -1.0),
+ end: const Alignment(0, 1),
+ colors: [
+ Colors.white.withOpacity(0.14000000059604645),
+ Colors.white.withOpacity(0.2800000011920929),
+ ],
+ ),
+ shape: const RoundedRectangleBorder(
+ borderRadius: BorderRadius.all(Radius.circular(3)),
+ ),
+ shadows: const [
+ BoxShadow(
+ color: Color(0x3F000000),
+ blurRadius: 1,
+ offset: Offset(0, 0),
+ spreadRadius: 0,
+ ),
+ ],
),
- ),
- borderRadius: BorderRadius.circular(4.0),
- ),
child: Icon(
isDisabled || value == false
? null
diff --git a/lib/src/buttons/disclosure_button.dart b/lib/src/buttons/disclosure_button.dart
index 3a8a3d6b..5cef6e0e 100644
--- a/lib/src/buttons/disclosure_button.dart
+++ b/lib/src/buttons/disclosure_button.dart
@@ -177,7 +177,7 @@ class MacosDisclosureButtonState extends State
? const MacosColor(0xff3C383C)
: const MacosColor(0xffE5E5E5)
: fillColor,
- borderRadius: BorderRadius.circular(7),
+ borderRadius: const BorderRadius.all(Radius.circular(7)),
),
child: RotatedBox(
quarterTurns: widget.isPressed ? 1 : 3,
diff --git a/lib/src/buttons/help_button.dart b/lib/src/buttons/help_button.dart
index 0c5d5ece..f825baf5 100644
--- a/lib/src/buttons/help_button.dart
+++ b/lib/src/buttons/help_button.dart
@@ -190,6 +190,8 @@ class HelpButtonState extends State
constraints: const BoxConstraints(
minWidth: 20,
minHeight: 20,
+ maxWidth: 20,
+ maxHeight: 20,
),
child: FadeTransition(
opacity: _opacityAnimation,
@@ -212,16 +214,14 @@ class HelpButtonState extends State
),
],
),
- child: Padding(
- padding: const EdgeInsets.all(8),
- child: Align(
- alignment: widget.alignment,
- widthFactor: 1.0,
- heightFactor: 1.0,
- child: Icon(
- CupertinoIcons.question,
- color: foregroundColor,
- ),
+ child: Align(
+ alignment: widget.alignment,
+ widthFactor: 1.0,
+ heightFactor: 1.0,
+ child: Icon(
+ CupertinoIcons.question,
+ color: foregroundColor,
+ size: 13,
),
),
),
diff --git a/lib/src/buttons/icon_button.dart b/lib/src/buttons/icon_button.dart
index 9b339aa7..88e8ef20 100644
--- a/lib/src/buttons/icon_button.dart
+++ b/lib/src/buttons/icon_button.dart
@@ -253,7 +253,7 @@ class MacosIconButtonState extends State
borderRadius: widget.borderRadius != null
? widget.borderRadius
: widget.shape == BoxShape.rectangle
- ? BorderRadius.circular(7.0)
+ ? const BorderRadius.all(Radius.circular(7))
: null,
color: !enabled
? disabledColor
diff --git a/lib/src/buttons/popup_button.dart b/lib/src/buttons/popup_button.dart
index a08cb113..8158e63c 100644
--- a/lib/src/buttons/popup_button.dart
+++ b/lib/src/buttons/popup_button.dart
@@ -1091,8 +1091,7 @@ class _MacosPopupButtonState extends State>
void _handleTap() {
final TextDirection? textDirection = Directionality.maybeOf(context);
- const EdgeInsetsGeometry menuMargin =
- EdgeInsetsDirectional.only(start: 4.0, end: 4.0);
+ const EdgeInsetsGeometry menuMargin = EdgeInsets.symmetric(horizontal: 4.0);
final List<_MenuItem> menuItems = <_MenuItem>[
for (int index = 0; index < widget.items!.length; index += 1)
@@ -1239,7 +1238,7 @@ class _MacosPopupButtonState extends State>
boxShadow: [
BoxShadow(
color: buttonStyles.borderColor,
- offset: const Offset(0, .5),
+ offset: const Offset(0, 0.5),
blurRadius: 0.2,
spreadRadius: 0,
),
@@ -1251,7 +1250,7 @@ class _MacosPopupButtonState extends State>
),
borderRadius: _kBorderRadius,
),
- padding: const EdgeInsets.fromLTRB(8.0, 0.0, 2.0, 0.0),
+ padding: const EdgeInsets.only(left: 8.0, right: 2.0),
height: _kPopupButtonHeight,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
diff --git a/lib/src/buttons/pulldown_button.dart b/lib/src/buttons/pulldown_button.dart
index dbe7e0d1..91de97a7 100644
--- a/lib/src/buttons/pulldown_button.dart
+++ b/lib/src/buttons/pulldown_button.dart
@@ -807,8 +807,7 @@ class _MacosPulldownButtonState extends State
void _handleTap() {
final TextDirection? textDirection = Directionality.maybeOf(context);
- const EdgeInsetsGeometry menuMargin =
- EdgeInsetsDirectional.only(start: 4.0, end: 4.0);
+ const EdgeInsetsGeometry menuMargin = EdgeInsets.symmetric(horizontal: 4.0);
final List<_MenuItem> menuItems = <_MenuItem>[
for (int index = 0; index < widget.items!.length; index += 1)
@@ -904,7 +903,7 @@ class _MacosPulldownButtonState extends State
boxShadow: [
BoxShadow(
color: buttonStyles.borderColor,
- offset: const Offset(0, .5),
+ offset: const Offset(0, 0.5),
blurRadius: 0.2,
spreadRadius: 0,
),
@@ -913,7 +912,7 @@ class _MacosPulldownButtonState extends State
color: buttonStyles.bgColor,
borderRadius: borderRadius,
),
- padding: const EdgeInsets.fromLTRB(8.0, 0.0, 2.0, 0.0),
+ padding: const EdgeInsets.only(left: 8.0, right: 2.0),
height: buttonHeight,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
diff --git a/lib/src/buttons/push_button.dart b/lib/src/buttons/push_button.dart
index 6f759c1d..19180016 100644
--- a/lib/src/buttons/push_button.dart
+++ b/lib/src/buttons/push_button.dart
@@ -5,36 +5,120 @@ import 'package:flutter/rendering.dart';
import 'package:macos_ui/macos_ui.dart';
import 'package:macos_ui/src/library.dart';
-/// The sizes a [PushButton] can be.
-enum ButtonSize {
- /// A large [PushButton].
- large,
-
- /// A small [PushButton].
- small,
-}
-
-const EdgeInsetsGeometry _kSmallButtonPadding = EdgeInsets.symmetric(
- vertical: 3.0,
- horizontal: 8.0,
+const _kMiniButtonSize = Size(26.0, 11.0);
+const _kSmallButtonSize = Size(39.0, 14.0);
+const _kRegularButtonSize = Size(60.0, 18.0);
+const _kLargeButtonSize = Size(48.0, 26.0);
+
+const _kMiniButtonPadding = EdgeInsets.only(left: 6.0, right: 6.0, bottom: 1.0);
+const _kSmallButtonPadding = EdgeInsets.symmetric(
+ vertical: 1.0,
+ horizontal: 7.0,
);
-const EdgeInsetsGeometry _kLargeButtonPadding = EdgeInsets.symmetric(
- vertical: 6.0,
- horizontal: 8.0,
+const _kRegularButtonPadding = EdgeInsets.only(
+ left: 8.0,
+ right: 8.0,
+ top: 1.0,
+ bottom: 4.0,
);
+const _kLargeButtonPadding = EdgeInsets.only(
+ right: 8.0,
+ left: 8.0,
+ bottom: 1.0,
+);
+
+const _kMiniButtonRadius = BorderRadius.all(Radius.circular(2.0));
+const _kSmallButtonRadius = BorderRadius.all(Radius.circular(2.0));
+const _kRegularButtonRadius = BorderRadius.all(Radius.circular(5.0));
+const _kLargeButtonRadius = BorderRadius.all(Radius.circular(7.0));
+
+/// Shortcuts for various [PushButton] properties based on the [ControlSize].
+extension PushButtonControlSizeX on ControlSize {
+ /// Determines the padding of the button's text.
+ EdgeInsetsGeometry get padding {
+ switch (this) {
+ case ControlSize.mini:
+ return _kMiniButtonPadding;
+ case ControlSize.small:
+ return _kSmallButtonPadding;
+ case ControlSize.regular:
+ return _kRegularButtonPadding;
+ case ControlSize.large:
+ return _kLargeButtonPadding;
+ }
+ }
+
+ /// Determines the button's border radius.
+ BorderRadiusGeometry get borderRadius {
+ switch (this) {
+ case ControlSize.mini:
+ return _kMiniButtonRadius;
+ case ControlSize.small:
+ return _kSmallButtonRadius;
+ case ControlSize.regular:
+ return _kRegularButtonRadius;
+ case ControlSize.large:
+ return _kLargeButtonRadius;
+ }
+ }
+
+ /// Determines the styling of the button's text.
+ TextStyle textStyle(TextStyle baseStyle) {
+ switch (this) {
+ case ControlSize.mini:
+ return baseStyle.copyWith(fontSize: 9.0);
+ case ControlSize.small:
+ return baseStyle.copyWith(fontSize: 11.0);
+ case ControlSize.regular:
+ return baseStyle.copyWith(fontSize: 13.0);
+ case ControlSize.large:
+ return baseStyle;
+ }
+ }
-const BorderRadius _kSmallButtonRadius = BorderRadius.all(Radius.circular(5.0));
-const BorderRadius _kLargeButtonRadius = BorderRadius.all(Radius.circular(7.0));
+ /// Determines the button's minimum size.
+ BoxConstraints get constraints {
+ switch (this) {
+ case ControlSize.mini:
+ return BoxConstraints(
+ minHeight: _kMiniButtonSize.height,
+ minWidth: _kMiniButtonSize.width,
+ );
+ case ControlSize.small:
+ return BoxConstraints(
+ minHeight: _kSmallButtonSize.height,
+ minWidth: _kSmallButtonSize.width,
+ );
+ case ControlSize.regular:
+ return BoxConstraints(
+ minHeight: _kRegularButtonSize.height,
+ minWidth: _kRegularButtonSize.width,
+ );
+ case ControlSize.large:
+ return BoxConstraints(
+ minHeight: _kLargeButtonSize.height,
+ minWidth: _kLargeButtonSize.width,
+ );
+ }
+ }
+}
/// {@template pushButton}
-/// A macOS-style button.
+/// A control that initiates an action.
+///
+/// Push Buttons are the standard button type in macOS.
+///
+/// Reference:
+/// * [Button (SwiftUI)](https://developer.apple.com/documentation/SwiftUI/Button)
+/// * [NSButton (AppKit)](https://developer.apple.com/documentation/appkit/nsbutton)
+/// * [Buttons (Human Interface Guidelines)](https://developer.apple.com/design/human-interface-guidelines/buttons)
/// {@endtemplate}
class PushButton extends StatefulWidget {
/// {@macro pushButton}
const PushButton({
super.key,
required this.child,
- required this.buttonSize,
+ required this.controlSize,
this.padding,
this.color,
this.disabledColor,
@@ -44,7 +128,7 @@ class PushButton extends StatefulWidget {
this.alignment = Alignment.center,
this.semanticLabel,
this.mouseCursor = SystemMouseCursors.basic,
- this.isSecondary,
+ this.secondary,
}) : assert(pressedOpacity == null ||
(pressedOpacity >= 0.0 && pressedOpacity <= 1.0));
@@ -55,12 +139,8 @@ class PushButton extends StatefulWidget {
/// The size of the button.
///
- /// Must be either [ButtonSize.small] or [ButtonSize.large].
///
- /// Small buttons have a `padding` of [_kSmallButtonPadding] and a
- /// `borderRadius` of [_kSmallButtonRadius]. Large buttons have a `padding`
- /// of [_kLargeButtonPadding] and a `borderRadius` of [_kLargeButtonRadius].
- final ButtonSize buttonSize;
+ final ControlSize controlSize;
/// The amount of space to surround the child inside the bounds of the button.
///
@@ -116,8 +196,8 @@ class PushButton extends StatefulWidget {
/// Whether the button is used as a secondary action button (e.g. Cancel buttons in dialogs)
///
/// Sets its background color to [PushButtonThemeData]'s [secondaryColor] attributes (defaults
- /// are gray colors). Can still be overriden if the [color] attribute is non-null.
- final bool? isSecondary;
+ /// are gray colors). Can still be overridden if the [color] attribute is non-null.
+ final bool? secondary;
/// Whether the button is enabled or disabled. Buttons are disabled by default. To
/// enable a button, set its [onPressed] property to a non-null value.
@@ -126,7 +206,7 @@ class PushButton extends StatefulWidget {
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
- properties.add(EnumProperty('buttonSize', buttonSize));
+ properties.add(EnumProperty('controlSize', controlSize));
properties.add(ColorProperty('color', color));
properties.add(ColorProperty('disabledColor', disabledColor));
properties.add(DoubleProperty('pressedOpacity', pressedOpacity));
@@ -138,7 +218,7 @@ class PushButton extends StatefulWidget {
value: enabled,
ifFalse: 'disabled',
));
- properties.add(DiagnosticsProperty('isSecondary', isSecondary));
+ properties.add(DiagnosticsProperty('secondary', secondary));
}
@override
@@ -224,7 +304,7 @@ class PushButtonState extends State
Widget build(BuildContext context) {
assert(debugCheckHasMacosTheme(context));
final bool enabled = widget.enabled;
- final bool isSecondary = widget.isSecondary != null && widget.isSecondary!;
+ final bool isSecondary = widget.secondary != null && widget.secondary!;
final MacosThemeData theme = MacosTheme.of(context);
final Color backgroundColor = MacosDynamicColor.resolve(
widget.color ??
@@ -234,22 +314,9 @@ class PushButtonState extends State
context,
);
- final Color disabledColor = MacosDynamicColor.resolve(
- widget.disabledColor ?? theme.pushButtonTheme.disabledColor!,
- context,
- );
-
- final EdgeInsetsGeometry? buttonPadding = widget.padding == null
- ? widget.buttonSize == ButtonSize.small
- ? _kSmallButtonPadding
- : _kLargeButtonPadding
- : widget.padding;
-
- final BorderRadiusGeometry? borderRadius = widget.borderRadius == null
- ? widget.buttonSize == ButtonSize.small
- ? _kSmallButtonRadius
- : _kLargeButtonRadius
- : widget.borderRadius;
+ final disabledColor = !isSecondary
+ ? backgroundColor.withOpacity(0.5)
+ : backgroundColor.withOpacity(0.25);
final Color foregroundColor = widget.enabled
? textLuminance(backgroundColor)
@@ -257,8 +324,7 @@ class PushButtonState extends State
? const Color.fromRGBO(255, 255, 255, 0.25)
: const Color.fromRGBO(0, 0, 0, 0.25);
- final TextStyle textStyle =
- theme.typography.headline.copyWith(color: foregroundColor);
+ final baseStyle = theme.typography.body.copyWith(color: foregroundColor);
return MouseRegion(
cursor: widget.mouseCursor!,
@@ -272,25 +338,25 @@ class PushButtonState extends State
button: true,
label: widget.semanticLabel,
child: ConstrainedBox(
- constraints: const BoxConstraints(
- minWidth: 49,
- minHeight: 20,
- ),
+ constraints: widget.controlSize.constraints,
child: FadeTransition(
opacity: _opacityAnimation,
child: DecoratedBox(
- decoration: BoxDecoration(
- borderRadius: borderRadius,
- color: !enabled ? disabledColor : backgroundColor,
+ decoration: ShapeDecoration(
+ shape: RoundedRectangleBorder(
+ borderRadius: widget.controlSize.borderRadius,
+ ),
+ // color: !enabled ? disabledColor : backgroundColor,
+ color: enabled ? backgroundColor : disabledColor,
),
child: Padding(
- padding: buttonPadding!,
+ padding: widget.controlSize.padding,
child: Align(
alignment: widget.alignment,
widthFactor: 1.0,
heightFactor: 1.0,
child: DefaultTextStyle(
- style: textStyle,
+ style: widget.controlSize.textStyle(baseStyle),
child: widget.child,
),
),
diff --git a/lib/src/buttons/segmented_control.dart b/lib/src/buttons/segmented_control.dart
index 411fd8d1..45f36539 100644
--- a/lib/src/buttons/segmented_control.dart
+++ b/lib/src/buttons/segmented_control.dart
@@ -54,8 +54,8 @@ class _MacosSegmentedControlState extends State {
const Color(0xFFDBDCDE),
const Color(0xFF4F5155),
),
- offset: const Offset(0, .5),
- spreadRadius: .5,
+ offset: const Offset(0, 0.5),
+ spreadRadius: 0.5,
),
],
borderRadius: const BorderRadius.all(
diff --git a/lib/src/buttons/switch.dart b/lib/src/buttons/switch.dart
index 6dd4375e..25d7f637 100644
--- a/lib/src/buttons/switch.dart
+++ b/lib/src/buttons/switch.dart
@@ -1,23 +1,48 @@
-import 'package:flutter/cupertino.dart' as c;
-import 'package:flutter/foundation.dart';
+import 'dart:ui';
+
import 'package:flutter/gestures.dart';
+import 'package:flutter/rendering.dart';
import 'package:macos_ui/macos_ui.dart';
import 'package:macos_ui/src/library.dart';
+const _kDefaultBorderColor = CupertinoDynamicColor.withBrightness(
+ color: MacosColor.fromRGBO(215, 215, 215, 1.0),
+ darkColor: MacosColor.fromRGBO(101, 101, 101, 1.0),
+);
+
+const _kDefaultTrackColor = CupertinoDynamicColor.withBrightness(
+ color: MacosColor.fromRGBO(228, 226, 228, 1.0),
+ darkColor: MacosColor.fromRGBO(66, 66, 66, 1.0),
+);
+
+// Dark color might be Color.fromRGBO(255, 255, 255, 0.721)??
+const _kDefaultKnobColor = CupertinoDynamicColor.withBrightness(
+ color: MacosColors.white,
+ darkColor: MacosColor.fromRGBO(207, 207, 207, 1.0),
+);
+
/// {@template macosSwitch}
-/// A switch is a visual toggle between two mutually exclusive
-/// states — on and off. A switch shows that it's on when the
-/// accent color is visible and off when the switch appears colorless.
+/// A switch is a control that offers a binary choice between two mutually
+/// exclusive states — on and off.
+///
+/// A switch shows that it's on when the [activeColor] is visible and off when
+/// the [trackColor] is visible.
+///
+/// Additional Reference:
+/// * [Toggles (Human Interface Guidelines)](https://developer.apple.com/design/human-interface-guidelines/components/selection-and-input/toggles)
+/// * [Toggles (Apple Developer)](https://developer.apple.com/documentation/swiftui/toggle)
/// {@endtemplate}
-class MacosSwitch extends StatelessWidget {
+class MacosSwitch extends StatefulWidget {
/// {@macro macosSwitch}
const MacosSwitch({
super.key,
required this.value,
+ this.size = ControlSize.regular,
required this.onChanged,
this.dragStartBehavior = DragStartBehavior.start,
this.activeColor,
this.trackColor,
+ this.knobColor,
this.semanticLabel,
});
@@ -26,6 +51,13 @@ class MacosSwitch extends StatelessWidget {
/// Must not be null.
final bool value;
+ /// The size of the switch, which is [ControlSize.regular] by default.
+ ///
+ /// Allowable sizes are [ControlSize.mini], [ControlSize.small], and
+ /// [ControlSize.regular]. If [ControlSize.large] is used, the switch will
+ /// size itself as a [ControlSize.regular] switch.
+ final ControlSize size;
+
/// Called when the user toggles with switch on or off.
///
/// The switch passes the new value to the callback but does not actually
@@ -39,7 +71,7 @@ class MacosSwitch extends StatelessWidget {
/// gets rebuilt; for example:
///
/// ```dart
- /// Switch(
+ /// MacosSwitch(
/// value: _giveVerse,
/// onChanged: (bool newValue) {
/// setState(() {
@@ -53,19 +85,25 @@ class MacosSwitch extends StatelessWidget {
/// {@macro flutter.cupertino.CupertinoSwitch.dragStartBehavior}
final DragStartBehavior dragStartBehavior;
- /// The color to use when this switch is on.
+ /// The color to use for the track when this switch is on.
///
/// Defaults to [MacosThemeData.primaryColor] when null.
- final Color? activeColor;
+ final MacosColor? activeColor;
- /// The color to use for the background when the switch is off.
+ /// The color to use for track when this switch is off.
///
- /// Defaults to [CupertinoColors.secondarySystemFill] when null.
- final Color? trackColor;
+ /// Defaults to [MacosTheme.primaryColor] when null.
+ final MacosColor? trackColor;
+
+ /// The color to use for the switch's knob.
+ final MacosColor? knobColor;
/// The semantic label used by screen readers.
final String? semanticLabel;
+ @override
+ State createState() => _MacosSwitchState();
+
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
@@ -74,6 +112,7 @@ class MacosSwitch extends StatelessWidget {
value: value,
ifFalse: 'unchecked',
));
+ properties.add(EnumProperty('size', size));
properties.add(EnumProperty('dragStartBehavior', dragStartBehavior));
properties.add(FlagProperty(
'enabled',
@@ -82,26 +121,606 @@ class MacosSwitch extends StatelessWidget {
));
properties.add(ColorProperty('activeColor', activeColor));
properties.add(ColorProperty('trackColor', trackColor));
+ properties.add(ColorProperty('knobColor', knobColor));
properties.add(StringProperty('semanticLabel', semanticLabel));
}
+}
+
+class _MacosSwitchState extends State
+ with TickerProviderStateMixin {
+ late TapGestureRecognizer _tap;
+ late HorizontalDragGestureRecognizer _drag;
+
+ late AnimationController _positionController;
+ late CurvedAnimation position;
+
+ late AnimationController _reactionController;
+ late Animation _reaction;
+
+ bool get isInteractive => widget.onChanged != null;
+
+ // A non-null boolean value that changes to true at the end of a drag if the
+ // switch must be animated to the position indicated by the widget's value.
+ bool needsPositionAnimation = false;
+
+ @override
+ void initState() {
+ super.initState();
+
+ _tap = TapGestureRecognizer()
+ ..onTapDown = _handleTapDown
+ ..onTapUp = _handleTapUp
+ ..onTap = _handleTap
+ ..onTapCancel = _handleTapCancel;
+ _drag = HorizontalDragGestureRecognizer()
+ ..onStart = _handleDragStart
+ ..onUpdate = _handleDragUpdate
+ ..onEnd = _handleDragEnd
+ ..dragStartBehavior = widget.dragStartBehavior;
+
+ _positionController = AnimationController(
+ duration: _kToggleDuration,
+ value: widget.value ? 1.0 : 0.0,
+ vsync: this,
+ );
+ position = CurvedAnimation(
+ parent: _positionController,
+ curve: Curves.linear,
+ );
+ _reactionController = AnimationController(
+ duration: _kReactionDuration,
+ vsync: this,
+ );
+ _reaction = CurvedAnimation(
+ parent: _reactionController,
+ curve: Curves.ease,
+ );
+ }
+
+ @override
+ void didUpdateWidget(MacosSwitch oldWidget) {
+ super.didUpdateWidget(oldWidget);
+ _drag.dragStartBehavior = widget.dragStartBehavior;
+
+ if (needsPositionAnimation || oldWidget.value != widget.value) {
+ _resumePositionAnimation(isLinear: needsPositionAnimation);
+ }
+ }
+
+ // `isLinear` must be true if the position animation is trying to move the
+ // knob to the closest end after the most recent drag animation, so the curve
+ // does not change when the controller's value is not 0 or 1.
+ //
+ // It can be set to false when it's an implicit animation triggered by
+ // widget.value changes.
+ void _resumePositionAnimation({bool isLinear = true}) {
+ needsPositionAnimation = false;
+ position
+ ..curve = isLinear ? Curves.linear : Curves.ease
+ ..reverseCurve = isLinear ? Curves.linear : Curves.ease.flipped;
+ if (widget.value) {
+ _positionController.forward();
+ } else {
+ _positionController.reverse();
+ }
+ }
+
+ void _handleTapDown(TapDownDetails details) {
+ if (isInteractive) {
+ needsPositionAnimation = false;
+ }
+ _reactionController.forward();
+ }
+
+ void _handleTap() {
+ if (isInteractive) {
+ widget.onChanged!(!widget.value);
+ }
+ }
+
+ void _handleTapUp(TapUpDetails details) {
+ if (isInteractive) {
+ needsPositionAnimation = false;
+ _reactionController.reverse();
+ }
+ }
+
+ void _handleTapCancel() {
+ if (isInteractive) {
+ _reactionController.reverse();
+ }
+ }
+
+ void _handleDragStart(DragStartDetails details) {
+ if (isInteractive) {
+ needsPositionAnimation = false;
+ _reactionController.forward();
+ }
+ }
+
+ void _handleDragUpdate(DragUpdateDetails details) {
+ if (isInteractive) {
+ position
+ ..curve = Curves.linear
+ ..reverseCurve = Curves.linear;
+ final double delta = details.primaryDelta! / widget.size.trackInnerLength;
+ switch (Directionality.of(context)) {
+ case TextDirection.rtl:
+ _positionController.value -= delta;
+ break;
+ case TextDirection.ltr:
+ _positionController.value += delta;
+ break;
+ }
+ }
+ }
+
+ void _handleDragEnd(DragEndDetails details) {
+ // Deferring the animation to the next build phase.
+ setState(() => needsPositionAnimation = true);
+ // Call onChanged when the user's intent to change value is clear.
+ if (position.value >= 0.5 != widget.value) {
+ widget.onChanged!(!widget.value);
+ }
+ _reactionController.reverse();
+ }
+
+ @override
+ void dispose() {
+ _tap.dispose();
+ _drag.dispose();
+
+ _positionController.dispose();
+ _reactionController.dispose();
+ super.dispose();
+ }
@override
Widget build(BuildContext context) {
assert(debugCheckHasMacosTheme(context));
final MacosThemeData theme = MacosTheme.of(context);
+ MacosColor borderColor =
+ MacosDynamicColor.resolve(_kDefaultBorderColor, context).toMacosColor();
+ MacosColor activeColor = MacosColor(MacosDynamicColor.resolve(
+ widget.activeColor ?? theme.primaryColor,
+ context,
+ ).value);
+ MacosColor trackColor = widget.trackColor ??
+ MacosDynamicColor.resolve(_kDefaultTrackColor, context).toMacosColor();
+ MacosColor knobColor = widget.knobColor ??
+ MacosDynamicColor.resolve(_kDefaultKnobColor, context).toMacosColor();
+
+ // Shot in the dark to try and get the border color correct for each
+ // possible color
+ if (widget.value) {
+ if (theme.brightness.isDark) {
+ borderColor.computeLuminance() > 0.5
+ ? borderColor = MacosColor.darken(activeColor, 20)
+ : borderColor = MacosColor.lighten(activeColor, 20);
+ } else {
+ borderColor.computeLuminance() > 0.5
+ ? borderColor = MacosColor.darken(activeColor, 20)
+ : borderColor = MacosColor.lighten(activeColor, 20);
+ }
+ }
+
return Semantics(
- label: semanticLabel,
- checked: value,
- child: c.CupertinoSwitch(
- value: value,
- onChanged: onChanged,
- dragStartBehavior: dragStartBehavior,
- activeColor: MacosDynamicColor.resolve(
- activeColor ?? theme.primaryColor,
- context,
- ),
+ label: widget.semanticLabel,
+ checked: widget.value,
+ child: _MacosSwitchRenderObjectWidget(
+ value: widget.value,
+ size: widget.size,
+ activeColor: activeColor,
trackColor: trackColor,
+ knobColor: knobColor,
+ borderColor: borderColor,
+ onChanged: widget.onChanged,
+ textDirection: Directionality.of(context),
+ state: this,
),
);
}
}
+
+class _MacosSwitchRenderObjectWidget extends LeafRenderObjectWidget {
+ const _MacosSwitchRenderObjectWidget({
+ required this.value,
+ required this.size,
+ required this.activeColor,
+ required this.trackColor,
+ required this.knobColor,
+ required this.borderColor,
+ required this.onChanged,
+ required this.textDirection,
+ required this.state,
+ });
+ final bool value;
+ final ControlSize size;
+ final MacosColor activeColor;
+ final MacosColor trackColor;
+ final MacosColor knobColor;
+ final MacosColor borderColor;
+ final ValueChanged? onChanged;
+ final TextDirection textDirection;
+ final _MacosSwitchState state;
+
+ @override
+ _RenderMacosSwitch createRenderObject(BuildContext context) {
+ return _RenderMacosSwitch(
+ value: value,
+ size: size,
+ activeColor: activeColor,
+ trackColor: trackColor,
+ knobColor: knobColor,
+ borderColor: borderColor,
+ onChanged: onChanged,
+ textDirection: textDirection,
+ state: state,
+ );
+ }
+
+ @override
+ void updateRenderObject(
+ BuildContext context,
+ _RenderMacosSwitch renderObject,
+ ) {
+ assert(renderObject._state == state);
+ renderObject
+ ..value = value
+ ..controlSize = size
+ ..activeColor = activeColor
+ ..trackColor = trackColor
+ ..knobColor = knobColor
+ ..borderColor = borderColor
+ ..onChanged = onChanged
+ ..textDirection = textDirection;
+ }
+}
+
+const Size _kMiniTrackSize = Size(26.0, 15.0);
+const Size _kSmallTrackSize = Size(32.0, 18.0);
+const Size _kRegularTrackSize = Size(38.0, 22.0);
+
+const double _kMiniKnobSize = 13.0;
+const double _kSmallKnobSize = 16.0;
+const double _kRegularKnobSize = 20.0;
+
+// Shortcuts for details about how to create the switch, based on the control
+// size.
+extension _ControlSizeX on ControlSize {
+ Size get trackSize {
+ switch (this) {
+ case ControlSize.mini:
+ return _kMiniTrackSize;
+ case ControlSize.small:
+ return _kSmallTrackSize;
+ default:
+ return _kRegularTrackSize;
+ }
+ }
+
+ double get knobSize {
+ switch (this) {
+ case ControlSize.mini:
+ return _kMiniKnobSize;
+ case ControlSize.small:
+ return _kSmallKnobSize;
+ default:
+ return _kRegularKnobSize;
+ }
+ }
+
+ double get knobRadius => knobSize / 2.0;
+ double get trackInnerStart => trackSize.height / 2.0;
+ double get trackInnerEnd => trackSize.width - trackInnerStart;
+ double get trackInnerLength => trackInnerEnd - trackInnerStart;
+}
+
+const Duration _kReactionDuration = Duration(milliseconds: 400);
+const Duration _kToggleDuration = Duration(milliseconds: 300);
+
+class _RenderMacosSwitch extends RenderConstrainedBox {
+ _RenderMacosSwitch({
+ required bool value,
+ required ControlSize size,
+ required MacosColor activeColor,
+ required MacosColor trackColor,
+ required MacosColor knobColor,
+ required MacosColor borderColor,
+ required ValueChanged? onChanged,
+ required TextDirection textDirection,
+ required _MacosSwitchState state,
+ }) : _value = value,
+ _size = size,
+ _activeColor = activeColor,
+ _trackColor = trackColor,
+ _knobPainter = MacosSwitchKnobPainter(color: knobColor),
+ _borderColor = borderColor,
+ _onChanged = onChanged,
+ _textDirection = textDirection,
+ _state = state,
+ super(
+ additionalConstraints: BoxConstraints.tightFor(
+ width: size.trackSize.width,
+ height: size.trackSize.height,
+ ),
+ ) {
+ state.position.addListener(markNeedsPaint);
+ state._reaction.addListener(markNeedsPaint);
+ }
+
+ final _MacosSwitchState _state;
+
+ bool get value => _value;
+ bool _value;
+ set value(bool newValue) {
+ if (newValue == _value) {
+ return;
+ }
+ _value = newValue;
+ markNeedsSemanticsUpdate();
+ }
+
+ ControlSize get controlSize => _size;
+ ControlSize _size;
+ set controlSize(ControlSize value) {
+ if (value == _size) {
+ return;
+ }
+ _size = value;
+ markNeedsPaint();
+ }
+
+ MacosColor get activeColor => _activeColor;
+ MacosColor _activeColor;
+ set activeColor(MacosColor value) {
+ if (value == _activeColor) {
+ return;
+ }
+ _activeColor = value;
+ markNeedsPaint();
+ }
+
+ MacosColor get trackColor => _trackColor;
+ MacosColor _trackColor;
+ set trackColor(MacosColor value) {
+ if (value == _trackColor) {
+ return;
+ }
+ _trackColor = value;
+ markNeedsPaint();
+ }
+
+ MacosColor get knobColor => _knobPainter.color;
+ MacosSwitchKnobPainter _knobPainter;
+ set knobColor(MacosColor value) {
+ if (value == knobColor) {
+ return;
+ }
+ _knobPainter = MacosSwitchKnobPainter(color: value);
+ markNeedsPaint();
+ }
+
+ MacosColor get borderColor => _borderColor;
+ MacosColor _borderColor;
+ set borderColor(MacosColor value) {
+ if (value == borderColor) {
+ return;
+ }
+ _borderColor = value;
+ markNeedsPaint();
+ }
+
+ ValueChanged? get onChanged => _onChanged;
+ ValueChanged? _onChanged;
+ set onChanged(ValueChanged? value) {
+ if (value == _onChanged) {
+ return;
+ }
+ final bool wasInteractive = isInteractive;
+ _onChanged = value;
+ if (wasInteractive != isInteractive) {
+ markNeedsPaint();
+ markNeedsSemanticsUpdate();
+ }
+ }
+
+ TextDirection get textDirection => _textDirection;
+ TextDirection _textDirection;
+ set textDirection(TextDirection value) {
+ if (value == _textDirection) {
+ return;
+ }
+ _textDirection = value;
+ markNeedsPaint();
+ }
+
+ bool get isInteractive => onChanged != null;
+
+ @override
+ bool hitTestSelf(Offset position) => true;
+
+ @override
+ void handleEvent(PointerEvent event, BoxHitTestEntry entry) {
+ assert(debugHandleEvent(event, entry));
+ if (event is PointerDownEvent && isInteractive) {
+ _state._drag.addPointer(event);
+ _state._tap.addPointer(event);
+ }
+ }
+
+ @override
+ void describeSemanticsConfiguration(SemanticsConfiguration config) {
+ super.describeSemanticsConfiguration(config);
+
+ if (isInteractive) {
+ config.onTap = _state._handleTap;
+ }
+
+ config.isEnabled = isInteractive;
+ config.isToggled = _value;
+ }
+
+ @override
+ void paint(PaintingContext context, Offset offset) {
+ final Canvas canvas = context.canvas;
+ final double currentValue = _state.position.value;
+ final trackSize = controlSize.trackSize;
+ final innerStart = controlSize.trackInnerStart;
+ final innerEnd = controlSize.trackInnerEnd;
+
+ final double visualPosition;
+ switch (textDirection) {
+ case TextDirection.rtl:
+ visualPosition = 1.0 - currentValue;
+ break;
+ case TextDirection.ltr:
+ visualPosition = currentValue;
+ break;
+ }
+
+ final Paint paint = Paint()
+ ..color = MacosColor.lerp(trackColor, activeColor, currentValue);
+
+ final Rect trackRect = Rect.fromLTWH(
+ offset.dx + (size.width - trackSize.width) / 2.0,
+ offset.dy + (size.height - trackSize.height) / 2.0,
+ trackSize.width,
+ trackSize.height,
+ );
+ final RRect trackRRect = RRect.fromRectAndRadius(
+ trackRect,
+ Radius.circular(trackSize.height / 2.0),
+ );
+ canvas.drawRRect(trackRRect, paint);
+ canvas.drawRRect(
+ trackRRect,
+ Paint()
+ ..color = borderColor
+ ..style = PaintingStyle.stroke,
+ );
+
+ final double knobLeft = lerpDouble(
+ trackRect.left + innerStart - controlSize.knobRadius,
+ trackRect.left + innerEnd - controlSize.knobRadius,
+ visualPosition,
+ )!;
+ final double knobRight = lerpDouble(
+ trackRect.left + innerStart + controlSize.knobRadius,
+ trackRect.left + innerEnd + controlSize.knobRadius,
+ visualPosition,
+ )!;
+ final double knobCenterY = offset.dy + size.height / 2.0;
+ final Rect knobBounds = Rect.fromLTRB(
+ knobLeft,
+ knobCenterY - controlSize.knobRadius,
+ knobRight,
+ knobCenterY + controlSize.knobRadius,
+ );
+
+ _clipRRectLayer.layer = context.pushClipRRect(
+ needsCompositing,
+ Offset.zero,
+ knobBounds,
+ trackRRect,
+ (PaintingContext innerContext, Offset offset) {
+ _knobPainter.paint(
+ innerContext.canvas,
+ knobBounds,
+ visualPosition == 1.0,
+ );
+ },
+ oldLayer: _clipRRectLayer.layer,
+ );
+ }
+
+ final LayerHandle _clipRRectLayer =
+ LayerHandle();
+
+ @override
+ void dispose() {
+ _clipRRectLayer.layer = null;
+ super.dispose();
+ }
+
+ @override
+ void debugFillProperties(DiagnosticPropertiesBuilder description) {
+ super.debugFillProperties(description);
+ description.add(FlagProperty(
+ 'value',
+ value: value,
+ ifTrue: 'checked',
+ ifFalse: 'unchecked',
+ showName: true,
+ ));
+ description.add(FlagProperty(
+ 'isInteractive',
+ value: isInteractive,
+ ifTrue: 'enabled',
+ ifFalse: 'disabled',
+ showName: true,
+ defaultValue: true,
+ ));
+ }
+}
+
+const List _kSwitchOffBoxShadows = [
+ BoxShadow(
+ color: Color(0x26000000),
+ // offset: Offset(1, 1),
+ blurRadius: 8.0,
+ blurStyle: BlurStyle.inner,
+ ),
+ BoxShadow(
+ color: Color(0x0F000000),
+ // offset: Offset(1, 1),
+ blurRadius: 1.0,
+ blurStyle: BlurStyle.inner,
+ ),
+];
+
+const List _kSwitchOnBoxShadows = [
+ BoxShadow(
+ color: Color(0x26000000),
+ // offset: Offset(-3, 1),
+ blurRadius: 8.0,
+ blurStyle: BlurStyle.inner,
+ ),
+ BoxShadow(
+ color: Color(0x0F000000),
+ // offset: Offset(-1, 1),
+ blurRadius: 1.0,
+ blurStyle: BlurStyle.inner,
+ ),
+];
+
+/// Paints a macOS-style switch knob.
+///
+/// Used by [MacosSwitch].
+class MacosSwitchKnobPainter {
+ /// Creates an object that paints a macOS-style switch knob.
+ const MacosSwitchKnobPainter({required this.color});
+
+ /// The color of the interior of the knob.
+ final MacosColor color;
+
+ /// Paints the knob onto the given canvas in the given rectangle.
+ void paint(Canvas canvas, Rect rect, bool isOn) {
+ final RRect rrect = RRect.fromRectAndRadius(
+ rect,
+ Radius.circular(rect.shortestSide / 2.0),
+ );
+
+ if (isOn) {
+ for (final BoxShadow shadow in _kSwitchOnBoxShadows) {
+ canvas.drawRRect(rrect.shift(shadow.offset), shadow.toPaint());
+ }
+ } else {
+ for (final BoxShadow shadow in _kSwitchOffBoxShadows) {
+ canvas.drawRRect(rrect.shift(shadow.offset), shadow.toPaint());
+ }
+ }
+
+ canvas.drawRRect(rrect, Paint()..color = color);
+ }
+}
diff --git a/lib/src/buttons/toolbar/toolbar_icon_button.dart b/lib/src/buttons/toolbar/toolbar_icon_button.dart
index 3d94a1f3..4b165a3d 100644
--- a/lib/src/buttons/toolbar/toolbar_icon_button.dart
+++ b/lib/src/buttons/toolbar/toolbar_icon_button.dart
@@ -77,7 +77,7 @@ class ToolBarIconButton extends ToolbarItem {
if (showLabel) {
iconButton = Padding(
- padding: const EdgeInsets.fromLTRB(6.0, 6.0, 6.0, 0.0),
+ padding: const EdgeInsets.only(left: 6.0, top: 6.0, right: 6.0),
child: Column(
children: [
iconButton,
diff --git a/lib/src/dialogs/macos_alert_dialog.dart b/lib/src/dialogs/macos_alert_dialog.dart
index 057f04df..0dab2588 100644
--- a/lib/src/dialogs/macos_alert_dialog.dart
+++ b/lib/src/dialogs/macos_alert_dialog.dart
@@ -3,6 +3,10 @@ import 'package:macos_ui/macos_ui.dart';
import 'package:macos_ui/src/library.dart';
const _kDialogBorderRadius = BorderRadius.all(Radius.circular(12.0));
+const _kDefaultDialogConstraints = BoxConstraints(
+ minWidth: 260,
+ maxWidth: 260,
+);
/// A macOS-style AlertDialog.
///
@@ -17,14 +21,12 @@ const _kDialogBorderRadius = BorderRadius.all(Radius.circular(12.0));
/// appIcon: FlutterLogo(
/// size: 56,
/// ),
-/// title: Text(
-/// 'Alert Dialog with Primary Action',
-/// ),
+/// title: Text('Alert Dialog with Primary Action'),
/// message: Text(
/// 'This is an alert dialog with a primary action and no secondary action',
/// ),
/// primaryButton: PushButton(
-/// buttonSize: ButtonSize.large,
+/// controlSize: ControlSize.large,
/// child: Text('Primary'),
/// onPressed: Navigator.of(context).pop,
/// ),
@@ -46,7 +48,7 @@ class MacosAlertDialog extends StatelessWidget {
/// This should be your application's icon.
///
- /// The size of this widget should be 56x56.
+ /// The size of this widget should be 64x64.
final Widget appIcon;
/// The title for the dialog.
@@ -61,13 +63,13 @@ class MacosAlertDialog extends StatelessWidget {
/// The primary action a user can take.
///
- /// Typically a [PushButton].
- final Widget primaryButton;
+ /// Must a [PushButton] with a [ControlSize] of `large`.
+ final PushButton primaryButton;
/// The secondary action a user can take.
///
- /// Typically a [PushButton].
- final Widget? secondaryButton;
+ /// Must a [PushButton] with a [ControlSize] of `large`.
+ final PushButton? secondaryButton;
/// Determines whether to lay out [primaryButton] and [secondaryButton]
/// horizontally or vertically.
@@ -116,6 +118,11 @@ class MacosAlertDialog extends StatelessWidget {
@override
Widget build(BuildContext context) {
assert(debugCheckHasMacosTheme(context));
+ assert(primaryButton.controlSize == ControlSize.large);
+ if (secondaryButton != null) {
+ assert(secondaryButton is PushButton);
+ assert(secondaryButton!.controlSize == ControlSize.large);
+ }
final brightness = MacosTheme.brightnessOf(context);
final outerBorderColor = brightness.resolve(
@@ -153,39 +160,35 @@ class MacosAlertDialog extends StatelessWidget {
borderRadius: _kDialogBorderRadius,
),
child: ConstrainedBox(
- constraints: const BoxConstraints(
- maxWidth: 260,
- ),
+ constraints: _kDefaultDialogConstraints,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
- const SizedBox(height: 28),
+ const SizedBox(height: 20),
ConstrainedBox(
constraints: const BoxConstraints(
- maxHeight: 56,
- maxWidth: 56,
+ maxHeight: 64,
+ maxWidth: 64,
),
child: appIcon,
),
- const SizedBox(height: 28),
+ const SizedBox(height: 16),
DefaultTextStyle(
style: MacosTheme.of(context).typography.headline,
textAlign: TextAlign.center,
child: title,
),
- const SizedBox(height: 16),
+ const SizedBox(height: 10),
DefaultTextStyle(
textAlign: TextAlign.center,
style: MacosTheme.of(context).typography.headline,
child: message,
),
- const SizedBox(height: 18),
+ const SizedBox(height: 16),
if (secondaryButton == null) ...[
Row(
children: [
- Expanded(
- child: primaryButton,
- ),
+ Expanded(child: primaryButton),
],
),
] else ...[
@@ -193,9 +196,7 @@ class MacosAlertDialog extends StatelessWidget {
Row(
children: [
if (secondaryButton != null) ...[
- Expanded(
- child: secondaryButton!,
- ),
+ Expanded(child: secondaryButton!),
const SizedBox(width: 8.0),
],
Expanded(
diff --git a/lib/src/enums/control_size.dart b/lib/src/enums/control_size.dart
new file mode 100644
index 00000000..835812bf
--- /dev/null
+++ b/lib/src/enums/control_size.dart
@@ -0,0 +1,25 @@
+/// The out-of-the-box sizes that certain "control" widgets can be.
+///
+///
+///
+/// Not all controls support all sizes. For example, a [PushButton] can be any
+/// size, but a [MacosSwitch] can be all but large. In cases where a control
+/// doesn't support a certain size, the control will automatically fall back to
+/// the nearest supported size.
+///
+/// Reference:
+/// * https://developer.apple.com/documentation/swiftui/controlsize
+/// * https://developer.apple.com/documentation/swiftui/view/controlsize(_:)
+enum ControlSize {
+ /// A control that is minimally sized.
+ mini,
+
+ /// A control that is proportionally smaller size for space-constrained views.
+ small,
+
+ /// A control that is the default size.
+ regular,
+
+ /// A control that is prominently sized.
+ large,
+}
diff --git a/lib/src/fields/text_field.dart b/lib/src/fields/text_field.dart
index 9387b029..8564301d 100644
--- a/lib/src/fields/text_field.dart
+++ b/lib/src/fields/text_field.dart
@@ -105,7 +105,7 @@ class _TextFieldSelectionGestureDetectorBuilder
final _MacosTextFieldState _state;
@override
- void onSingleTapUp(TapUpDetails details) {
+ void onSingleTapUp(TapDragUpDetails details) {
// Because TextSelectionGestureDetector listens to taps that happen on
// widgets in front of it, tapping the clear button will also trigger
// this handler. If the clear button widget recognizes the up event,
@@ -124,11 +124,14 @@ class _TextFieldSelectionGestureDetectorBuilder
}
_state._requestKeyboard();
if (_state.widget.onTap != null) _state.widget.onTap!();
+
+ super.onSingleTapUp(details);
}
@override
- void onDragSelectionEnd(DragEndDetails details) {
+ void onDragSelectionEnd(TapDragEndDetails details) {
_state._requestKeyboard();
+ super.onDragSelectionEnd(details);
}
}
@@ -358,7 +361,7 @@ class MacosTextField extends StatefulWidget {
this.focusNode,
this.decoration,
this.focusedDecoration,
- this.padding = const EdgeInsets.fromLTRB(2.0, 4.0, 2.0, 4.0),
+ this.padding = const EdgeInsets.symmetric(horizontal: 2.0, vertical: 4.0),
this.placeholder,
this.placeholderStyle = _kDefaultPlaceholderStyle,
this.prefix,
@@ -756,7 +759,7 @@ class MacosTextField extends StatefulWidget {
'clearButtonMode',
clearButtonMode,
));
- properties.add(EnumProperty(
+ properties.add(DiagnosticsProperty(
'keyboardType',
keyboardType,
defaultValue: TextInputType.text,
@@ -1157,6 +1160,7 @@ class _MacosTextFieldState extends State
void dispose() {
_focusNode?.dispose();
_controller?.dispose();
+ _effectiveFocusNode.removeListener(_handleFocusChanged);
super.dispose();
}
diff --git a/lib/src/indicators/progress_indicators.dart b/lib/src/indicators/progress_indicators.dart
index 9bc9196e..6fe47cd9 100644
--- a/lib/src/indicators/progress_indicators.dart
+++ b/lib/src/indicators/progress_indicators.dart
@@ -107,7 +107,7 @@ class _DeterminateCirclePainter extends CustomPainter {
final Color? borderColor;
static const double _twoPi = math.pi * 2.0;
- static const double _epsilon = .001;
+ static const double _epsilon = 0.001;
static const double _sweep = _twoPi - _epsilon;
static const double _startAngle = -math.pi / 2.0;
@@ -240,7 +240,7 @@ class _DeterminateBarPainter extends CustomPainter {
void paint(Canvas canvas, Size size) {
// Draw the background line
canvas.drawRRect(
- BorderRadius.circular(100).toRRect(
+ const BorderRadius.all(Radius.circular(100)).toRRect(
Offset.zero & size,
),
Paint()
diff --git a/lib/src/indicators/relevance_indicator.dart b/lib/src/indicators/relevance_indicator.dart
index fa4d6e19..5d8d0dd2 100644
--- a/lib/src/indicators/relevance_indicator.dart
+++ b/lib/src/indicators/relevance_indicator.dart
@@ -5,6 +5,7 @@ import 'package:macos_ui/src/library.dart';
/// A relevance indicator communicates relevancy using a series
/// of vertical bars. It often appears in a list of search results
/// for reference when sorting and comparing multiple items.
+@Deprecated('Apple no longer supports this component.')
class RelevanceIndicator extends StatelessWidget {
/// Creates a relevance indicator.
///
diff --git a/lib/src/indicators/slider.dart b/lib/src/indicators/slider.dart
index 8a172c2e..9e5e7514 100644
--- a/lib/src/indicators/slider.dart
+++ b/lib/src/indicators/slider.dart
@@ -178,8 +178,9 @@ class MacosSlider extends StatelessWidget {
backgroundColor,
context,
),
- borderRadius:
- BorderRadius.circular(_kSliderBorderRadius),
+ borderRadius: const BorderRadius.all(
+ Radius.circular(_kSliderBorderRadius),
+ ),
),
),
),
@@ -192,8 +193,9 @@ class MacosSlider extends StatelessWidget {
width: width * _percentage,
decoration: BoxDecoration(
color: MacosDynamicColor.resolve(color, context),
- borderRadius:
- BorderRadius.circular(_kSliderBorderRadius),
+ borderRadius: const BorderRadius.all(
+ Radius.circular(_kSliderBorderRadius),
+ ),
),
),
),
@@ -273,7 +275,8 @@ class _ContinuousThumb extends StatelessWidget {
width: _kContinuousThumbSize,
decoration: BoxDecoration(
color: color,
- borderRadius: BorderRadius.circular(_kContinuousThumbSize),
+ borderRadius:
+ const BorderRadius.all(Radius.circular(_kContinuousThumbSize)),
boxShadow: const [
BoxShadow(
color: Color.fromRGBO(0, 0, 0, 0.1),
@@ -300,7 +303,9 @@ class _DiscreteThumb extends StatelessWidget {
width: _kDiscreteThumbWidth,
decoration: BoxDecoration(
color: color,
- borderRadius: BorderRadius.circular(_kDiscreteThumbBorderRadius),
+ borderRadius: const BorderRadius.all(
+ Radius.circular(_kDiscreteThumbBorderRadius),
+ ),
boxShadow: const [
BoxShadow(
color: Color.fromRGBO(0, 0, 0, 0.1),
diff --git a/lib/src/layout/resizable_pane.dart b/lib/src/layout/resizable_pane.dart
index 68ccfbed..97836bdd 100644
--- a/lib/src/layout/resizable_pane.dart
+++ b/lib/src/layout/resizable_pane.dart
@@ -30,10 +30,15 @@ enum ResizableSide {
/// The [startSize] is the initial width or height depending on the orientation of the pane.
/// {@endtemplate}
class ResizablePane extends StatefulWidget {
- /// {@macro resizablePane}
+ /// Creates a [ResizablePane] with an internal [MacosScrollbar].
+ ///
+ /// Consider using [ResizablePane.noScrollBar] constructor when the internal
+ /// [MacosScrollbar] is not needed or when working with widgets which do not
+ /// expose their scroll controllers.
+ /// {@macro resizablePane}.
const ResizablePane({
super.key,
- required this.builder,
+ required ScrollableWidgetBuilder this.builder,
this.decoration,
this.maxSize = 500.0,
required this.minSize,
@@ -41,7 +46,38 @@ class ResizablePane extends StatefulWidget {
required this.resizableSide,
this.windowBreakpoint,
required this.startSize,
- }) : assert(
+ }) : child = null,
+ useScrollBar = true,
+ assert(
+ maxSize >= minSize,
+ 'minSize should not be more than maxSize.',
+ ),
+ assert(
+ (startSize >= minSize) && (startSize <= maxSize),
+ 'startSize must not be less than minSize or more than maxWidth',
+ );
+
+ /// Creates a [ResizablePane] without an internal [MacosScrollbar].
+ ///
+ /// Useful when working with widgets which do not expose their scroll
+ /// controllers or when not using the platform scroll bar is preferred.
+ ///
+ /// Consider using the default constructor if showing a [MacosScrollbar]
+ /// when scrolling the content of this widget is the expected behavior.
+ /// {@macro resizablePane}.
+ const ResizablePane.noScrollBar({
+ super.key,
+ required Widget this.child,
+ this.decoration,
+ this.maxSize = 500.0,
+ required this.minSize,
+ this.isResizable = true,
+ required this.resizableSide,
+ this.windowBreakpoint,
+ required this.startSize,
+ }) : builder = null,
+ useScrollBar = false,
+ assert(
maxSize >= minSize,
'minSize should not be more than maxSize.',
),
@@ -55,7 +91,15 @@ class ResizablePane extends StatefulWidget {
///
/// Pass the [scrollController] obtained from this method, to a scrollable
/// widget used in this method to work with the internal [MacosScrollbar].
- final ScrollableWidgetBuilder builder;
+ final ScrollableWidgetBuilder? builder;
+
+ /// The child to display in this widget.
+ ///
+ /// This is only referenced when the constructor used is [ResizablePane.noScrollbar].
+ final Widget? child;
+
+ /// Specify if this [ResizablePane] should have an internal [MacosScrollbar].
+ final bool useScrollBar;
/// The [BoxDecoration] to paint behind the child in the [builder].
final BoxDecoration? decoration;
@@ -238,10 +282,8 @@ class _ResizablePaneState extends State {
oldWidget.minSize != widget.minSize ||
oldWidget.maxSize != widget.maxSize ||
oldWidget.resizableSide != widget.resizableSide) {
- setState(() {
- if (widget.minSize > _size) _size = widget.minSize;
- if (widget.maxSize < _size) _size = widget.maxSize;
- });
+ if (widget.minSize > _size) _size = widget.minSize;
+ if (widget.maxSize < _size) _size = widget.maxSize;
}
}
@@ -279,10 +321,12 @@ class _ResizablePaneState extends State {
SafeArea(
left: false,
right: false,
- child: MacosScrollbar(
- controller: _scrollController,
- child: widget.builder(context, _scrollController),
- ),
+ child: widget.useScrollBar
+ ? MacosScrollbar(
+ controller: _scrollController,
+ child: widget.builder!(context, _scrollController),
+ )
+ : widget.child!,
),
if (widget.isResizable && !_resizeOnRight && !_resizeOnTop)
Positioned(
diff --git a/lib/src/layout/scaffold.dart b/lib/src/layout/scaffold.dart
index 712ab1a7..59678372 100644
--- a/lib/src/layout/scaffold.dart
+++ b/lib/src/layout/scaffold.dart
@@ -1,14 +1,9 @@
import 'dart:math' as math;
+import 'package:flutter/foundation.dart';
import 'package:flutter/rendering.dart';
-import 'package:macos_ui/src/layout/content_area.dart';
-import 'package:macos_ui/src/layout/resizable_pane.dart';
-import 'package:macos_ui/src/layout/sidebar/sidebar.dart';
-import 'package:macos_ui/src/layout/title_bar.dart';
-import 'package:macos_ui/src/layout/toolbar/toolbar.dart';
-import 'package:macos_ui/src/layout/window.dart';
+import 'package:macos_ui/macos_ui.dart';
import 'package:macos_ui/src/library.dart';
-import 'package:macos_ui/src/theme/macos_theme.dart';
/// A macOS page widget.
///
@@ -69,7 +64,7 @@ class _MacosScaffoldState extends State {
);
final MacosThemeData theme = MacosTheme.of(context);
- late Color backgroundColor = widget.backgroundColor ?? theme.canvasColor;
+ Color backgroundColor = widget.backgroundColor ?? theme.canvasColor;
return LayoutBuilder(
builder: (context, constraints) {
@@ -82,23 +77,42 @@ class _MacosScaffoldState extends State {
return Stack(
children: [
- // Background color
- Positioned.fill(
- child: ColoredBox(color: backgroundColor),
- ),
-
- // Content Area
- Positioned(
- top: 0,
- width: width,
- height: height,
- child: MediaQuery(
- data: mediaQuery.copyWith(
- padding: EdgeInsets.only(top: topPadding),
+ if (!kIsWeb) ...[
+ // Content Area
+ Positioned(
+ top: 0,
+ width: width,
+ height: height,
+ child: WallpaperTintedArea(
+ backgroundColor: backgroundColor,
+ insertRepaintBoundary: true,
+ child: MediaQuery(
+ data: mediaQuery.copyWith(
+ padding: EdgeInsets.only(top: topPadding),
+ ),
+ child: _ScaffoldBody(children: children),
+ ),
+ ),
+ ),
+ ] else ...[
+ // Background color
+ Positioned.fill(
+ child: ColoredBox(color: backgroundColor),
+ ),
+
+ // Content Area
+ Positioned(
+ top: 0,
+ width: width,
+ height: height,
+ child: MediaQuery(
+ data: mediaQuery.copyWith(
+ padding: EdgeInsets.only(top: topPadding),
+ ),
+ child: _ScaffoldBody(children: children),
),
- child: _ScaffoldBody(children: children),
),
- ),
+ ],
// Toolbar
if (widget.toolBar != null)
@@ -115,7 +129,7 @@ class _MacosScaffoldState extends State {
}
class _ScaffoldBody extends MultiChildRenderObjectWidget {
- _ScaffoldBody({
+ const _ScaffoldBody({
super.children,
});
diff --git a/lib/src/layout/scrollbar.dart b/lib/src/layout/scrollbar.dart
index 334aced5..1cfc819b 100644
--- a/lib/src/layout/scrollbar.dart
+++ b/lib/src/layout/scrollbar.dart
@@ -156,7 +156,7 @@ class _RawMacosScrollBarState extends RawScrollbarState<_RawMacosScrollBar> {
);
_trackColorTween = ColorTween(
begin: MacosColors.transparent,
- end: widget.effectiveThumbColor.withOpacity(.15),
+ end: widget.effectiveThumbColor.withOpacity(0.15),
).animate(_trackColorAnimationController);
_thumbThicknessAnimationController.addListener(() {
updateScrollbarPainter();
diff --git a/lib/src/layout/sidebar/sidebar_items.dart b/lib/src/layout/sidebar/sidebar_items.dart
index f5954951..65b3dadb 100644
--- a/lib/src/layout/sidebar/sidebar_items.dart
+++ b/lib/src/layout/sidebar/sidebar_items.dart
@@ -206,9 +206,7 @@ class _SidebarItem extends StatelessWidget {
/// Typically a [Navigator] call
final VoidCallback? onClick;
- void _handleActionTap() async {
- onClick?.call();
- }
+ void _handleActionTap() => onClick?.call();
Map> get _actionMap => >{
ActivateIntent: CallbackAction(
@@ -286,9 +284,8 @@ class _SidebarItem extends StatelessWidget {
padding: EdgeInsets.only(right: spacing),
child: MacosIconTheme.merge(
data: MacosIconThemeData(
- color: selected
- ? MacosColors.white
- : theme.primaryColor,
+ color:
+ selected ? MacosColors.white : theme.primaryColor,
size: itemSize.iconSize,
),
child: item.leading!,
diff --git a/lib/src/layout/toolbar/sliver_toolbar.dart b/lib/src/layout/toolbar/sliver_toolbar.dart
index 8ec1b087..a96d571b 100644
--- a/lib/src/layout/toolbar/sliver_toolbar.dart
+++ b/lib/src/layout/toolbar/sliver_toolbar.dart
@@ -40,6 +40,7 @@ class SliverToolBar extends StatefulWidget with Diagnosticable {
this.pinned = true,
this.floating = false,
this.toolbarOpacity = 0.9,
+ this.allowWallpaperTintingOverrides = true,
});
/// Specifies the height of this [ToolBar].
@@ -138,6 +139,30 @@ class SliverToolBar extends StatefulWidget with Diagnosticable {
/// Defaults to `0.9`.
final double toolbarOpacity;
+ /// Whether this [SliverToolBar] is allowed to perform wallpaper tinting
+ /// overrides.
+ ///
+ /// This property is supposed to be set to true when this [SliverToolBar] is
+ /// currently visible on the screen (that is, not e.g. hidden by an
+ /// [IndexedStack]).
+ ///
+ /// By default, macos_ui applies wallpaper tinting to the application's
+ /// window to match macOS' native appearance:
+ ///
+ ///
+ ///
+ /// However, this effect is realized by inserting `NSVisualEffectView`s behind
+ /// Flutter's canvas and turning the background of areas that are meant to be
+ /// affected by wallpaper tinting transparent. Since Flutter's
+ /// [`ImageFilter.blur`](https://api.flutter.dev/flutter/dart-ui/ImageFilter/ImageFilter.blur.html)
+ /// does not support transparency, wallpaper tinting is disabled automatically
+ /// when this widget's [allowWallpaperTintingOverrides] is true.
+ ///
+ /// This is meant to be a temporary solution until
+ /// [#16296](https://github.com/flutter/flutter/issues/16296) is resolved in
+ /// the Flutter project.
+ final bool allowWallpaperTintingOverrides;
+
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
@@ -204,6 +229,7 @@ class _SliverToolBarState extends State
floating: widget.floating,
pinned: widget.pinned,
toolbarOpacity: widget.toolbarOpacity,
+ allowWallpaperTintingOverrides: widget.allowWallpaperTintingOverrides,
vsync: this,
),
),
@@ -228,6 +254,7 @@ class _SliverToolBarDelegate extends SliverPersistentHeaderDelegate {
required this.floating,
required this.pinned,
required this.toolbarOpacity,
+ required this.allowWallpaperTintingOverrides,
});
final double height;
@@ -244,6 +271,7 @@ class _SliverToolBarDelegate extends SliverPersistentHeaderDelegate {
final bool floating;
final bool pinned;
final double toolbarOpacity;
+ final bool allowWallpaperTintingOverrides;
@override
double get minExtent => _kToolbarHeight;
@@ -275,7 +303,7 @@ class _SliverToolBarDelegate extends SliverPersistentHeaderDelegate {
);
}
- final Widget toolBar = FlexibleSpaceBar.createSettings(
+ return FlexibleSpaceBar.createSettings(
minExtent: minExtent,
maxExtent: maxExtent,
currentExtent: math.max(minExtent, maxExtent - shrinkOffset),
@@ -293,9 +321,10 @@ class _SliverToolBarDelegate extends SliverPersistentHeaderDelegate {
dividerColor: dividerColor,
alignment: alignment,
height: height,
+ enableBlur: true,
+ allowWallpaperTintingOverrides: allowWallpaperTintingOverrides,
),
);
- return toolBar;
}
@override
@@ -312,6 +341,8 @@ class _SliverToolBarDelegate extends SliverPersistentHeaderDelegate {
centerTitle != oldDelegate.centerTitle ||
dividerColor != oldDelegate.dividerColor ||
floating != oldDelegate.floating ||
- pinned != oldDelegate.pinned;
+ pinned != oldDelegate.pinned ||
+ allowWallpaperTintingOverrides !=
+ oldDelegate.allowWallpaperTintingOverrides;
}
}
diff --git a/lib/src/layout/toolbar/toolbar.dart b/lib/src/layout/toolbar/toolbar.dart
index a9685f6a..efd62e3c 100644
--- a/lib/src/layout/toolbar/toolbar.dart
+++ b/lib/src/layout/toolbar/toolbar.dart
@@ -3,6 +3,7 @@ import 'dart:ui';
import 'package:flutter/foundation.dart';
import 'package:macos_ui/macos_ui.dart';
import 'package:macos_ui/src/layout/toolbar/overflow_handler.dart';
+import 'package:macos_ui/src/layout/wallpaper_tinting_settings/wallpaper_tinting_override.dart';
import 'package:macos_ui/src/library.dart';
/// Defines the height of a regular-sized [ToolBar]
@@ -43,6 +44,8 @@ class ToolBar extends StatefulWidget with Diagnosticable {
this.actions,
this.centerTitle = false,
this.dividerColor,
+ this.allowWallpaperTintingOverrides = true,
+ this.enableBlur = false,
});
/// Specifies the height of this [ToolBar].
@@ -120,6 +123,35 @@ class ToolBar extends StatefulWidget with Diagnosticable {
/// Set this to `MacosColors.transparent` to remove.
final Color? dividerColor;
+ /// Whether this [ToolBar] is allowed to perform wallpaper tinting overrides.
+ ///
+ /// This property is supposed to be set to true when this [ToolBar] is
+ /// currently visible on the screen (that is, not e.g. hidden by an
+ /// [IndexedStack]).
+ ///
+ /// This parameter only needs to be supplied when [enableBlur] is true.
+ ///
+ /// By default, macos_ui applies wallpaper tinting to the application's
+ /// window to match macOS' native appearance:
+ ///
+ ///
+ ///
+ /// However, this effect is realized by inserting `NSVisualEffectView`s behind
+ /// Flutter's canvas and turning the background of areas that are meant to be
+ /// affected by wallpaper tinting transparent. Since Flutter's
+ /// [`ImageFilter.blur`](https://api.flutter.dev/flutter/dart-ui/ImageFilter/ImageFilter.blur.html)
+ /// does not support transparency, wallpaper tinting is disabled automatically
+ /// when this widget's [enableBlur] and [allowWallpaperTintingOverrides] is
+ /// true.
+ ///
+ /// This is meant to be a temporary solution until
+ /// [#16296](https://github.com/flutter/flutter/issues/16296) is resolved in
+ /// the Flutter project.
+ final bool allowWallpaperTintingOverrides;
+
+ /// Whether this [ToolBar] should have a blur backdrop filter applied to it.
+ final bool enableBlur;
+
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
@@ -191,13 +223,10 @@ class _ToolBarState extends State {
title = SizedBox(
width: widget.titleWidth,
child: DefaultTextStyle(
- style: MacosTheme.of(context).typography.headline.copyWith(
- fontSize: 15,
- fontWeight: FontWeight.w600,
- color: theme.brightness.isDark
- ? const Color(0xFFEAEAEA)
- : const Color(0xFF4D4D4D),
- ),
+ style: theme.typography.title3.copyWith(
+ fontSize: 15,
+ fontWeight: MacosFontWeight.w590,
+ ),
child: title,
),
);
@@ -233,57 +262,56 @@ class _ToolBarState extends State {
left: !kIsWeb && isMacOS ? 70 : 0,
),
),
- child: ClipRect(
- child: BackdropFilter(
- filter: widget.decoration?.color?.opacity == 1
- ? ImageFilter.blur()
- : ImageFilter.blur(sigmaX: 5.0, sigmaY: 5.0),
- child: Container(
- alignment: widget.alignment,
- padding: widget.padding,
- decoration: BoxDecoration(
- color: theme.canvasColor,
- border: Border(bottom: BorderSide(color: dividerColor)),
- ).copyWith(
- color: widget.decoration?.color,
- image: widget.decoration?.image,
- border: widget.decoration?.border,
- borderRadius: widget.decoration?.borderRadius,
- boxShadow: widget.decoration?.boxShadow,
- gradient: widget.decoration?.gradient,
- ),
- child: NavigationToolbar(
- middle: title,
- centerMiddle: widget.centerTitle,
- trailing: OverflowHandler(
- overflowBreakpoint: overflowBreakpoint,
- overflowWidget: ToolbarOverflowButton(
- isDense: doAllItemsShowLabel,
- overflowContentBuilder: (context) => ToolbarOverflowMenu(
- children: overflowedActions
- .map((action) => action.build(
- context,
- ToolbarItemDisplayMode.overflowed,
- ))
- .toList(),
- ),
+ child: _WallpaperTintedAreaOrBlurFilter(
+ enableWallpaperTintedArea: kIsWeb ? false : !widget.enableBlur,
+ isWidgetVisible: widget.allowWallpaperTintingOverrides,
+ backgroundColor: theme.canvasColor,
+ widgetOpacity: widget.decoration?.color?.opacity,
+ child: Container(
+ alignment: widget.alignment,
+ padding: widget.padding,
+ decoration: BoxDecoration(
+ border: Border(bottom: BorderSide(color: dividerColor)),
+ ).copyWith(
+ color: widget.decoration?.color,
+ image: widget.decoration?.image,
+ border: widget.decoration?.border,
+ borderRadius: widget.decoration?.borderRadius,
+ boxShadow: widget.decoration?.boxShadow,
+ gradient: widget.decoration?.gradient,
+ ),
+ child: NavigationToolbar(
+ middle: title,
+ centerMiddle: widget.centerTitle,
+ trailing: OverflowHandler(
+ overflowBreakpoint: overflowBreakpoint,
+ overflowWidget: ToolbarOverflowButton(
+ isDense: doAllItemsShowLabel,
+ overflowContentBuilder: (context) => ToolbarOverflowMenu(
+ children: overflowedActions
+ .map((action) => action.build(
+ context,
+ ToolbarItemDisplayMode.overflowed,
+ ))
+ .toList(),
),
- children: inToolbarActions
- .map((e) =>
- e.build(context, ToolbarItemDisplayMode.inToolbar))
- .toList(),
- overflowChangedCallback: (hiddenItems) {
- setState(() => overflowedActionsCount = hiddenItems.length);
- },
- ),
- middleSpacing: 8,
- leading: SafeArea(
- top: false,
- right: false,
- bottom: false,
- left: !(scope?.isSidebarShown ?? false),
- child: leading ?? const SizedBox.shrink(),
),
+ children: inToolbarActions
+ .map(
+ (e) => e.build(context, ToolbarItemDisplayMode.inToolbar),
+ )
+ .toList(),
+ overflowChangedCallback: (hiddenItems) {
+ setState(() => overflowedActionsCount = hiddenItems.length);
+ },
+ ),
+ middleSpacing: 8,
+ leading: SafeArea(
+ top: false,
+ right: false,
+ bottom: false,
+ left: !(scope?.isSidebarShown ?? false),
+ child: leading ?? const SizedBox.shrink(),
),
),
),
@@ -319,3 +347,50 @@ abstract class ToolbarItem with Diagnosticable {
/// for the given display mode (in toolbar or overflowed).
Widget build(BuildContext context, ToolbarItemDisplayMode displayMode);
}
+
+/// Wraps the widget in either a [WallpaperTintingOverride] or a blurry backdrop
+/// filter.
+class _WallpaperTintedAreaOrBlurFilter extends StatelessWidget {
+ const _WallpaperTintedAreaOrBlurFilter({
+ required this.child,
+ required this.enableWallpaperTintedArea,
+ required this.backgroundColor,
+ required this.widgetOpacity,
+ required this.isWidgetVisible,
+ });
+
+ final Widget child;
+ final bool enableWallpaperTintedArea;
+ final Color backgroundColor;
+ final double? widgetOpacity;
+ final bool isWidgetVisible;
+
+ @override
+ Widget build(BuildContext context) {
+ if (enableWallpaperTintedArea) {
+ return WallpaperTintedArea(
+ backgroundColor: backgroundColor,
+ insertRepaintBoundary: true,
+ child: child,
+ );
+ }
+
+ if (!isWidgetVisible) {
+ return child;
+ }
+
+ return WallpaperTintingOverride(
+ child: ClipRect(
+ child: BackdropFilter(
+ filter: widgetOpacity == 1.0
+ ? ImageFilter.blur()
+ : ImageFilter.blur(
+ sigmaX: 5.0,
+ sigmaY: 5.0,
+ ),
+ child: child,
+ ),
+ ),
+ );
+ }
+}
diff --git a/lib/src/layout/toolbar/toolbar_divider.dart b/lib/src/layout/toolbar/toolbar_divider.dart
index d20a3990..346a999c 100644
--- a/lib/src/layout/toolbar/toolbar_divider.dart
+++ b/lib/src/layout/toolbar/toolbar_divider.dart
@@ -23,15 +23,9 @@ class ToolBarDivider extends ToolbarItem {
const Color.fromRGBO(255, 255, 255, 0.25),
);
if (displayMode == ToolbarItemDisplayMode.inToolbar) {
- return Padding(
- padding: padding!,
- child: Container(color: color, width: 1, height: 28),
- );
+ return Container(color: color, width: 1, height: 28, padding: padding!);
} else {
- return Padding(
- padding: padding!,
- child: Container(color: color, height: 1),
- );
+ return Container(color: color, height: 1, padding: padding!);
}
}
}
diff --git a/lib/src/layout/toolbar/toolbar_popup.dart b/lib/src/layout/toolbar/toolbar_popup.dart
index 9d618128..87f1158b 100644
--- a/lib/src/layout/toolbar/toolbar_popup.dart
+++ b/lib/src/layout/toolbar/toolbar_popup.dart
@@ -203,7 +203,7 @@ class _ToolbarPopupMenuState extends State<_ToolbarPopupMenu> {
super.initState();
_fadeOpacity = CurvedAnimation(
parent: widget.route.animation!,
- curve: const Interval(0.0, 0.50),
+ curve: const Interval(0.0, 0.5),
reverseCurve: const Interval(0.75, 1.0),
);
}
@@ -335,7 +335,7 @@ class _ToolbarPopupRoute extends PopupRoute {
@override
Widget buildPage(context, animation, secondaryAnimation) {
return LayoutBuilder(builder: (context, constraints) {
- final page = _ToolbarPopupRoutePage(
+ return _ToolbarPopupRoutePage(
target: target,
placementOffset: placementOffset,
placement: placement,
@@ -349,7 +349,6 @@ class _ToolbarPopupRoute extends PopupRoute {
horizontalOffset: horizontalOffset,
position: position,
);
- return page;
});
}
diff --git a/lib/src/layout/wallpaper_tinted_area.dart b/lib/src/layout/wallpaper_tinted_area.dart
new file mode 100644
index 00000000..607ae808
--- /dev/null
+++ b/lib/src/layout/wallpaper_tinted_area.dart
@@ -0,0 +1,156 @@
+import 'package:flutter/cupertino.dart';
+import 'package:macos_ui/src/layout/wallpaper_tinting_settings/global_wallpaper_tinting_settings.dart';
+import 'package:macos_ui/src/layout/wallpaper_tinting_settings/wallpaper_tinting_settings_builder.dart';
+import 'package:macos_window_utils/macos/ns_visual_effect_view_material.dart';
+import 'package:macos_window_utils/widgets/visual_effect_subview_container/visual_effect_subview_container.dart';
+
+/// A widget that applies a wallpaper tint to its child widget.
+///
+/// This widget only works on macOS.
+///
+/// The [backgroundColor] is the color to apply to the background when wallpaper
+/// tinting is disabled. If [insertRepaintBoundary] is true, a [RepaintBoundary]
+/// is inserted above this widget in the widget tree. In some instances, it may
+/// be necessary to insert a [RepaintBoundary] to ensure proper rendering.
+/// The [child] is the widget below this widget in the tree.
+///
+/// Example:
+///
+/// ```dart
+/// WallpaperTintedArea(
+/// backgroundColor: MacosColors.white,
+/// child: Text('Hello World'),
+/// )
+/// ```
+class WallpaperTintedArea extends StatelessWidget {
+ /// Creates a [WallpaperTintedArea].
+ ///
+ /// Widgets wrapped in this widget will have a wallpaper tint applied to them.
+ ///
+ /// **Note:** This widget only works on macOS.
+ const WallpaperTintedArea({
+ super.key,
+ required this.backgroundColor,
+ this.insertRepaintBoundary = false,
+ this.child,
+ });
+
+ /// The color to apply to the background when wallpaper tinting is disabled.
+ final Color backgroundColor;
+
+ /// Whether to insert a [RepaintBoundary] above this widget in the widget
+ /// tree.
+ ///
+ /// In some instances, it may be necessary to insert a [RepaintBoundary] above
+ /// this widget into the widget tree to ensure that this widget is rendered
+ /// properly.
+ final bool insertRepaintBoundary;
+
+ /// The widget below this widget in the tree.
+ final Widget? child;
+
+ @override
+ Widget build(BuildContext context) {
+ if (insertRepaintBoundary) {
+ return RepaintBoundary(
+ child: _WallpaperTintedAreaLayoutBuilder(
+ backgroundColor: backgroundColor,
+ child: child,
+ ),
+ );
+ }
+
+ return _WallpaperTintedAreaLayoutBuilder(
+ backgroundColor: backgroundColor,
+ child: child,
+ );
+ }
+}
+
+class _WallpaperTintedAreaLayoutBuilder extends StatelessWidget {
+ const _WallpaperTintedAreaLayoutBuilder({
+ required this.backgroundColor,
+ required this.child,
+ });
+
+ /// The color to apply to the background when wallpaper tinting is disabled.
+ final Color backgroundColor;
+
+ /// The widget below this widget in the tree.
+ final Widget? child;
+
+ @override
+ Widget build(BuildContext context) {
+ if (GlobalWallpaperTintingSettings
+ .data.isWallpaperTintingDisabledByWindow) {
+ return Container(
+ decoration: BoxDecoration(
+ color: backgroundColor,
+ ),
+ child: child,
+ );
+ }
+
+ // This LayoutBuilder forces the widget to be rebuilt when a layout change
+ // is detected. This is necessary for the VisualEffectSubviewContainer to
+ // be updated.
+ return LayoutBuilder(
+ builder: (context, _) {
+ return VisualEffectSubviewContainer(
+ material: NSVisualEffectViewMaterial.windowBackground,
+ child: WallpaperTintingSettingsBuilder(
+ builder: (context, data) {
+ final isWallpaperTintingEnabled = data.isWallpaperTintingEnabled;
+
+ return _WallpaperTintedAreaTweenAnimationBuilder(
+ isWallpaperTintingEnabled: isWallpaperTintingEnabled,
+ backgroundColor: backgroundColor,
+ child: child,
+ );
+ },
+ ),
+ );
+ },
+ );
+ }
+}
+
+class _WallpaperTintedAreaTweenAnimationBuilder extends StatelessWidget {
+ const _WallpaperTintedAreaTweenAnimationBuilder({
+ required this.isWallpaperTintingEnabled,
+ required this.backgroundColor,
+ required this.child,
+ });
+
+ /// Whether wallpaper tinting is enabled.
+ final bool isWallpaperTintingEnabled;
+
+ /// The color to apply to the background when wallpaper tinting is disabled.
+ final Color backgroundColor;
+
+ /// The widget below this widget in the tree.
+ final Widget? child;
+
+ @override
+ Widget build(BuildContext context) {
+ return TweenAnimationBuilder(
+ duration: const Duration(milliseconds: 100),
+ tween: Tween(
+ begin: isWallpaperTintingEnabled ? 0.0 : 1.0,
+ end: isWallpaperTintingEnabled ? 0.0 : 1.0,
+ ),
+ builder: (context, value, child) {
+ return Container(
+ decoration: BoxDecoration(
+ color: backgroundColor.withOpacity(value),
+ backgroundBlendMode: BlendMode.src,
+ ),
+ child: child,
+ );
+ },
+ child: RepaintBoundary(
+ child: child,
+ ),
+ );
+ }
+}
diff --git a/lib/src/layout/wallpaper_tinting_settings/global_wallpaper_tinting_settings.dart b/lib/src/layout/wallpaper_tinting_settings/global_wallpaper_tinting_settings.dart
new file mode 100644
index 00000000..78ec46f1
--- /dev/null
+++ b/lib/src/layout/wallpaper_tinting_settings/global_wallpaper_tinting_settings.dart
@@ -0,0 +1,46 @@
+import 'dart:async';
+
+import 'package:macos_ui/src/layout/wallpaper_tinting_settings/wallpaper_tinting_settings_data.dart';
+
+/// A class that provides a global instance of [WallpaperTintingSettingsData].
+class GlobalWallpaperTintingSettings {
+ /// The [WallpaperTintingSettingsData] instance.
+ static final WallpaperTintingSettingsData data =
+ WallpaperTintingSettingsData();
+
+ /// The [StreamController] for an event stream that is triggered when [data]
+ /// changes.
+ static final _onDataChangedStreamController =
+ StreamController.broadcast();
+
+ /// A stream that can be used to listen to [data] changes.
+ static Stream get onDataChangedStream =>
+ _onDataChangedStreamController.stream;
+
+ /// Gets whether wallpaper tinting should be enabled.
+ static bool get isWallpaperTintingEnabled => data.isWallpaperTintingEnabled;
+
+ /// Increments the number of active overrides.
+ static void addWallpaperTintingOverride() {
+ data.addOverride();
+ _onDataChangedStreamController.add(data);
+ }
+
+ /// Decrements the number of active overrides.
+ static void removeWallpaperTintingOverride() {
+ data.removeOverride();
+ _onDataChangedStreamController.add(data);
+ }
+
+ /// Disables wallpaper tinting altogether.
+ static void disableWallpaperTinting() {
+ data.disableWallpaperTinting();
+ _onDataChangedStreamController.add(data);
+ }
+
+ /// Allows wallpaper tinting, unless overridden.
+ static void allowWallpaperTinting() {
+ data.allowWallpaperTinting();
+ _onDataChangedStreamController.add(data);
+ }
+}
diff --git a/lib/src/layout/wallpaper_tinting_settings/wallpaper_tinting_override.dart b/lib/src/layout/wallpaper_tinting_settings/wallpaper_tinting_override.dart
new file mode 100644
index 00000000..7cd05629
--- /dev/null
+++ b/lib/src/layout/wallpaper_tinting_settings/wallpaper_tinting_override.dart
@@ -0,0 +1,41 @@
+import 'package:flutter/widgets.dart';
+import 'package:macos_ui/src/layout/wallpaper_tinting_settings/global_wallpaper_tinting_settings.dart';
+
+class WallpaperTintingOverride extends StatefulWidget {
+ /// Creates a [WallpaperTintingOverride].
+ ///
+ /// Including this widget in the widget tree will disable wallpaper tinting
+ /// globally. It is intended to be used by [MacosOverlayFilter] to disable
+ /// wallpaper tinting when an overlay filter is active, since
+ /// [`ImageFilter.blur`](https://api.flutter.dev/flutter/dart-ui/ImageFilter/ImageFilter.blur.html)
+ /// does not support transparency.
+ const WallpaperTintingOverride({super.key, this.child});
+
+ /// The widget below this widget in the tree.
+ final Widget? child;
+
+ @override
+ State createState() =>
+ _WallpaperTintingOverrideState();
+}
+
+class _WallpaperTintingOverrideState extends State {
+ @override
+ void initState() {
+ super.initState();
+
+ GlobalWallpaperTintingSettings.addWallpaperTintingOverride();
+ }
+
+ @override
+ void dispose() {
+ GlobalWallpaperTintingSettings.removeWallpaperTintingOverride();
+
+ super.dispose();
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return widget.child ?? const SizedBox();
+ }
+}
diff --git a/lib/src/layout/wallpaper_tinting_settings/wallpaper_tinting_settings_builder.dart b/lib/src/layout/wallpaper_tinting_settings/wallpaper_tinting_settings_builder.dart
new file mode 100644
index 00000000..19fe2873
--- /dev/null
+++ b/lib/src/layout/wallpaper_tinting_settings/wallpaper_tinting_settings_builder.dart
@@ -0,0 +1,46 @@
+import 'package:flutter/widgets.dart';
+import 'package:macos_ui/src/layout/wallpaper_tinting_settings/global_wallpaper_tinting_settings.dart';
+
+import 'wallpaper_tinting_settings_data.dart';
+
+/// A widget that listens for changes to [WallpaperTintingSettingsData] and
+/// rebuilds with the latest data when a change is detected.
+///
+/// The [builder] callback is called whenever [WallpaperTintingSettingsData]
+/// changes. It should build a widget using the latest
+/// [WallpaperTintingSettingsData].
+///
+/// Example:
+///
+/// ```dart
+/// WallpaperTintingSettingsBuilder(
+/// builder: (context, data) {
+/// return Text(
+/// 'isWallpaperTintingEnabled: ${data.isWallpaperTintingEnabled}',
+/// );
+/// },
+/// )
+/// ```
+class WallpaperTintingSettingsBuilder extends StatelessWidget {
+ /// Creates a [WallpaperTintingSettingsBuilder].
+ ///
+ /// This widget can be used to listen to [WallpaperTintingSettingsData]
+ /// changes and rebuild if a change has been detected.
+ const WallpaperTintingSettingsBuilder({super.key, required this.builder});
+
+ /// Called when [WallpaperTintingSettingsData] changes.
+ final Widget Function(BuildContext, WallpaperTintingSettingsData) builder;
+
+ @override
+ Widget build(BuildContext context) {
+ return StreamBuilder(
+ stream: GlobalWallpaperTintingSettings.onDataChangedStream,
+ initialData: GlobalWallpaperTintingSettings.data,
+ builder: (context, snapshot) {
+ final data = snapshot.data ?? GlobalWallpaperTintingSettings.data;
+
+ return builder(context, data);
+ },
+ );
+ }
+}
diff --git a/lib/src/layout/wallpaper_tinting_settings/wallpaper_tinting_settings_data.dart b/lib/src/layout/wallpaper_tinting_settings/wallpaper_tinting_settings_data.dart
new file mode 100644
index 00000000..07c8aea4
--- /dev/null
+++ b/lib/src/layout/wallpaper_tinting_settings/wallpaper_tinting_settings_data.dart
@@ -0,0 +1,38 @@
+/// Holds data related to wallpaper tinting.
+class WallpaperTintingSettingsData {
+ /// The number of wallpaper tinting overrides that are currently active.
+ ///
+ /// A wallpaper tinting override causes wallpaper tinting to be disabled.
+ int _numberOfWallpaperTintingOverrides = 0;
+
+ /// Whether wallpaper tinting is disabled by the application's window.
+ bool _isWallpaperTintingDisabledByWindow = false;
+
+ /// Gets whether wallpaper tinting should be enabled.
+ bool get isWallpaperTintingEnabled =>
+ !_isWallpaperTintingDisabledByWindow &&
+ _numberOfWallpaperTintingOverrides == 0;
+
+ /// Gets whether wallpaper tinting is disabled by the application's window.
+ bool get isWallpaperTintingDisabledByWindow =>
+ _isWallpaperTintingDisabledByWindow;
+
+ /// Increments the number of active overrides.
+ void addOverride() => _numberOfWallpaperTintingOverrides += 1;
+
+ /// Decrements the number of active overrides.
+ void removeOverride() {
+ _numberOfWallpaperTintingOverrides -= 1;
+ assert(_numberOfWallpaperTintingOverrides >= 0);
+ }
+
+ /// Disables wallpaper tinting altogether.
+ void disableWallpaperTinting() {
+ _isWallpaperTintingDisabledByWindow = true;
+ }
+
+ /// Allows wallpaper tinting, unless overridden.
+ void allowWallpaperTinting() {
+ _isWallpaperTintingDisabledByWindow = false;
+ }
+}
diff --git a/lib/src/layout/window.dart b/lib/src/layout/window.dart
index 9ce38eb0..85c20417 100644
--- a/lib/src/layout/window.dart
+++ b/lib/src/layout/window.dart
@@ -2,14 +2,10 @@ import 'dart:math' as math;
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
-import 'package:macos_ui/src/layout/scrollbar.dart';
-import 'package:macos_ui/src/layout/content_area.dart';
-import 'package:macos_ui/src/layout/resizable_pane.dart';
-import 'package:macos_ui/src/layout/scaffold.dart';
-import 'package:macos_ui/src/layout/sidebar/sidebar.dart';
-import 'package:macos_ui/src/layout/title_bar.dart';
+import 'package:macos_ui/macos_ui.dart';
+import 'package:macos_ui/src/layout/wallpaper_tinting_settings/global_wallpaper_tinting_settings.dart';
import 'package:macos_ui/src/library.dart';
-import 'package:macos_ui/src/theme/macos_theme.dart';
+import 'package:macos_window_utils/widgets/transparent_macos_sidebar.dart';
/// A basic frame layout.
///
@@ -28,6 +24,8 @@ class MacosWindow extends StatefulWidget {
this.sidebar,
this.backgroundColor,
this.endSidebar,
+ this.disableWallpaperTinting = false,
+ this.sidebarState = NSVisualEffectViewState.followsWindowActiveState,
});
/// Specifies the background color for the Window.
@@ -47,13 +45,47 @@ class MacosWindow extends StatefulWidget {
/// A sidebar to display at the right of the window.
final Sidebar? endSidebar;
+ /// Whether wallpaper tinting should be disabled.
+ ///
+ /// By default, `macos_ui` applies wallpaper tinting to the application's
+ /// window to match macOS' native appearance:
+ ///
+ ///
+ ///
+ /// However, this effect is realized by inserting `NSVisualEffectView`s behind
+ /// Flutter's canvas and turning the background of areas that are meant to be
+ /// affected by wallpaper tinting transparent. Since Flutter's
+ /// [`ImageFilter.blur`](https://api.flutter.dev/flutter/dart-ui/ImageFilter/ImageFilter.blur.html)
+ /// does not support transparency, wallpaper tinting is disabled automatically
+ /// when a [MacosOverlayFilter] is present in the widget tree.
+ ///
+ /// This is meant to be a temporary solution until
+ /// [#16296](https://github.com/flutter/flutter/issues/16296) is resolved in
+ /// the Flutter project.
+ ///
+ /// Since the disabling of wallpaper tinting may be found to be too noticeable,
+ /// this property may be used to disable wallpaper tinting outright.
+ final bool disableWallpaperTinting;
+
+ /// The state of the sidebar's [NSVisualEffectView].
+ ///
+ /// Possible values are:
+ ///
+ /// - [NSVisualEffectViewState.active]: The sidebar is always active.
+ /// - [NSVisualEffectViewState.inactive]: The sidebar is always inactive.
+ /// - [NSVisualEffectViewState.followsWindowActiveState]: The sidebar's state
+ /// follows the window's active state.
+ ///
+ /// Defaults to [NSVisualEffectViewState.followsWindowActiveState].
+ final NSVisualEffectViewState sidebarState;
+
@override
State createState() => _MacosWindowState();
}
class _MacosWindowState extends State {
- final _sidebarScrollController = ScrollController();
- final _endSidebarScrollController = ScrollController();
+ var _sidebarScrollController = ScrollController();
+ var _endSidebarScrollController = ScrollController();
double _sidebarWidth = 0.0;
double _sidebarDragStartWidth = 0.0;
double _sidebarDragStartPosition = 0.0;
@@ -74,47 +106,71 @@ class _MacosWindowState extends State {
_endSidebarWidth =
(widget.endSidebar?.startWidth ?? widget.endSidebar?.minWidth) ??
_endSidebarWidth;
- if (widget.sidebar?.builder != null) {
- _sidebarScrollController.addListener(() => setState(() {}));
- }
- if (widget.endSidebar?.builder != null) {
- _endSidebarScrollController.addListener(() => setState(() {}));
- }
+
+ widget.disableWallpaperTinting
+ ? GlobalWallpaperTintingSettings.disableWallpaperTinting()
+ : GlobalWallpaperTintingSettings.allowWallpaperTinting();
+
+ _addSidebarScrollControllerListenerIfNeeded();
+ _addEndSidebarScrollControllerListenerIfNeeded();
}
@override
void didUpdateWidget(covariant MacosWindow old) {
super.didUpdateWidget(old);
- setState(() {
- if (widget.sidebar == null) {
- _sidebarWidth = 0.0;
- } else if (widget.sidebar!.minWidth != old.sidebar!.minWidth ||
- widget.sidebar!.maxWidth != old.sidebar!.maxWidth) {
- if (widget.sidebar!.minWidth > _sidebarWidth) {
- _sidebarWidth = widget.sidebar!.minWidth;
- }
- if (widget.sidebar!.maxWidth! < _sidebarWidth) {
- _sidebarWidth = widget.sidebar!.maxWidth!;
- }
+ final sidebar = widget.sidebar;
+ if (sidebar == null) {
+ _sidebarWidth = 0.0;
+ } else if (sidebar.minWidth != old.sidebar!.minWidth ||
+ sidebar.maxWidth != old.sidebar!.maxWidth) {
+ if (sidebar.minWidth > _sidebarWidth) {
+ _sidebarWidth = sidebar.minWidth;
+ }
+ if (sidebar.maxWidth! < _sidebarWidth) {
+ _sidebarWidth = sidebar.maxWidth!;
+ }
+ }
+ if (sidebar?.key != old.sidebar?.key) {
+ _sidebarScrollController.dispose();
+ _sidebarScrollController = ScrollController();
+ _addSidebarScrollControllerListenerIfNeeded();
+ }
+ final endSidebar = widget.endSidebar;
+ if (endSidebar == null) {
+ _endSidebarWidth = 0.0;
+ } else if (endSidebar.minWidth != old.endSidebar!.minWidth ||
+ endSidebar.maxWidth != old.endSidebar!.maxWidth) {
+ if (endSidebar.minWidth > _endSidebarWidth) {
+ _endSidebarWidth = endSidebar.minWidth;
}
- if (widget.endSidebar == null) {
- _endSidebarWidth = 0.0;
- } else if (widget.endSidebar!.minWidth != old.endSidebar!.minWidth ||
- widget.endSidebar!.maxWidth != old.endSidebar!.maxWidth) {
- if (widget.endSidebar!.minWidth > _endSidebarWidth) {
- _endSidebarWidth = widget.endSidebar!.minWidth;
- }
- if (widget.endSidebar!.maxWidth! < _endSidebarWidth) {
- _endSidebarWidth = widget.endSidebar!.maxWidth!;
- }
+ if (endSidebar.maxWidth! < _endSidebarWidth) {
+ _endSidebarWidth = endSidebar.maxWidth!;
}
- });
+ }
+ if (endSidebar?.key != old.endSidebar?.key) {
+ _endSidebarScrollController.dispose();
+ _endSidebarScrollController = ScrollController();
+ _addEndSidebarScrollControllerListenerIfNeeded();
+ }
+ }
+
+ void _addSidebarScrollControllerListenerIfNeeded() {
+ if (widget.sidebar?.builder != null) {
+ _sidebarScrollController.addListener(() => setState(() {}));
+ }
+ }
+
+ void _addEndSidebarScrollControllerListenerIfNeeded() {
+ if (widget.endSidebar?.builder != null) {
+ _endSidebarScrollController.addListener(() => setState(() {}));
+ }
}
@override
void dispose() {
_sidebarScrollController.dispose();
_endSidebarScrollController.dispose();
+
super.dispose();
}
@@ -122,13 +178,15 @@ class _MacosWindowState extends State {
// ignore: code-metrics
Widget build(BuildContext context) {
assert(debugCheckHasMacosTheme(context));
- if (widget.sidebar?.startWidth != null) {
- assert((widget.sidebar!.startWidth! >= widget.sidebar!.minWidth) &&
- (widget.sidebar!.startWidth! <= widget.sidebar!.maxWidth!));
+ final sidebar = widget.sidebar;
+ final endSidebar = widget.endSidebar;
+ if (sidebar?.startWidth != null) {
+ assert((sidebar!.startWidth! >= sidebar.minWidth) &&
+ (sidebar.startWidth! <= sidebar.maxWidth!));
}
- if (widget.endSidebar?.startWidth != null) {
- assert((widget.endSidebar!.startWidth! >= widget.endSidebar!.minWidth) &&
- (widget.endSidebar!.startWidth! <= widget.endSidebar!.maxWidth!));
+ if (endSidebar?.startWidth != null) {
+ assert((endSidebar!.startWidth! >= endSidebar.minWidth) &&
+ (endSidebar.startWidth! <= endSidebar.maxWidth!));
}
final MacosThemeData theme = MacosTheme.of(context);
late Color backgroundColor = widget.backgroundColor ?? theme.canvasColor;
@@ -139,28 +197,19 @@ class _MacosWindowState extends State {
final isMac = !kIsWeb && defaultTargetPlatform == TargetPlatform.macOS;
// Respect the sidebar color override from parent if one is given
- if (widget.sidebar?.decoration?.color != null) {
- sidebarBackgroundColor = widget.sidebar!.decoration!.color!;
- } else if (isMac &&
- MediaQuery.of(context).platformBrightness.isDark ==
- theme.brightness.isDark) {
- // Only show blurry, transparent sidebar when platform brightness and app
- // brightness are the same, otherwise it looks awful. Also only make the
- // sidebar transparent on native Mac, or it will just be flat black or
- // white.
- sidebarBackgroundColor = Colors.transparent;
+ if (sidebar?.decoration?.color != null) {
+ sidebarBackgroundColor = sidebar!.decoration!.color!;
} else {
- sidebarBackgroundColor = theme.brightness.isDark
- ? CupertinoColors.tertiarySystemBackground.darkColor
- : CupertinoColors.systemGrey6.color;
+ sidebarBackgroundColor = MacosColors.transparent;
}
+ // Set the application window's brightness on macOS
+ MacOSBrightnessOverrideHandler.ensureMatchingBrightness(theme.brightness);
+
// Respect the end sidebar color override from parent if one is given
- if (widget.endSidebar?.decoration?.color != null) {
- endSidebarBackgroundColor = widget.endSidebar!.decoration!.color!;
- } else if (isMac &&
- MediaQuery.of(context).platformBrightness.isDark ==
- theme.brightness.isDark) {
+ if (endSidebar?.decoration?.color != null) {
+ endSidebarBackgroundColor = endSidebar!.decoration!.color!;
+ } else if (isMac) {
endSidebarBackgroundColor = theme.canvasColor;
} else {
endSidebarBackgroundColor = theme.brightness.isDark
@@ -175,14 +224,16 @@ class _MacosWindowState extends State {
builder: (context, constraints) {
final width = constraints.maxWidth;
final height = constraints.maxHeight;
- final isAtBreakpoint = width <= (widget.sidebar?.windowBreakpoint ?? 0);
- final isAtEndBreakpoint =
- width <= (widget.endSidebar?.windowBreakpoint ?? 0);
- final canShowSidebar = _showSidebar && !isAtBreakpoint;
- final canShowEndSidebar = _showEndSidebar && !isAtEndBreakpoint;
+ final isAtBreakpoint = width <= (sidebar?.windowBreakpoint ?? 0);
+ final isAtEndBreakpoint = width <= (endSidebar?.windowBreakpoint ?? 0);
+ final canShowSidebar =
+ _showSidebar && !isAtBreakpoint && sidebar != null;
+ final canShowEndSidebar =
+ _showEndSidebar && !isAtEndBreakpoint && endSidebar != null;
final visibleSidebarWidth = canShowSidebar ? _sidebarWidth : 0.0;
final visibleEndSidebarWidth =
canShowEndSidebar ? _endSidebarWidth : 0.0;
+ final sidebarState = widget.sidebarState;
final layout = Stack(
children: [
@@ -197,8 +248,9 @@ class _MacosWindowState extends State {
),
// Sidebar
- if (widget.sidebar != null)
+ if (sidebar != null)
AnimatedPositioned(
+ key: sidebar.key,
curve: curve,
duration: duration,
height: height,
@@ -208,41 +260,99 @@ class _MacosWindowState extends State {
curve: Curves.easeInOut,
color: sidebarBackgroundColor,
constraints: BoxConstraints(
- minWidth: widget.sidebar!.minWidth,
- maxWidth: widget.sidebar!.maxWidth!,
+ minWidth: sidebar.minWidth,
+ maxWidth: sidebar.maxWidth!,
minHeight: height,
maxHeight: height,
).normalize(),
- child: Column(
- children: [
- if ((widget.sidebar?.topOffset ?? 0) > 0)
- SizedBox(height: widget.sidebar?.topOffset),
- if (_sidebarScrollController.hasClients &&
- _sidebarScrollController.offset > 0.0)
- Divider(thickness: 1, height: 1, color: dividerColor),
- if (widget.sidebar!.top != null &&
- constraints.maxHeight > 81)
- Padding(
- padding: const EdgeInsets.symmetric(horizontal: 8.0),
- child: widget.sidebar!.top!,
- ),
- Expanded(
- child: MacosScrollbar(
- controller: _sidebarScrollController,
- child: Padding(
- padding: widget.sidebar?.padding ?? EdgeInsets.zero,
- child: widget.sidebar!
- .builder(context, _sidebarScrollController),
+ child: kIsWeb ? ColoredBox(
+ color: theme.canvasColor,
+ child: Column(
+ children: [
+ // If an app is running on macOS, apply
+ // sidebar.topOffset as needed in order to avoid the
+ // traffic lights. Otherwise, position the sidebar
+ // by the top of the application's bounds based on
+ // the presence of sidebar.top.
+ if (!kIsWeb && sidebar.topOffset > 0) ...[
+ SizedBox(height: sidebar.topOffset),
+ ] else if (sidebar.top != null) ...[
+ const SizedBox(height: 12),
+ ] else
+ const SizedBox.shrink(),
+ if (_sidebarScrollController.hasClients &&
+ _sidebarScrollController.offset > 0.0)
+ Divider(thickness: 1, height: 1, color: dividerColor),
+ if (sidebar.top != null && constraints.maxHeight > 81)
+ Padding(
+ padding:
+ const EdgeInsets.symmetric(horizontal: 8.0),
+ child: sidebar.top!,
+ ),
+ Expanded(
+ child: MacosScrollbar(
+ controller: _sidebarScrollController,
+ child: Padding(
+ padding: sidebar.padding,
+ child: sidebar.builder(
+ context,
+ _sidebarScrollController,
+ ),
+ ),
),
),
- ),
- if (widget.sidebar?.bottom != null &&
- constraints.maxHeight > 141)
- Padding(
- padding: const EdgeInsets.all(16.0),
- child: widget.sidebar!.bottom!,
+ if (sidebar.bottom != null &&
+ constraints.maxHeight > 141)
+ Padding(
+ padding: const EdgeInsets.all(16.0),
+ child: sidebar.bottom!,
+ ),
+ ],
+ ),
+ ) : TransparentMacOSSidebar(
+ state: sidebarState,
+ child: Column(
+ children: [
+ // If an app is running on macOS, apply
+ // sidebar.topOffset as needed in order to avoid the
+ // traffic lights. Otherwise, position the sidebar
+ // by the top of the application's bounds based on
+ // the presence of sidebar.top.
+ if (!kIsWeb && sidebar.topOffset > 0) ...[
+ SizedBox(height: sidebar.topOffset),
+ ] else if (sidebar.top != null) ...[
+ const SizedBox(height: 12),
+ ] else
+ const SizedBox.shrink(),
+ if (_sidebarScrollController.hasClients &&
+ _sidebarScrollController.offset > 0.0)
+ Divider(thickness: 1, height: 1, color: dividerColor),
+ if (sidebar.top != null && constraints.maxHeight > 81)
+ Padding(
+ padding:
+ const EdgeInsets.symmetric(horizontal: 8.0),
+ child: sidebar.top!,
+ ),
+ Expanded(
+ child: MacosScrollbar(
+ controller: _sidebarScrollController,
+ child: Padding(
+ padding: sidebar.padding,
+ child: sidebar.builder(
+ context,
+ _sidebarScrollController,
+ ),
+ ),
+ ),
),
- ],
+ if (sidebar.bottom != null &&
+ constraints.maxHeight > 141)
+ Padding(
+ padding: const EdgeInsets.all(16.0),
+ child: sidebar.bottom!,
+ ),
+ ],
+ ),
),
),
),
@@ -275,7 +385,7 @@ class _MacosWindowState extends State {
),
// Sidebar resizer
- if (widget.sidebar?.isResizable ?? false)
+ if (sidebar?.isResizable ?? false)
AnimatedPositioned(
curve: curve,
duration: duration,
@@ -289,13 +399,12 @@ class _MacosWindowState extends State {
_sidebarDragStartPosition = details.globalPosition.dx;
},
onHorizontalDragUpdate: (details) {
- final sidebar = widget.sidebar!;
setState(() {
var newWidth = _sidebarDragStartWidth +
details.globalPosition.dx -
_sidebarDragStartPosition;
- if (sidebar.startWidth != null &&
+ if (sidebar!.startWidth != null &&
sidebar.snapToStartBuffer != null &&
(newWidth - sidebar.startWidth!).abs() <=
sidebar.snapToStartBuffer!) {
@@ -340,8 +449,9 @@ class _MacosWindowState extends State {
),
// End sidebar
- if (widget.endSidebar != null)
+ if (endSidebar != null)
AnimatedPositioned(
+ key: endSidebar.key,
left: width - visibleEndSidebarWidth,
curve: curve,
duration: duration,
@@ -352,46 +462,56 @@ class _MacosWindowState extends State {
curve: Curves.easeInOut,
color: endSidebarBackgroundColor,
constraints: BoxConstraints(
- minWidth: widget.endSidebar!.minWidth,
- maxWidth: widget.endSidebar!.maxWidth!,
+ minWidth: endSidebar.minWidth,
+ maxWidth: endSidebar.maxWidth!,
minHeight: height,
maxHeight: height,
).normalize(),
- child: Column(
- children: [
- if ((widget.endSidebar?.topOffset ?? 0) > 0)
- SizedBox(height: widget.endSidebar?.topOffset),
- if (_endSidebarScrollController.hasClients &&
- _endSidebarScrollController.offset > 0.0)
- Divider(thickness: 1, height: 1, color: dividerColor),
- if (widget.endSidebar!.top != null)
- Padding(
- padding: const EdgeInsets.symmetric(horizontal: 8.0),
- child: widget.endSidebar!.top!,
- ),
- Expanded(
- child: MacosScrollbar(
- controller: _endSidebarScrollController,
- child: Padding(
+ child: WallpaperTintedArea(
+ backgroundColor: endSidebarBackgroundColor,
+ insertRepaintBoundary: true,
+ child: Column(
+ children: [
+ if (endSidebar.topOffset > 0)
+ SizedBox(height: endSidebar.topOffset),
+ if (_endSidebarScrollController.hasClients &&
+ _endSidebarScrollController.offset > 0.0)
+ Divider(
+ thickness: 1,
+ height: 1,
+ color: dividerColor,
+ ),
+ if (endSidebar.top != null)
+ Padding(
padding:
- widget.endSidebar?.padding ?? EdgeInsets.zero,
- child: widget.endSidebar!
- .builder(context, _endSidebarScrollController),
+ const EdgeInsets.symmetric(horizontal: 8.0),
+ child: endSidebar.top!,
+ ),
+ Expanded(
+ child: MacosScrollbar(
+ controller: _endSidebarScrollController,
+ child: Padding(
+ padding: endSidebar.padding,
+ child: endSidebar.builder(
+ context,
+ _endSidebarScrollController,
+ ),
+ ),
),
),
- ),
- if (widget.endSidebar?.bottom != null)
- Padding(
- padding: const EdgeInsets.all(16.0),
- child: widget.endSidebar!.bottom!,
- ),
- ],
+ if (endSidebar.bottom != null)
+ Padding(
+ padding: const EdgeInsets.all(16.0),
+ child: endSidebar.bottom!,
+ ),
+ ],
+ ),
),
),
),
// End sidebar resizer
- if (widget.endSidebar?.isResizable ?? false)
+ if (endSidebar?.isResizable ?? false)
AnimatedPositioned(
curve: curve,
duration: duration,
@@ -405,13 +525,12 @@ class _MacosWindowState extends State {
_endSidebarDragStartPosition = details.globalPosition.dx;
},
onHorizontalDragUpdate: (details) {
- final endSidebar = widget.endSidebar!;
setState(() {
var newWidth = _endSidebarDragStartWidth -
details.globalPosition.dx +
_endSidebarDragStartPosition;
- if (endSidebar.startWidth != null &&
+ if (endSidebar!.startWidth != null &&
endSidebar.snapToStartBuffer != null &&
(newWidth + endSidebar.startWidth!).abs() <=
endSidebar.snapToStartBuffer!) {
@@ -465,13 +584,17 @@ class _MacosWindowState extends State {
setState(() => _sidebarSlideDuration = 300);
setState(() => _showSidebar = !_showSidebar);
await Future.delayed(Duration(milliseconds: _sidebarSlideDuration));
- setState(() => _sidebarSlideDuration = 0);
+ if (mounted) {
+ setState(() => _sidebarSlideDuration = 0);
+ }
},
endSidebarToggler: () async {
setState(() => _sidebarSlideDuration = 300);
setState(() => _showEndSidebar = !_showEndSidebar);
await Future.delayed(Duration(milliseconds: _sidebarSlideDuration));
- setState(() => _sidebarSlideDuration = 0);
+ if (mounted) {
+ setState(() => _sidebarSlideDuration = 0);
+ }
},
child: layout,
);
diff --git a/lib/src/macos_app.dart b/lib/src/macos_app.dart
index c4068f30..459918fb 100644
--- a/lib/src/macos_app.dart
+++ b/lib/src/macos_app.dart
@@ -74,15 +74,17 @@ class MacosApp extends StatefulWidget {
}) : routeInformationProvider = null,
routeInformationParser = null,
routerDelegate = null,
- backButtonDispatcher = null;
+ backButtonDispatcher = null,
+ routerConfig = null;
/// Creates a [MacosApp] that uses the [Router] instead of a [Navigator].
MacosApp.router({
super.key,
this.routeInformationProvider,
- required RouteInformationParser