macos_ui
Flutter widgets and themes implementing the current macOS design language.
macOS welcomes contributions. Please see CONTRIBUTING.md for more information.
Layout
MacosWindow is the basic frame for the macOS layout.
It has a Sidebar on the left and the rest of the window is typically filled out with a MacosScaffold. A scope for the MacosWindow is provided by MacosWindowScope. The sidebar can be toggled with MacosWindowScope.of(context).toggleSidebar().
The MacosScaffold is what you would call a "page".
The scaffold has a TitleBar property and the children property which accepts a ContentArea widget and multiple ResizablePane widgets. To catch navigation or routes below the scaffold, consider wrapping the MacosScaffold in a CupertinoTabView. By doing so, navigation inside the MacosScaffold will be displayed inside the MacosScaffold area instead of covering the entire window. To push a route outside a MacosScaffold wrapped in a CupertinoTabView, use the root navigator Navigator.of(context, rootNavigator: true)
See the documentation for customizations.
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.
import Cocoa
import FlutterMacOS
class MainFlutterWindow: NSWindow {
override func awakeFromNib() {
let flutterViewController = FlutterViewController.init()
let windowFrame = self.frame
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, *) {
self.toolbarStyle = .unified
}
self.isMovableByWindowBackground = true
self.styleMask.insert(NSWindow.StyleMask.fullSizeContentView)
RegisterGeneratedPlugins(registry: flutterViewController)
super.awakeFromNib()
}
}
Buttons
A checkbox is a type of button that lets the user choose between two opposite states, actions, or values. A selected 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
| Off | On | Mixed | | --------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------ | | | | |
Here's an example of how to create a basic checkbox:
bool selected = false;
MacosCheckbox(
value: selected,
onChanged: (value) {
setState(() => selected = value);
},
)
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
Here's an example of how to create a help button:
HelpButton(
onPressed: () {
print('pressed help button'),
},
)
You can customize the help button appearance and behaviour using the HelpButtonTheme, but it's not recommended by apple to change help button's appearance.
A radio button is a small, circular button followed by a title. Typically presented in groups of two to five, radio 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
Here's an example of how to create a basic radio button:
bool selected = false;
MacosRadioButton(
value: selected,
onChanged: (value) {
setState(() => selected = value);
},
),
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 complete a task. Learn more
| Dark Theme | Light Theme | | ------------------------------------------ | ------------------------------------------ | | | | | | | | | | | | |
Here's an example of how to create a basic push button:
PushButton(
child: Text('button'),
buttonSize: ButtonSize.large,
onPressed: () {
print('button pressed');
},
),
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
| On | Off | | ------------------------------------------ | ------------------------------------------ | | | |
Here's an example of how to create a basic toggle switch:
bool selected = false;
MacosSwitch(
value: selected,
onChanged: (value) {
setState(() => selected = value);
},
),
Dialogs
Usage:
showDialog(
context: context,
builder: (_) => MacosAlertDialog(
appIcon: FlutterLogo(
size: 56,
),
title: Text(
'Alert Dialog with Primary Action',
style: MacosTheme.of(context).typography.headline,
),
message: Text(
'This is an alert dialog with a primary action and no secondary action',
textAlign: TextAlign.center,
style: MacosTheme.of(context).typography.headline,
),
primaryButton: PushButton(
buttonSize: ButtonSize.large,
child: Text('Primary'),
onPressed: () {},
),
),
);
Fields
A text field is a rectangular area in which the user enters or edits one or more lines of text. A text field can contain plain or styled text.
Here's an example of how to create a basic text field:
MacosTextField(),
Labels
Labels are a short description of what an element on the screen does.
Tooltips succinctly describe how to use controls without shifting people’s focus away from the primary interface. Help tags appear when the user positions the pointer over a control for a few seconds. A tooltip remains visible for 10 seconds, or until the pointer moves away from the control.
To create a tooltip, wrap any widget on a Tooltip:
MacosTooltip(
message: 'This is a tooltip',
child: Text('Hover or long press to show a tooltip'),
),
You can customize the tooltip the way you want using its style property. A tooltip automatically adapts to its environment, responding to touch and pointer events.
Indicators
Don’t make people sit around staring at a static screen waiting for your app to load content or perform lengthy data processing operations. Use progress indicators to let people know your app hasn't stalled and to give them some idea of how long they’ll be waiting.
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
A ProgressCircle can be either determinate or indeterminate.
| Determinate Progress Circle | Indeterminate Progress Circle | | ------------------------------------------ | ------------------------------------------ | | | |
Here's an example of how to create an indeterminate progress circle:
ProgressCircle(
value: null,
),
You can provide a non-null value to value to make the progress circle determinate.
A ProgressBar can only be determinate.
Here's an example of how to create a determinate progress bar:
ProgressBar(
value: 30,
)
A level indicator graphically represents of a specific value within a range of numeric values. It’s similar to a slider in purpose, but more visual and doesn’t contain a distinct control for selecting a value—clicking and dragging across the level indicator itself to select a value is supported, however. A level indicator can also include tick marks, making it easy for the user to pinpoint a specific value in the range. There are three different level indicator styles, each with a different appearance, for communicating capacity, rating, and relevance.
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
| Continuous | Discrete | | ---------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | | | | 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:
double value = 30;
CapacityIndicator(
value: value,
discrete: false,
onChanged: (v) {
setState(() => value = v);
},
),
You can set discrete to true to make it a discrete capacity indicator.
A rating indicator uses a series of horizontally arranged graphical symbols to communicate a ranking level. The default symbol is a star.
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. Learn more
Here's an example of how to create an interactive rating indicator:
double value = 3;
RatingIndicator(
amount: 5,
value: value,
onChanged: (v) {
setState(() => value = v);
}
)
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
Here's an example of how to create a relevance indicator:
RelevanceIndicator(
value: 15,
amount: 20,
)
Run this command:
With Flutter:
$ flutter pub add macos_ui
This will add a line like this to your package's pubspec.yaml (and run an implicit dart pub get):
dependencies:
macos_ui: ^0.7.0
Alternatively, your editor might support flutter pub get. Check the docs for your editor to learn more.
Now in your Dart code, you can use:
import 'package:macos_ui/macos_ui.dart';
import 'package:example/pages/buttons.dart';
import 'package:example/pages/colors_page.dart';
import 'package:example/pages/dialogs_page.dart';
import 'package:example/pages/fields.dart';
import 'package:example/pages/indicators.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:macos_ui/macos_ui.dart';
import 'package:macos_ui/src/library.dart';
import 'package:provider/provider.dart';
import 'theme.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (_) => AppTheme(),
builder: (context, _) {
final appTheme = context.watch<AppTheme>();
return MacosApp(
title: 'macos_ui example',
theme: MacosThemeData.light(),
darkTheme: MacosThemeData.dark(),
themeMode: appTheme.mode,
debugShowCheckedModeBanner: false,
home: Demo(),
);
},
);
}
}
class Demo extends StatefulWidget {
@override
_DemoState createState() => _DemoState();
}
class _DemoState extends State<Demo> {
double ratingValue = 0;
double sliderValue = 0;
bool value = false;
int pageIndex = 0;
final List<Widget> pages = [
CupertinoTabView(
builder: (_) => ButtonsPage(),
),
IndicatorsPage(),
FieldsPage(),
ColorsPage(),
Center(child: Text('Disclosure item 2')),
Center(child: Text('Disclosure item 3')),
DialogsPage(),
];
Color textLuminance(Color backgroundColor) {
return backgroundColor.computeLuminance() > 0.5
? Colors.black
: Colors.white;
}
@override
Widget build(BuildContext context) {
return MacosWindow(
child: IndexedStack(
index: pageIndex,
children: pages,
),
sidebar: Sidebar(
minWidth: 200,
bottom: Padding(
padding: const EdgeInsets.all(16.0),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Icon(CupertinoIcons.profile_circled),
const SizedBox(width: 8.0),
Text('Tim Apple'),
],
),
),
builder: (context, controller) {
return SidebarItems(
currentIndex: pageIndex,
onChanged: (i) => setState(() => pageIndex = i),
scrollController: controller,
items: [
SidebarItem(
leading: Icon(CupertinoIcons.square_on_circle),
label: Text('Buttons'),
),
SidebarItem(
leading: Icon(CupertinoIcons.arrow_2_circlepath),
label: Text('Indicators'),
),
SidebarItem(
leading: Icon(CupertinoIcons.textbox),
label: Text('Fields'),
),
SidebarItem(
label: Text('Disclosure'),
disclosureItems: [
SidebarItem(
leading: Icon(CupertinoIcons.infinite),
label: Text('Colors'),
),
SidebarItem(
leading: Icon(CupertinoIcons.heart),
label: Text('Item 2'),
),
SidebarItem(
leading: Icon(CupertinoIcons.infinite),
label: Text('Item 3'),
),
],
),
SidebarItem(
leading: Icon(CupertinoIcons.rectangle),
label: Text('Dialogs'),
),
],
);
},
),
);
}
}
Author: GroovinChip
Official Website: https://github.com/GroovinChip/macos_ui