Native context menu for Flutter apps

Overview

native_context_menu

Native context menu for flutter apps

lesnitsky.dev GitHub stars Twitter Follow

Preview

Installation

flutter pub add native_context_menu

Usage

import 'package:native_context_menu/native_context_menu.dart';
import 'package:flutter/material.dart';

void main() {
  runApp(App());
}

class App extends StatefulWidget {
  const App({Key? key}) : super(key: key);

  @override
  State<App> createState() => _AppState();
}

class _AppState extends State<App> {
  String? action;

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: ContextMenuRegion(
          onDismissed: () => setState(() => action = 'Menu was dismissed'),
          onItemSelected: (item) => setState(() {
            action = '${item.title} was selected';
          }),
          menuItems: [
            MenuItem(title: 'First item'),
            MenuItem(title: 'Second item'),
            MenuItem(
              title: 'Third item with submenu',
              items: [
                MenuItem(title: 'First subitem'),
                MenuItem(title: 'Second subitem'),
                MenuItem(title: 'Third subitem'),
              ],
            ),
            MenuItem(title: 'Fourth item'),
          ],
          child: Card(
            child: Center(
              child: Text(action ?? 'Right click me'),
            ),
          ),
        ),
      ),
    );
  }
}

Platform support

Platform Supported
MacOS
Linux
Windows

License

MIT


lesnitsky.dev GitHub stars Twitter Follow

Comments
  • Add Linux support, refactor Windows & remove dependency upon Dart devicePixelRatio & position

    Add Linux support, refactor Windows & remove dependency upon Dart devicePixelRatio & position

    This PR adds:

    • Linux support.
    • Refactoring of Windows implementation to use InvokeMethod on C++ side to notify about selected menu item. And using GetCursorPos to get position for opening the context menu.
    • Linux & Windows no longer require devicePixelRatio and position arguments (although optional).

    NOTE: I made native code itself find the menu coordinates and show the menu accordingly.

    I noticed that you are passing the context menu coordinates from Dart (which is perfectly fine), so I declared an optional macro USE_FLUTTER_POSITION to use those coordinates on Linux instead of using GTK's method (to use it just add #define USE_FLUTTER_POSITION at the start of the file). I do not recommend this however since it was causing problems for me on the Wayland.

    I believe code is nicely arranged (follows Google C++ Style Guide) & I have added good amount of comments explaining everything.

    https://user-images.githubusercontent.com/28951144/135107609-8f02281a-3492-4f22-a09a-7425d4980dee.mp4

    opened by alexmercerind 4
  • Add Linux support & refactor Windows

    Add Linux support & refactor Windows

    This PR adds:

    • Linux support to the plugin.
    • Refactored Windows implementation, to use InvokeMethod on C++ side to notify about selected menu item. And using GetCursorPos to get position for opening the context menu.
    • Now passing devicePixelRatio and position arguments (from Dart) will result in context menu shown at the passed position, otherwise at the cursor position.
    • Following Google C++ Style Guide on Windows & Linux.
    • Other changes proposed from: #7.

    Screenshot_20211001_134609

    EDIT: I will be glad if this my contribution gets counted for Hacktoberfest's criteria of having hacktoberfest-accepted label on this PR. 😄

    opened by alexmercerind 2
  • Fixing compliation in Flutter 3.0

    Fixing compliation in Flutter 3.0

    Flutter 3.0 adds its own MenuItem type, which conflicts with one of the types from this package and causes compilation errors when both are imported. This PR fixes the issue by just hiding the Flutter type in the import statements.

    opened by jmatth 1
  • Add a longer description to pubspec.yaml

    Add a longer description to pubspec.yaml

    This PR adds a longer description to pubspec.yaml to add another 10 points to the pub.dev score.

    It also updates .gitignore to ignore IntelliJ configuration files.

    opened by GroovinChip 1
  • Linux Nested Sub Menu

    Linux Nested Sub Menu

    I had a need to have a menu on Linux with a menu with a sub menu nested inside a sub menu. I'm not proficient in C++ so after I fumbled around for a while, I ultimately came up with this solution. I'll let it in this issue since I doubt this hackery is worthy of a PR. All edits were made inside native_context_menu_plugin_handle_method_call:

    static void native_context_menu_plugin_handle_method_call(
        NativeContextMenuPlugin* self, FlMethodCall* method_call) {
      g_autoptr(FlMethodResponse) response = nullptr;
      const gchar* method = fl_method_call_get_name(method_call);
      if (strcmp(method, kShowMenu) == 0) {
        // Clear previously saved object instances.
        self->last_menu_items.clear();
        self->last_menu_item_selected = false;
        if (self->last_menu_thread != nullptr) {
          self->last_menu_thread->detach();
          self->last_menu_thread.reset(nullptr);
        }
        auto arguments = fl_method_call_get_args(method_call);
        auto device_pixel_ratio =
            fl_value_lookup_string(arguments, "devicePixelRatio");
    
        auto position = fl_value_lookup_string(arguments, "position");
    
        GdkWindow* window = get_window(self);
    
        GtkWidget* top_level_menu = gtk_menu_new();
        auto top_level_items = fl_value_lookup_string(arguments, "items");
    
        for (int32_t i = 0; i < fl_value_get_length(top_level_items); i++) {
    
          int32_t top_level_id = fl_value_get_int(
              fl_value_lookup_string(fl_value_get_list_value(top_level_items, i), "id"));
          const char* top_level_title = fl_value_get_string(
              fl_value_lookup_string(fl_value_get_list_value(top_level_items, i), "title"));
    
          auto second_level_items =
              fl_value_lookup_string(fl_value_get_list_value(top_level_items, i), "items");
    
          self->last_menu_items.emplace_back(std::make_unique<MenuItem>(top_level_id, top_level_title));
    
          GtkWidget* top_level_item = gtk_menu_item_new_with_label(top_level_title);
    
          // Check for second level items & create a second level menu.
          if (fl_value_get_length(second_level_items) > 0) {
    
            GtkWidget* second_level_menu = gtk_menu_new();
    
            for (int32_t j = 0; j < fl_value_get_length(second_level_items); j++) {
    
              int32_t second_level_id = fl_value_get_int(fl_value_lookup_string(
                  fl_value_get_list_value(second_level_items, j), "id"));
              const char* second_level_title = fl_value_get_string(fl_value_lookup_string(
                  fl_value_get_list_value(second_level_items, j), "title"));
    
              auto third_level_items =
                fl_value_lookup_string(fl_value_get_list_value(second_level_items, j), "items");
    
              self->last_menu_items.back()->items().emplace_back(
                  std::make_unique<MenuItem>(second_level_id, second_level_title));
    
              GtkWidget* second_level_item = gtk_menu_item_new_with_label(second_level_title);
    
              // Check for third level items & create a third level menu.
              if (fl_value_get_length(third_level_items) > 0) {
                GtkWidget* third_level_menu = gtk_menu_new();
    
                for (int32_t k = 0; k < fl_value_get_length(third_level_items); k++) {
    
                  int32_t third_level_id = fl_value_get_int(fl_value_lookup_string(
                      fl_value_get_list_value(third_level_items, k), "id"));
                  const char* third_level_title = fl_value_get_string(fl_value_lookup_string(
                      fl_value_get_list_value(third_level_items, k), "title"));
    
                  self->last_menu_items.back()->items().back()->items().emplace_back(
                    std::make_unique<MenuItem>(third_level_id, third_level_title));
    
                  GtkWidget* third_level_item = gtk_menu_item_new_with_label(third_level_title);
    
                  gtk_widget_show(third_level_item);
                  gtk_menu_shell_append(GTK_MENU_SHELL(third_level_menu), third_level_item);
    
                  g_signal_connect(
                      G_OBJECT(third_level_item), "activate",
                      G_CALLBACK(on_menu_item_clicked),
                      (gpointer)self->last_menu_items.back()->items().at(j).get()->items().at(k).get());
                }
    
                gtk_menu_item_set_submenu(GTK_MENU_ITEM(second_level_item), third_level_menu);
    
              } else {
                g_signal_connect(G_OBJECT(second_level_item), "activate",
                                G_CALLBACK(on_menu_item_clicked),
                                (gpointer)self->last_menu_items.back()->items().at(j).get());
              }
    
              gtk_widget_show(second_level_item);
              gtk_menu_shell_append(GTK_MENU_SHELL(second_level_menu), second_level_item);
    
            }
    
            gtk_menu_item_set_submenu(GTK_MENU_ITEM(top_level_item), second_level_menu);
    
          } else {
            g_signal_connect(G_OBJECT(top_level_item), "activate",
                             G_CALLBACK(on_menu_item_clicked),
                             (gpointer)self->last_menu_items.back().get());
          }
    
          gtk_widget_show(top_level_item);
          gtk_menu_shell_append(GTK_MENU_SHELL(top_level_menu), top_level_item);
        }
    
        GdkRectangle rectangle;
        // Pass `devicePixelRatio` and `position` from Dart to show menu at
        // specified coordinates. If it is not defined, WIN32 will use
        // `GetCursorPos` to show the context menu at the cursor's position.
        if (device_pixel_ratio != nullptr && position != nullptr) {
          rectangle.x = fl_value_get_float(fl_value_get_list_value(position, 0)) *
                        fl_value_get_float(device_pixel_ratio);
          rectangle.y = fl_value_get_float(fl_value_get_list_value(position, 1)) *
                        fl_value_get_float(device_pixel_ratio);
        } else {
          GdkDevice* mouse_device;
          int x, y;
          // Legacy support.
    #if GTK_CHECK_VERSION(3, 20, 0)
          GdkSeat* seat = gdk_display_get_default_seat(gdk_display_get_default());
          mouse_device = gdk_seat_get_pointer(seat);
    #else
          GdkDeviceManager* devman =
              gdk_display_get_device_manager(gdk_display_get_default());
          mouse_device = gdk_device_manager_get_client_pointer(devman);
    #endif
          gdk_window_get_device_position(window, mouse_device, &x, &y, NULL);
          rectangle.x = x;
          rectangle.y = y;
        }
        g_signal_connect(G_OBJECT(top_level_menu), "deactivate",
                         G_CALLBACK(on_menu_deactivated), nullptr);
        // `gtk_menu_popup_at_rect` is used since `gtk_menu_popup_at_pointer` will
        // require event box creation & another callback will be involved. This way
        // is straight forward & easy to work with.
        // NOTE: GDK_GRAVITY_NORTH_WEST is hard-coded by default since no analog is
        // present for it inside the Dart platform channel code (as of now). In
        // summary, this will create a menu whose body is in bottom-right to the
        // position of the mouse pointer.
        gtk_menu_popup_at_rect(GTK_MENU(top_level_menu), window, &rectangle,
                               GDK_GRAVITY_NORTH_WEST, GDK_GRAVITY_NORTH_WEST,
                               NULL);
    
        // Responding with `null`, click event & respective `id` of the `MenuItem`
        // is notified through callback. Otherwise the GUI will become unresponsive.
        // To keep the API same, a `Completer` is used in the Dart.
        response =
            FL_METHOD_RESPONSE(fl_method_success_response_new(fl_value_new_null()));
      } else {
        response = FL_METHOD_RESPONSE(fl_method_not_implemented_response_new());
      }
      fl_method_call_respond(method_call, response, nullptr);
    }
    
    opened by cranst0n 0
  • no support for left mouse click

    no support for left mouse click

    Sometimes it is necessarily to open context menu by left click So i have to modify:

    shouldReact = e.kind == PointerDeviceKind.mouse &&
                    e.buttons == kSecondaryMouseButton ||
                e.buttons == kPrimaryButton;
    

    But it would be better if developers can choose which mouse will open menu

    opened by khomin 0
  • Context menu pop on primary display

    Context menu pop on primary display

    On a multi-monitor setup, the context menu always open on the primary monitor.

    What happens: If the app is on the main monitor, the context pops near the mouse. If the app is on a secondary monitor the context menu will pop on the edge of the main monitor.

    What should happen: The context menu should always pop next to the mouse, no matter on what monitor the screen is running.

    https://user-images.githubusercontent.com/7316082/183260854-ec7cf5a6-6e8b-436b-817e-c1e6417f8ee2.mp4

    opened by MoutonSanglant 0
  • MenuItem conflict on Flutter 3.0

    MenuItem conflict on Flutter 3.0

    After upgrade to Flutter 3.0, it have error:

    ../../.pub-cache/hosted/pub.dartlang.org/native_context_menu-0.2.1+4/lib/src/context_menu_region.dart:4:1: Error: 'MenuItem' is imported from both 'package:flutter/src/widgets/platform_menu_bar.dart' and 'package:native_context_menu/src/method_channel.dart'.

    opened by dodatw 5
  • Federalize the plugin

    Federalize the plugin

    Since there are some different context menu APIs in some platforms (eg. Windows, see #2), a federalized plugin could take advantage of its flexibility on having multiple platform implementations.

    opened by renancaraujo 3
  • Add support for disabling menu items on macos

    Add support for disabling menu items on macos

    This PR addresses https://github.com/lesnitsky/native_context_menu/issues/11 on macos. A couple of important notes:

    • There's an onSelected function on MenuItem that doesn't seem like it is used at the moment. It was a bit unclear what the difference was designed to be between this callback and onItemSelected in ContextMenuRegion. My assumption is that I'd use the former if I wanted different functions to be called back for each item, and I'd use the latter if I just wanted to have one function (and if both were present, then both would be called). However, as I mentioned, MenuItem.onSelected isn't currently hooked up, so I had to add a call to ContextMenuRegion to make sure it calls both. This assumption may not be correct, but was important for my decision on how to enable/disable each item.
    • I consider adding a separate flag called isEnabled defaulting to true, however, I ended adopting the Flutter convention of disabling the widget if the onSelected function is not set. However, this would be a breaking change for any current users of this library (all menu items would be disabled unless they were setting the onSelected in their MenuItem objects - which they wouldn't be because those callbacks don't currently get called)

    Windows and Linux have not been touched.

    opened by edwardaux 1
Owner
Andrei Lesnitsky
Open Source Software Engineer @invertase
Andrei Lesnitsky
Plays native alert sound and shows native dialogs/alerts in your Flutter app.

flutter_platform_alert 2021 © Weizhong Yang a.k.a zonble. A simple plugin to present native alerts, including playing alert sounds and showing alert d

Weizhong Yang a.k.a zonble 60 Dec 21, 2022
This plugin allows Flutter desktop apps to defines system tray.

tray_manager This plugin allows Flutter desktop apps to defines system tray. tray_manager Platform Support Quick Start Installation ⚠️ Linux requireme

LeanFlutter 122 Dec 22, 2022
This plugin allows Flutter desktop apps to Auto launch on startup / login.

This plugin allows Flutter desktop apps to Auto launch on startup / login.

LeanFlutter 45 Dec 18, 2022
This plugin allows Flutter desktop apps to defines system/inapp wide hotkey (i.e. shortcut).

hotkey_manager This plugin allows Flutter desktop apps to defines system/inapp wide hotkey (i.e. shortcut). hotkey_manager Platform Support Quick Star

LeanFlutter 82 Jan 6, 2023
A framework for building native Windows apps with React.

React Native for Windows Build native Windows apps with React. See the official React Native website for an introduction to React Native. React Native

Microsoft 15.2k Jan 2, 2023
A Flutter package that makes it easy to customize and work with your Flutter desktop app's system tray.

system_tray A Flutter package that that enables support for system tray menu for desktop flutter apps. on Windows, macOS and Linux. Features: - Modify

AnTler 140 Dec 30, 2022
Flutter-Clock-and-Reminder-App - a highly functional clock and reminder app developed on flutter framework.

clock_app A new Flutter project. Getting Started This project is a starting point for a Flutter application. A few resources to get you started if thi

Umar Baloch 6 Aug 4, 2022
A native textfield that can be used in place of Flutter's TextField widget.

Better Textfield A native textfield that can be used in place of Flutter's TextField widget. Demo demo.mp4 Here are some screenshots of the demo app:

Abhay Maurya 1 Sep 13, 2022
Acrylic effect for all existing Win32 context menus

AcrylicMenus This is a proof-of-concept tiny application that applies acrylic effect to almost all existing Win32 context menus on Windows 10 and Wind

null 411 Jan 1, 2023
Detect-KeAttachProcess by iterating through all processes as well as checking the context of the thread.

Detect-KeAttachProcess Detect-KeAttachProcess - by iterating through all processes as well as checking the context of the thread. Recently I started s

null 94 Nov 26, 2022
Visual Studio native debugger extension to help debug native applications using Mono.

Unity Mixed Callstack UnityMixedCallstack is a Visual Studio 2017/2019 extension to help debug native applications embedding Mono, like Unity. If you

Unity Technologies 83 Nov 28, 2022
A customized LGL Android mod menu, containing ESP only for PUBG Mobile 1.3.0 for Android

PUBG Mobile ESP Mod Menu A customized LGL mod menu, containing ESP only for PUBG Mobile 1.3.0 for Android. Everything are fixed so it works with both

null 42 Mar 19, 2022
Portal 2/Portal Reloaded internal cheat sdk with imgui-based menu

portal2-internal A simple Portal 2/Portal Reloaded internal cheat base with imgui-based menu coded in a few days because why not Features: simple menu

es3n1n 38 Jan 2, 2023
Install the Homebrew Channel to the vWii Menu from Wii U Mode.

Install a channel to the vWii Menu from Wii U Mode. In its current state, it simply installs the Homebrew Channel.

Puzzle 36 Jan 2, 2023
api & source menu base import imgui from imgui-js

onetap v4 crack https://discord.gg/AXCtxVH4PB people asking me for otv4 source "bin2h" (meaning binary to hex) large hexadecimal array deleted all the

h4xr0x#1337 9 Sep 6, 2022
Very minimalistic UEFI boot menu / Stivale2 bootloader

Tosaithe Tosaithe is a minimalistic UEFI-firmware menu/bootloader. It can chain-load other EFI programs and loaders, including Linux kernels, and has

Davin McCall 12 Dec 8, 2022
ColecoAdam a boot menu and configurator for ADE virtual drives

VHS HEAD For Coleco Adam users with Adamnet Disk Emulator (ADE) devices. VHS Head is a boot menu and configuration program for ADE (Pro and Lite) user

Thomas Cherryhomes 3 Dec 24, 2021
Coco External GTA5 Mod Menu

Coco External GTA5 Mod Menu DISCLAIMER: This program is purely for Educational and Evaluation purposes ONLY. Be aware that I am not responsible for an

Colton Kennedy 5 Dec 26, 2022
[PS3] Bo1 Mod Menu Jo-Milk's Playground V2... I never finished this project what a shame... works on HEN

-PS3-Bo1-Mod-Menu-Jo-Milk-s-Playground-V2 [PS3] Bo1 Mod Menu Jo-Milk's Playground V2... I never finished this project what a shame... works on HEN Ple

Jo-Milk 5 Oct 9, 2022