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
Native Dart client library for DBus

A native Dart client implementation of D-Bus. Accessing a remote object using dart-dbus The easiest way to get started is to use dart-dbus to generate

Canonical 81 Oct 28, 2022
Windows95 UI components for Flutter apps. Bring back the nostalgic look and feel of old operating systems with this set of UI components ready to use.

Flutter95 Windows95 UI components for Flutter apps. UNDER CONSTRUCTION Screenshots Components Scaffold95 Scaffold as a Windows95 styled window. Provid

Miguel Beltran 141 Dec 26, 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 81 Dec 21, 2022
This plugin allows Flutter desktop apps to Retrieve information about screen size, displays, cursor position, etc.

screen_retriever This plugin allows Flutter desktop apps to Retrieve information about screen size, displays, cursor position, etc. screen_retriever P

LeanFlutter 27 Dec 6, 2022
This plugin allows Flutter desktop apps to extract text from screen.

screen_text_extractor This plugin allows Flutter desktop apps to extract text from screen. screen_text_extractor Platform Support Quick Start Installa

LeanFlutter 30 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 resizing and repositioning the window.

window_manager This plugin allows Flutter desktop apps to resizing and repositioning the window. window_manager Platform Support Quick Start Installat

LeanFlutter 351 Jan 7, 2023
Build beautiful desktop apps with flutter and rust. 🌠 (wip)

flutter-rs Build flutter desktop app in dart & rust. Get Started Install requirements Rust flutter sdk Develop install the cargo flutter command cargo

null 2k Dec 26, 2022
A tool for debugging your Flutter apps.

anyinspect_app A tool for debugging your Flutter apps. Platform Support Linux macOS Windows ➖ ✔ī¸ ➖ Installation Downloads are available on the Release

AnyInspect 40 Nov 11, 2022
Build beautiful desktop apps with flutter and rust. 🌠

flutter-rs Build flutter desktop app in dart & rust. Get Started Install requirements Rust flutter sdk Develop install the cargo flutter command cargo

null 2k Dec 26, 2022
This plugin allows Flutter desktop apps to register and handle custom protocols

protocol_handler This plugin allows Flutter desktop apps to register and handle custom protocols (i.e. deep linking). English | įŽ€äŊ“中文 protocol_handler

LeanFlutter 57 Dec 22, 2022
Build Win32 apps with Dart!

A package that wraps some of the most common Win32 API calls using FFI to make them accessible to Dart code without requiring a C compiler or the Wind

Tim Sneath 609 Jan 2, 2023
Binder is a web framework that can be used to create web apps and APIs .

Binder Framework Binder is a web framework that can be used to create web apps and APIs . It's like React + Express or any combination of front-end fr

Kab Agouda 8 Sep 13, 2022
Flutter Installer is an installer for Flutter built with Flutter 💙😎✌

Flutter Installer Flutter Installer is an installer for Flutter built with Flutter ?? ?? ✌ Flutter and the related logo are trademarks of Google LLC.

Yazeed AlKhalaf 406 Dec 27, 2022
A Flutter package that makes it easy to customize and work with your Flutter desktop app window.

bitsdojo_window A Flutter package that makes it easy to customize and work with your Flutter desktop app window on Windows, macOS and Linux. Watch the

Bits Dojo 607 Jan 4, 2023
Flutter plugin for Flutter desktop(macOS/Linux/Windows) to change window size.

desktop_window Flutter plugin for Flutter desktop(macOS/Linux/Windows) to change window size. Usage import 'package:desktop_window/desktop_window.dart

ChunKoo Park 72 Dec 2, 2022
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
Fluttern is a web app made with Flutter to list Flutter internships/jobs for the community.

Fluttern Fluttern is a web app made with Flutter to list Flutter internships/jobs for the community. It uses Google Sheet as a backend, simplifying th

Aditya Thakur 3 Jan 5, 2022
Flutter on Windows, MacOS and Linux - based on Flutter Embedding, Go and GLFW.

go-flutter - A package that brings Flutter to the desktop Purpose Flutter allows you to build beautiful native apps on iOS and Android from a single c

null 5.5k Jan 6, 2023