Skip to content

Commit 0caabd9

Browse files
Merge pull request #56 from macosui/issue_53
Version 1.7.0
2 parents 7ff98b1 + 2b4d002 commit 0caabd9

File tree

20 files changed

+1114
-68
lines changed

20 files changed

+1114
-68
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
## 1.7.0
2+
- Add `MacosToolbarPassthrough` to allow for the creation of so-called “passthrough views” on the toolbar that relay mouse events to the Flutter application (thanks to [@Andre-lbc](https://github.com/Andre-lbc) and [@adiletcool](https://github.com/adiletcool)).
3+
14
## 1.6.1
25

36
- Add instructions on how to setup macos_window_utils for macOS Monterey and earlier versions.

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,10 +38,11 @@ English | [简体中文](README_zh.md)
3838
+ An `NSAppPresentationOptions` class that allows modifications to the window's fullscreen presentation options.
3939
+ Methods to get and set the positions of the window’s standard window buttons (such as the close, miniaturize, and zoom buttons).
4040
+ Methods to control whether the window should be closable by the user, as well as methods to close the window programmatically.
41+
+ Widgets to enable passthrough views in the toolbar that pass mouse events (such as clicking or dragging) to the Flutter application.
4142

4243
Additionally, the package ships with an example project that showcases the plugin's features via an intuitive searchable user interface:
4344

44-
<img width="857" alt="screenshot of example project" src="https://user-images.githubusercontent.com/86920182/209587744-b21f2cd1-07a4-43ee-99c8-7cce1d89482d.png">
45+
<img width="855" alt="Screenshot of Example Project" src="https://github.com/user-attachments/assets/32b58d27-6285-45a2-b79d-39bf49013a8f" />
4546

4647
## Getting started
4748

example/.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,11 @@
55
*.swp
66
.DS_Store
77
.atom/
8+
.build/
89
.buildlog/
910
.history
1011
.svn/
12+
.swiftpm/
1113
migrate_working_dir/
1214

1315
# IntelliJ related
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import 'package:appkit_ui_element_colors/appkit_ui_element_colors.dart';
2+
import 'package:flutter/material.dart';
3+
import 'package:gradient_borders/gradient_borders.dart';
4+
5+
class MacosWindowUtilsButton extends StatefulWidget {
6+
const MacosWindowUtilsButton(
7+
{super.key, this.onPressed, required this.child});
8+
9+
final VoidCallback? onPressed;
10+
final Widget child;
11+
12+
@override
13+
State<MacosWindowUtilsButton> createState() => _MacosWindowUtilsButtonState();
14+
}
15+
16+
class _MacosWindowUtilsButtonState extends State<MacosWindowUtilsButton> {
17+
bool _isPressed = false;
18+
19+
@override
20+
Widget build(BuildContext context) {
21+
return GestureDetector(
22+
onTap: widget.onPressed,
23+
onTapDown: (_) => setState(() => _isPressed = true),
24+
onTapCancel: () => setState(() => _isPressed = false),
25+
onTapUp: (_) => setState(() => _isPressed = false),
26+
child: UiElementColorBuilder(
27+
uiElementColorContainerInstanceProvider:
28+
OwnedUiElementColorContainerInstanceProvider(),
29+
builder: (context, colorContainer) {
30+
return Opacity(
31+
opacity: _isPressed ? 0.9 : 1.0,
32+
child: Container(
33+
padding:
34+
const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0),
35+
decoration: BoxDecoration(
36+
gradient: LinearGradient(
37+
begin: Alignment.topCenter,
38+
end: Alignment.bottomCenter,
39+
colors: [
40+
colorContainer.controlAccentColor,
41+
Color.fromRGBO(
42+
(colorContainer.controlAccentColor.r * 200).floor(),
43+
(colorContainer.controlAccentColor.g * 200).floor(),
44+
(colorContainer.controlAccentColor.b * 200).floor(),
45+
1.0,
46+
),
47+
],
48+
),
49+
borderRadius: BorderRadius.circular(8.0),
50+
border: const GradientBoxBorder(
51+
gradient: LinearGradient(
52+
begin: Alignment.topCenter,
53+
end: Alignment.bottomCenter,
54+
colors: [
55+
Color.fromRGBO(255, 255, 255, 0.5),
56+
Color.fromRGBO(255, 255, 255, 0.0),
57+
Color.fromRGBO(255, 255, 255, 0.0),
58+
Color.fromRGBO(255, 255, 255, 0.0),
59+
],
60+
),
61+
width: 1.7,
62+
),
63+
boxShadow: const [
64+
BoxShadow(
65+
color: Color.fromRGBO(0, 0, 0, 0.4),
66+
blurRadius: 1,
67+
offset: Offset(0, 1),
68+
),
69+
],
70+
),
71+
child: DefaultTextStyle(
72+
style: const TextStyle(color: Colors.white),
73+
child: widget.child,
74+
),
75+
),
76+
);
77+
}),
78+
);
79+
}
80+
}

example/lib/global_state.dart

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import 'dart:async';
2+
3+
class GlobalState {
4+
static final GlobalState instance = GlobalState();
5+
6+
final StreamController<GlobalState> _streamController =
7+
StreamController<GlobalState>.broadcast();
8+
9+
Stream<GlobalState> get stream => _streamController.stream;
10+
11+
bool _isTabExampleEnabled = false;
12+
13+
/// Whether the tab example is enabled and displayed in the toolbar.
14+
bool get isTabExampleEnabled => _isTabExampleEnabled;
15+
16+
set isTabExampleEnabled(bool value) {
17+
_isTabExampleEnabled = value;
18+
_streamController.add(this);
19+
}
20+
21+
bool _enableDebugLayersForToolbarPassthroughViews = false;
22+
23+
/// Whether to enable debug layers for the toolbar passthrough views.
24+
bool get enableDebugLayersForToolbarPassthroughViews =>
25+
_enableDebugLayersForToolbarPassthroughViews;
26+
27+
set enableDebugLayersForToolbarPassthroughViews(bool value) {
28+
_enableDebugLayersForToolbarPassthroughViews = value;
29+
_streamController.add(this);
30+
}
31+
}

example/lib/main.dart

Lines changed: 48 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import 'package:appkit_ui_element_colors/appkit_ui_element_colors.dart';
2+
import 'package:example/global_state.dart';
23
import 'package:example/main_area/main_area.dart';
34
import 'package:example/sidebar_content.dart';
5+
import 'package:example/toolbar_passthrough_demo/tab_example.dart';
46
import 'package:example/util/transparent_sidebar_and_content.dart';
57
import 'package:flutter/cupertino.dart';
68
import 'package:macos_window_utils/macos_window_utils.dart';
@@ -85,44 +87,54 @@ class _MyHomePageState extends State<MyHomePage> {
8587

8688
@override
8789
Widget build(BuildContext context) {
88-
return TransparentSidebarAndContent(
89-
isOpen: _isSidebarOpen,
90-
width: 280.0,
91-
sidebarBuilder: () => const TitlebarSafeArea(
92-
child: SidebarContent(),
93-
),
94-
child: TitlebarSafeArea(
95-
child: CupertinoPageScaffold(
96-
navigationBar: CupertinoNavigationBar(
97-
middle: const Text('macos_window_utils demo'),
98-
leading: CupertinoButton(
99-
padding: EdgeInsets.zero,
100-
child: UiElementColorBuilder(
101-
uiElementColorContainerInstanceProvider:
102-
OwnedUiElementColorContainerInstanceProvider(),
103-
builder: (context, colorContainer) {
104-
return Icon(
105-
CupertinoIcons.sidebar_left,
106-
color: colorContainer.controlAccentColor,
107-
);
108-
}),
109-
onPressed: () => setState(
110-
() {
111-
_isSidebarOpen = !_isSidebarOpen;
112-
},
113-
),
90+
return StreamBuilder<Object>(
91+
stream: GlobalState.instance.stream,
92+
builder: (context, snapshot) {
93+
return TransparentSidebarAndContent(
94+
isOpen: _isSidebarOpen,
95+
width: 280.0,
96+
sidebarBuilder: () => const TitlebarSafeArea(
97+
child: SidebarContent(),
11498
),
115-
),
116-
child: DefaultTextStyle(
117-
style: CupertinoTheme.of(context).textTheme.textStyle,
118-
child: SafeArea(
119-
child: MainArea(
120-
setState: setState,
99+
child: TitlebarSafeArea(
100+
isEnabled: !GlobalState.instance.isTabExampleEnabled,
101+
child: CupertinoPageScaffold(
102+
navigationBar: CupertinoNavigationBar(
103+
middle: GlobalState.instance.isTabExampleEnabled
104+
? const Padding(
105+
padding: EdgeInsets.only(left: 52.0),
106+
child: TabExample(),
107+
)
108+
: const Text('macos_window_utils demo'),
109+
leading: GlobalState.instance.isTabExampleEnabled
110+
? const SizedBox()
111+
: CupertinoButton(
112+
padding: EdgeInsets.zero,
113+
child: UiElementColorBuilder(
114+
uiElementColorContainerInstanceProvider:
115+
OwnedUiElementColorContainerInstanceProvider(),
116+
builder: (context, colorContainer) {
117+
return Icon(
118+
CupertinoIcons.sidebar_left,
119+
color: colorContainer.controlAccentColor,
120+
);
121+
}),
122+
onPressed: () => setState(
123+
() => _isSidebarOpen = !_isSidebarOpen,
124+
),
125+
),
126+
),
127+
child: DefaultTextStyle(
128+
style: CupertinoTheme.of(context).textTheme.textStyle,
129+
child: SafeArea(
130+
child: MainArea(
131+
setState: setState,
132+
),
133+
),
134+
),
121135
),
122136
),
123-
),
124-
),
125-
),
126-
);
137+
);
138+
});
127139
}
128140
}

example/lib/main_area/main_area.dart

Lines changed: 26 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import 'package:example/main_area/ns_window_delegate_demo/ns_window_delegate_demo.dart';
22
import 'package:example/main_area/window_manipulator_demo/window_manipulator_demo.dart';
3+
import 'package:example/toolbar_passthrough_demo/toolbar_passthrough_demo.dart';
34
import 'package:flutter/cupertino.dart';
45

56
class MainArea extends StatefulWidget {
@@ -33,6 +34,7 @@ class _MainAreaState extends State<MainArea> {
3334
setState: widget.setState,
3435
),
3536
const NSWindowDelegateDemo(),
37+
const ToolbarPassthroughDemo(),
3638
],
3739
),
3840
),
@@ -52,23 +54,32 @@ class _SegmentedControl extends StatelessWidget {
5254

5355
@override
5456
Widget build(BuildContext context) {
55-
return CupertinoSlidingSegmentedControl(
56-
groupValue: currentTabIndex,
57-
onValueChanged: onTabIndexChanged,
58-
children: const {
59-
0: Padding(
60-
padding: EdgeInsets.symmetric(horizontal: 20),
61-
child: Text(
62-
'WindowManipulator demo',
57+
return Padding(
58+
padding: const EdgeInsets.symmetric(horizontal: 20),
59+
child: CupertinoSlidingSegmentedControl(
60+
groupValue: currentTabIndex,
61+
onValueChanged: onTabIndexChanged,
62+
children: const {
63+
0: Padding(
64+
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 4),
65+
child: Text(
66+
'WindowManipulator demo',
67+
),
6368
),
64-
),
65-
1: Padding(
66-
padding: EdgeInsets.symmetric(horizontal: 20),
67-
child: Text(
68-
'NSWindowDelegate demo',
69+
1: Padding(
70+
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 4),
71+
child: Text(
72+
'NSWindowDelegate demo',
73+
),
6974
),
70-
),
71-
},
75+
2: Padding(
76+
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 4),
77+
child: Text(
78+
'MacosToolbarPassthrough demo',
79+
),
80+
),
81+
},
82+
),
7283
);
7384
}
7485
}

example/lib/main_area/window_manipulator_demo/command_list_provider/command_list_provider.dart

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -628,7 +628,7 @@ class CommandListProvider {
628628
buttonType: NSWindowButtonType.closeButton, offset: null),
629629
),
630630
Command(
631-
name: 'WindowManipulator.getStandardWindowButtonPosition(buttonType: '
631+
name: 'getStandardWindowButtonPosition(buttonType: '
632632
'NSWindowButtonType.closeButton)',
633633
description: 'Prints the position of the close button.\n\n'
634634
'**Note:** The y position is measured as the distance from the '
@@ -639,7 +639,7 @@ class CommandListProvider {
639639
.toString()),
640640
),
641641
Command(
642-
name: 'WindowManipulator.centerWindow()',
642+
name: 'centerWindow()',
643643
description: 'Sets the window’s location to the center of the screen.'
644644
'\n\nThe window is placed exactly in the center horizontally and '
645645
'somewhat above center vertically. Such a placement carries a '
@@ -649,7 +649,7 @@ class CommandListProvider {
649649
function: () => WindowManipulator.centerWindow(),
650650
),
651651
Command(
652-
name: 'WindowManipulator.getWindowFrame()',
652+
name: 'getWindowFrame()',
653653
description: 'Returns the window’s window’s frame rectangle in screen '
654654
'coordinates, including the title bar.\n\n'
655655
'Keep in mind that the y-coordinate returned is measured from the '
@@ -658,7 +658,7 @@ class CommandListProvider {
658658
debugPrint((await WindowManipulator.getWindowFrame()).toString()),
659659
),
660660
Command(
661-
name: 'WindowManipulator.setWindowFrame('
661+
name: 'setWindowFrame('
662662
'const Offset(64, 32) & const Size(512, 512),'
663663
'animate: true)',
664664
description: 'Sets the window’s frame rectangle in screen coordinates, '
@@ -668,25 +668,25 @@ class CommandListProvider {
668668
animate: true),
669669
),
670670
Command(
671-
name: 'WindowManipulator.preventWindowClosure()',
671+
name: 'preventWindowClosure()',
672672
description: 'Prevents the window from being closed by the user.\n\n'
673673
'The window will still be closable programmatically by calling '
674674
'`closeWindow`.',
675675
function: () => WindowManipulator.preventWindowClosure(),
676676
),
677677
Command(
678-
name: 'WindowManipulator.allowWindowClosure()',
678+
name: 'allowWindowClosure()',
679679
description: 'Allows the window to be closed by the user.',
680680
function: () => WindowManipulator.allowWindowClosure(),
681681
),
682682
Command(
683-
name: 'WindowManipulator.isWindowClosureAllowed()',
683+
name: 'isWindowClosureAllowed()',
684684
description: 'Returns whether the window can be closed by the user.',
685685
function: () async => debugPrint(
686686
(await WindowManipulator.isWindowClosureAllowed()).toString()),
687687
),
688688
Command(
689-
name: 'WindowManipulator.closeWindow()',
689+
name: 'closeWindow()',
690690
description: 'Removes the window from the screen. \n\n'
691691
'The close method differs in two important ways from the '
692692
'`performClose` method:\n'
@@ -699,7 +699,7 @@ class CommandListProvider {
699699
function: () => WindowManipulator.closeWindow(),
700700
),
701701
Command(
702-
name: 'WindowManipulator.performClose()',
702+
name: 'performClose()',
703703
description: 'Simulates the user clicking the close button by '
704704
'momentarily highlighting the button and then closing the window.',
705705
function: () => WindowManipulator.performClose(),

0 commit comments

Comments
 (0)