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
A custom is strong dropdown menu for Flutter

A custom is strong dropdown menu for Flutter. Easy to use and powerful for customization, it's up to you what you want to display in the dropdown menu!

干志雄 662 Dec 26, 2022
Native text input from iOS for Flutter

Native Text Input for Flutter A text input widget built using the native UITextView on iOS (this package only supports iOS for now). Installation Foll

Henry Leung 55 Dec 24, 2022
A flutter plugin for Easily make Flutter apps responsive. Automatically adapt UI to different screen sizes. Responsiveness made simple.

A flutter plugin for Easily make Flutter apps responsive. Automatically adapt UI to different screen sizes. Responsiveness made simple.

Urmish Patel 191 Dec 29, 2022
Foody - Flutter project to display foods to clients and display charts and has a lot of features , two flutter apps : Android and Web

Foody Flutter project to display foods to the clients and use charts and use a lot of features to complete the process (HUGE APP) There two apps: Andr

ABDULKARIMALBAIK 1 Feb 7, 2022
A Flutter Widget Approach for using HTML tags & CSS styles in your upcoming Apps.

html_widgets A Flutter Widget Approach for using HTML tags & CSS styles in your upcoming Apps. Text Widgets *text property is required for all the tex

XenonLabz 7 Jul 14, 2022
HSV(HSB)/HSL/RGB/Material color picker inspired by all the good design for your amazing flutter apps.

flutter_colorpicker HSV(HSB)/HSL/RGB/Material color picker inspired by all the good design for your amazing flutter apps. Adorable color pickers out o

Dark Knight 279 Dec 30, 2022
A conceptual design for on boarding screens for mobile apps.

flutter_onboarding_ui_concept A conceptual design for on boarding screens for mobile apps. This app provides you with all the information you need to

CODEHOMIE 265 Dec 25, 2022
Movies App UI in Flutter using Simple Widgets without Using API's in Flutter.

Movies App UI in Flutter using Simple Widgets without Using API's in Flutter.

Habib ullah 3 May 15, 2022
The Coolicons icon pack for Flutter with over 400 icons available for your flutter project.

coolicons This flutter package allows you to use the Coolicons icon pack. Made from Coolicons. ?? Installation In the dependencies: section of your pu

Stephen Joel 1 May 24, 2022
Flutter-business-card-app - Flutter + Dart business card mobile app

Dart + Flutter Business Card Mobile Application

Mark Hellner 1 Nov 8, 2022
A Flutter project that gives basic flutter design to implement a login UI

Login UI Design A Flutter project that gives basic flutter design to implement a

CABREX 9 Nov 8, 2022
Flutter Complete E-Commerce app (UI by - 'The Flutter Way')

NOT COMPLETED YET! e_commerce A new Flutter project. Getting Started This project is a starting point for a Flutter application. A few resources to ge

null 1 Mar 8, 2022
Flutter Shop UI - an e-commerce app using Flutter

If you are planning to create an e-commerce app using Flutter then this Shop UI Kit would be the perfect choice for you to make a gorgeous app for both Android & iOS.

Trần Văn Nguyên 21 Nov 21, 2022
A Flutter staggered grid view

flutter_staggered_grid_view A Flutter staggered grid view which supports multiple columns with rows of varying sizes. Features Configurable cross-axis

Romain Rastel 2.7k Dec 30, 2022
Tinder like cards swipe effect with Flutter.

Tinder cards Hi! After showcasing Focus for Reddit, the app I am working on, people asked me how did I do the tinder like cards swipe (posts media are

Ivascu Adrian 733 Jan 7, 2023
The app to demo animation with Flutter by implement Facebook reactions

Facebook Reactions Animation Description The app to demo animation with Flutter by implement Facebook reactions. Watch the demo video for more clarity

Duy Tran 318 Jan 8, 2023
Custom widget for Flutter

Flushbar Use this package if you need more customization when notifying your user. For Android developers, it is made to substitute toasts and snackba

Andre Haueisen 899 Dec 30, 2022
flutter stepper_touch widget

stepper_touch the concept of the widget inspired from Nikolay Kuchkarov. i extended the functionality to be more useful in real world applications Tha

Raouf Rahiche 271 Dec 30, 2022
A TypeAhead widget for Flutter, where you can show suggestions to users as they type

Flutter TypeAhead A TypeAhead (autocomplete) widget for Flutter, where you can show suggestions to users as they type Features Shows suggestions in an

null 661 Jan 5, 2023