Skip to content

Commit 341ae7c

Browse files
authored
Allow running callbacks when an item's highlight status changes (#2)
* Adding support for running callbacks when an item receives or loses highlight on macOS * Adding highlight callback support on Windows * Adding highlight callback support on Linux * Adding visual feedback for highlight and click events to example app * Adding submenu item highlight support on Windows
1 parent 772c61b commit 341ae7c

File tree

6 files changed

+204
-9
lines changed

6 files changed

+204
-9
lines changed

example/lib/pages/home.dart

Lines changed: 105 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
1-
import 'package:bot_toast/bot_toast.dart';
1+
import 'package:contextual_menu/contextual_menu.dart';
22
import 'package:flutter/foundation.dart';
33
import 'package:flutter/gestures.dart';
44
import 'package:flutter/material.dart' hide MenuItem;
55
import 'package:preference_list/preference_list.dart';
6-
import 'package:contextual_menu/contextual_menu.dart';
76

87
class HomePage extends StatefulWidget {
98
const HomePage({Key? key}) : super(key: key);
@@ -16,6 +15,8 @@ class _HomePageState extends State<HomePage> {
1615
bool _shouldReact = false;
1716
Offset? _position;
1817
Placement _placement = Placement.bottomLeft;
18+
String? _highlighted;
19+
String? _selected;
1920

2021
Menu? _menu;
2122

@@ -24,59 +25,119 @@ class _HomePageState extends State<HomePage> {
2425
super.initState();
2526
}
2627

28+
void _onClick(MenuItem item) {
29+
setState(() {
30+
_selected = item.label;
31+
});
32+
}
33+
34+
void _onHighlight(MenuItem item) {
35+
setState(() {
36+
_highlighted = item.label;
37+
});
38+
}
39+
40+
void _onLoseHighlight(_) {
41+
setState(() {
42+
_highlighted = null;
43+
});
44+
}
45+
2746
void _handleClickPopUp() {
2847
_menu ??= Menu(
2948
items: [
3049
MenuItem(
3150
label: 'Look Up "LeanFlutter"',
51+
onClick: _onClick,
52+
onHighlight: _onHighlight,
53+
onLoseHighlight: _onLoseHighlight,
3254
),
3355
MenuItem(
3456
label: 'Search with Google',
57+
onClick: _onClick,
58+
onHighlight: _onHighlight,
59+
onLoseHighlight: _onLoseHighlight,
3560
),
3661
MenuItem.separator(),
3762
MenuItem(
3863
label: 'Cut',
64+
onClick: _onClick,
65+
onHighlight: _onHighlight,
66+
onLoseHighlight: _onLoseHighlight,
3967
),
4068
MenuItem(
4169
label: 'Copy',
70+
onClick: _onClick,
71+
onHighlight: _onHighlight,
72+
onLoseHighlight: _onLoseHighlight,
4273
),
4374
MenuItem(
4475
label: 'Paste',
76+
onClick: _onClick,
77+
onHighlight: _onHighlight,
78+
onLoseHighlight: _onLoseHighlight,
4579
disabled: true,
4680
),
4781
MenuItem.submenu(
4882
label: 'Share',
83+
onClick: _onClick,
84+
onHighlight: _onHighlight,
85+
onLoseHighlight: _onLoseHighlight,
4986
submenu: Menu(
5087
items: [
5188
MenuItem(
5289
label: 'Item 1',
90+
onClick: _onClick,
91+
onHighlight: _onHighlight,
92+
onLoseHighlight: _onLoseHighlight,
5393
),
5494
MenuItem(
5595
label: 'Item 2',
96+
onClick: _onClick,
97+
onHighlight: _onHighlight,
98+
onLoseHighlight: _onLoseHighlight,
5699
),
57100
MenuItem.checkbox(
58101
label: 'Centered Layout',
102+
onClick: _onClick,
103+
onHighlight: _onHighlight,
104+
onLoseHighlight: _onLoseHighlight,
59105
checked: false,
60106
),
61107
MenuItem.separator(),
62108
MenuItem.checkbox(
63109
label: 'Show Primary Side Bar',
110+
onClick: _onClick,
111+
onHighlight: _onHighlight,
112+
onLoseHighlight: _onLoseHighlight,
64113
checked: true,
65114
),
66115
MenuItem.checkbox(
67116
label: 'Show Secondary Side Bar',
117+
onClick: _onClick,
118+
onHighlight: _onHighlight,
119+
onLoseHighlight: _onLoseHighlight,
68120
checked: true,
69121
),
70122
MenuItem.checkbox(
71123
label: 'Show Status Bar',
124+
onClick: _onClick,
125+
onHighlight: _onHighlight,
126+
onLoseHighlight: _onLoseHighlight,
72127
checked: true,
73128
),
74129
MenuItem.checkbox(
75130
label: 'Show Activity Bar',
131+
onClick: _onClick,
132+
onHighlight: _onHighlight,
133+
onLoseHighlight: _onLoseHighlight,
76134
checked: true,
77135
),
78136
MenuItem.checkbox(
79137
label: 'Show Panel Bar',
138+
onClick: _onClick,
139+
onHighlight: _onHighlight,
140+
onLoseHighlight: _onLoseHighlight,
80141
checked: false,
81142
),
82143
],
@@ -85,47 +146,74 @@ class _HomePageState extends State<HomePage> {
85146
MenuItem.separator(),
86147
MenuItem.submenu(
87148
label: 'Font',
149+
onClick: _onClick,
150+
onHighlight: _onHighlight,
151+
onLoseHighlight: _onLoseHighlight,
88152
submenu: Menu(
89153
items: [
90154
MenuItem.checkbox(
91155
label: 'Item 1',
156+
onHighlight: _onHighlight,
157+
onLoseHighlight: _onLoseHighlight,
92158
checked: true,
93159
onClick: (menuItem) {
94160
menuItem.checked = !(menuItem.checked == true);
161+
_onClick(menuItem);
95162
},
96163
),
97164
MenuItem.checkbox(
98165
label: 'Item 2',
166+
onHighlight: _onHighlight,
167+
onLoseHighlight: _onLoseHighlight,
99168
checked: false,
100169
onClick: (menuItem) {
101170
menuItem.checked = !(menuItem.checked == true);
171+
_onClick(menuItem);
102172
},
103173
),
104174
MenuItem.separator(),
105175
MenuItem(
106176
label: 'Item 3',
177+
onClick: _onClick,
178+
onHighlight: _onHighlight,
179+
onLoseHighlight: _onLoseHighlight,
107180
checked: false,
108181
),
109182
MenuItem(
110183
label: 'Item 4',
184+
onClick: _onClick,
185+
onHighlight: _onHighlight,
186+
onLoseHighlight: _onLoseHighlight,
111187
checked: false,
112188
),
113189
MenuItem(
114190
label: 'Item 5',
191+
onClick: _onClick,
192+
onHighlight: _onHighlight,
193+
onLoseHighlight: _onLoseHighlight,
115194
checked: false,
116195
),
117196
],
118197
),
119198
),
120199
MenuItem.submenu(
121200
label: 'Speech',
201+
onClick: _onClick,
202+
onHighlight: _onHighlight,
203+
onLoseHighlight: _onLoseHighlight,
122204
submenu: Menu(
123205
items: [
124206
MenuItem(
125207
label: 'Item 1',
208+
onClick: _onClick,
209+
onHighlight: _onHighlight,
210+
onLoseHighlight: _onLoseHighlight,
126211
),
127212
MenuItem(
128213
label: 'Item 2',
214+
onClick: _onClick,
215+
onHighlight: _onHighlight,
216+
onLoseHighlight: _onLoseHighlight,
129217
),
130218
],
131219
),
@@ -166,6 +254,21 @@ class _HomePageState extends State<HomePage> {
166254
),
167255
],
168256
),
257+
PreferenceListSection(
258+
title: const Text('Results'),
259+
children: [
260+
PreferenceListItem(
261+
disabled: true,
262+
title: const Text('Highlighted'),
263+
accessoryView: Text(_highlighted.toString()),
264+
),
265+
PreferenceListItem(
266+
disabled: true,
267+
title: const Text('Selected'),
268+
accessoryView: Text(_selected.toString()),
269+
),
270+
],
271+
),
169272
],
170273
);
171274
}

lib/src/contextual_menu.dart

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ class _ContextualMenu with MenuBehavior {
1616
final MethodChannel _channel = const MethodChannel('contextual_menu');
1717

1818
Menu? _menu;
19+
int? _lastHighlighted;
1920

2021
Future<void> _methodCallHandler(MethodCall call) async {
2122
switch (call.method) {
@@ -25,6 +26,23 @@ class _ContextualMenu with MenuBehavior {
2526
if (menuItem != null) {
2627
menuItem.onClick!(menuItem);
2728
}
29+
break;
30+
case 'onMenuItemHighlight':
31+
final id = call.arguments['id'] as int?;
32+
if (_lastHighlighted != null && _lastHighlighted != id) {
33+
final previouslyHighlighted =
34+
_menu?.getMenuItemById(_lastHighlighted!);
35+
previouslyHighlighted?.onLoseHighlight?.call(previouslyHighlighted);
36+
}
37+
_lastHighlighted = id;
38+
39+
if (id == null) {
40+
break;
41+
}
42+
43+
final menuItem = _menu?.getMenuItemById(id);
44+
menuItem?.onHighlight?.call(menuItem);
45+
2846
break;
2947
}
3048
}

linux/contextual_menu_plugin.cc

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,24 @@ void _on_activate(GtkMenuItem* item, gpointer user_data) {
4646
result_data, nullptr, nullptr, nullptr);
4747
}
4848

49+
void _on_select(GtkMenuItem* item, gpointer user_data) {
50+
gint id = GPOINTER_TO_INT(user_data);
51+
52+
g_autoptr(FlValue) result_data = fl_value_new_map();
53+
fl_value_set_string_take(result_data, "id", fl_value_new_int(id));
54+
fl_method_channel_invoke_method(plugin_instance->channel,
55+
"onMenuItemHighlight", result_data, nullptr,
56+
nullptr, nullptr);
57+
}
58+
59+
void _on_deselect(GtkMenuItem* item, gpointer user_data) {
60+
g_autoptr(FlValue) result_data = fl_value_new_map();
61+
fl_value_set_string_take(result_data, "id", fl_value_new_null());
62+
fl_method_channel_invoke_method(plugin_instance->channel,
63+
"onMenuItemHighlight", result_data, nullptr,
64+
nullptr, nullptr);
65+
}
66+
4967
GtkWidget* _create_menu(FlValue* args) {
5068
FlValue* items_value = fl_value_lookup_string(args, "items");
5169

@@ -88,6 +106,10 @@ GtkWidget* _create_menu(FlValue* args) {
88106

89107
g_signal_connect(G_OBJECT(item), "activate", G_CALLBACK(_on_activate),
90108
GINT_TO_POINTER(item_id));
109+
g_signal_connect(G_OBJECT(item), "select", G_CALLBACK(_on_select),
110+
GINT_TO_POINTER(item_id));
111+
g_signal_connect(G_OBJECT(item), "deselect", G_CALLBACK(_on_deselect),
112+
GINT_TO_POINTER(item_id));
91113

92114
gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
93115
}

macos/Classes/ContextualMenu.swift

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import AppKit
99

1010
public class ContextualMenu: NSMenu, NSMenuDelegate {
1111
public var onMenuItemClick:((NSMenuItem) -> Void)?
12+
public var onMenuItemHighlight:((NSMenuItem?) -> Void)?
1213

1314
public override init(title: String) {
1415
super.init(title: title)
@@ -81,9 +82,15 @@ public class ContextualMenu: NSMenu, NSMenuDelegate {
8182
}
8283
}
8384

84-
// NSMenuDelegate
85+
// MARK: NSMenuDelegate
8586

8687
public func menuDidClose(_ menu: NSMenu) {
8788

8889
}
90+
91+
public func menu(_ menu: NSMenu, willHighlight item: NSMenuItem?) {
92+
if (onMenuItemHighlight != nil) {
93+
self.onMenuItemHighlight!(item)
94+
}
95+
}
8996
}

macos/Classes/ContextualMenuPlugin.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,12 @@ public class ContextualMenuPlugin: NSObject, FlutterPlugin {
4141
]
4242
self.channel.invokeMethod("onMenuItemClick", arguments: args, result: nil)
4343
}
44+
menu!.onMenuItemHighlight = { (menuItem: NSMenuItem?) in
45+
let args: NSDictionary = [
46+
"id": menuItem?.tag as Any,
47+
]
48+
self.channel.invokeMethod("onMenuItemHighlight", arguments: args, result: nil)
49+
}
4450

4551
let position = args["position"] as? [String: Any]
4652
let placement = args["placement"] as! String

0 commit comments

Comments
 (0)