[AD] native menus

[ Thread Index | Date Index | More lists.liballeg.org/allegro-developers Archives ]


Attached is a sample implementation of native system menus for
Windows. I put it in the native dialog addon and updated the
native_file_chooser example. The API is functional, but not complete:

ALLEGRO_MENU *al_create_menu(void);
bool al_append_menu (ALLEGRO_MENU *parent, ALLEGRO_MENU *popup, char
const *title, int id);
void al_set_display_menu(ALLEGRO_DISPLAY *display, ALLEGRO_MENU *menu);

Basic usage:

ALLEGRO_MENU *menu = al_create_menu();
ALLEGRO_MENU *file = al_create_menu();
al_append_menu(file, NULL, "Open", OPEN_ID);
al_append_menu(file, NULL, "Exit", EXIT_ID);
al_append_menu(menu, file, "File", 0);
al_set_display_menu(display, menu);

A higher level function could take some sort of A4-like structure to
make creating the menu less verbose.

Events are sent via the display's event source:

if (event.type == ALLEGRO_EVENT_MENU_CLICK && event.menu.id == EXIT_ID)

Now to the dirty implementation details:

1) It requires hooking into the Allegro Windows message callback
(window_callback). I just hacked on an extra callback as part of the
ALLEGRO_DISPLAY_WIN structure that the native dialog addon uses.
Obviously that's not a great solution. What about adding a public
function like:

al_win_add_message_callback( /* my callback function */ );

That would add a callback that would be processed before Allegro's. If
it returns TRUE, then Allegro does not process the event. Obviously a
person could break things with this, but I don't see why that's our
concern.

2) I added a "first class" ALLEGRO_MENU_EVENT, but I assume addons
shouldn't do that.

I was going to just create and emit a user event (I assume that's what
the audio streams do), but I wanted to use the display's event source.
The docs say "The event source must have been initialised with
al_init_user_event_source." Does that mean I'm not allowed to emit a
user event through the display's event source?

3) I don't know about OS X or GTK menus, but I assume they could use
the same sort of API as above.

--
Matthew Leverton
Index: include/allegro5/platform/aintwin.h
===================================================================
--- include/allegro5/platform/aintwin.h	(revision 14805)
+++ include/allegro5/platform/aintwin.h	(working copy)
@@ -76,6 +76,10 @@
     */
    int toggle_w;
    int toggle_h;
+   
+   /* Hack for the native menu addon */
+   LRESULT CALLBACK (*window_callback)
+      (ALLEGRO_DISPLAY_WIN *, HWND, UINT, WPARAM, LPARAM);
 };
 
 
Index: include/allegro5/events.h
===================================================================
--- include/allegro5/events.h	(revision 14805)
+++ include/allegro5/events.h	(working copy)
@@ -44,7 +44,9 @@
    ALLEGRO_EVENT_TOUCH_BEGIN                 = 50,
    ALLEGRO_EVENT_TOUCH_END                   = 51,
    ALLEGRO_EVENT_TOUCH_MOVE                  = 52,
-   ALLEGRO_EVENT_TOUCH_CANCEL                = 53
+   ALLEGRO_EVENT_TOUCH_CANCEL                = 53,
+   
+   ALLEGRO_EVENT_MENU_CLICK                  = 60
 };
 
 
@@ -132,6 +134,15 @@
 
 
 
+typedef struct ALLEGRO_MENU_EVENT
+{
+   _AL_EVENT_HEADER(struct ALLEGRO_MENU_EVENT)
+   struct ALLEGRO_DISPLAY *display;
+   int id;
+} ALLEGRO_MENU_EVENT;
+
+
+
 typedef struct ALLEGRO_MOUSE_EVENT
 {
    _AL_EVENT_HEADER(struct ALLEGRO_MOUSE)
@@ -210,6 +221,7 @@
    ALLEGRO_DISPLAY_EVENT  display;
    ALLEGRO_JOYSTICK_EVENT joystick;
    ALLEGRO_KEYBOARD_EVENT keyboard;
+   ALLEGRO_MENU_EVENT     menu;
    ALLEGRO_MOUSE_EVENT    mouse;
    ALLEGRO_TIMER_EVENT    timer;
    ALLEGRO_TOUCH_EVENT    touch;
Index: src/win/wwindow.c
===================================================================
--- src/win/wwindow.c	(revision 14805)
+++ src/win/wwindow.c	(working copy)
@@ -431,6 +431,11 @@
       DestroyWindow(hWnd);
       return 0;
    }
+   
+   if (win_display->window_callback) {
+      LRESULT rc = win_display->window_callback(win_display, hWnd, message, wParam, lParam);
+      if (rc != FALSE) return rc;
+   }
 
    switch (message) {
       case WM_INPUT:
Index: addons/native_dialog/dialog.c
===================================================================
--- addons/native_dialog/dialog.c	(revision 14805)
+++ addons/native_dialog/dialog.c	(working copy)
@@ -115,7 +115,75 @@
    return r;
 }
 
+/* Function: al_create_menu 
+ */
+ALLEGRO_MENU *al_create_menu()
+{
+   ALLEGRO_MENU *menu = al_malloc(sizeof(*menu));
+   
+   if (menu) {
+      memset(menu, 0, sizeof(*menu));
+      if (!_al_create_menu(menu)) {
+         al_free(menu);
+         menu = NULL;
+      }
+   }
+   
+   return menu;
+}
 
+bool al_append_menu(ALLEGRO_MENU *parent, ALLEGRO_MENU *popup,
+   const char *caption, int id)
+{
+   ALLEGRO_MENU_ITEM *item;
+	
+   ASSERT(parent);
+   
+   item = al_malloc(sizeof(*item));
+   if (!item) {
+      return false;
+   }
+   memset(item, 0, sizeof(*item));
+   
+   item->caption = al_ustr_new(caption);
+   item->id = id;
+   item->popup = popup;
+   
+   parent->item_count++;
+   parent->items = al_realloc(parent->items, sizeof(ALLEGRO_MENU_ITEM) * parent->item_count);
+   parent->items[parent->item_count - 1] = item;
+   
+   _al_append_menu(parent, item);
+   
+   return true;
+}
+
+void al_set_display_menu(ALLEGRO_DISPLAY *display, ALLEGRO_MENU *menu)
+{
+   ASSERT(display);
+   _al_set_display_menu(display, menu);
+}
+
+void _al_emit_menu_event(ALLEGRO_DISPLAY *display, int id)
+{
+   ALLEGRO_EVENT_SOURCE *es;
+   
+   ASSERT(display);
+   
+   es = &display->es;
+   
+   _al_event_source_lock(es);
+   if (_al_event_source_needs_to_generate_event(es)) {
+      ALLEGRO_EVENT event;
+      event.menu.type = ALLEGRO_EVENT_MENU_CLICK;
+      event.menu.timestamp = al_get_time();
+      event.menu.display = display;
+      event.menu.id = id;
+      _al_event_source_emit_event(es, &event);
+   }
+   _al_event_source_unlock(es);
+}
+
 /* Function: al_get_allegro_native_dialog_version
  */
 uint32_t al_get_allegro_native_dialog_version(void)
Index: addons/native_dialog/allegro5/allegro_native_dialog.h
===================================================================
--- addons/native_dialog/allegro5/allegro_native_dialog.h	(revision 14805)
+++ addons/native_dialog/allegro5/allegro_native_dialog.h	(working copy)
@@ -37,6 +37,10 @@
  */
 typedef struct ALLEGRO_TEXTLOG ALLEGRO_TEXTLOG;
 
+/* Type: ALLEGRO_MENU
+ */
+typedef struct ALLEGRO_MENU ALLEGRO_MENU;
+
 ALLEGRO_DIALOG_FUNC(ALLEGRO_FILECHOOSER *, al_create_native_file_dialog, (char const *initial_path,
    char const *title, char const *patterns, int mode));
 ALLEGRO_DIALOG_FUNC(bool, al_show_native_file_dialog, (ALLEGRO_DISPLAY *display, ALLEGRO_FILECHOOSER *dialog));
@@ -53,6 +57,10 @@
 ALLEGRO_DIALOG_FUNC(void, al_append_native_text_log, (ALLEGRO_TEXTLOG *textlog, char const *format, ...));
 ALLEGRO_DIALOG_FUNC(ALLEGRO_EVENT_SOURCE *, al_get_native_text_log_event_source, (ALLEGRO_TEXTLOG *textlog));
 
+ALLEGRO_DIALOG_FUNC(ALLEGRO_MENU *, al_create_menu, (void));
+ALLEGRO_DIALOG_FUNC(bool, al_append_menu, (ALLEGRO_MENU *parent, ALLEGRO_MENU *child, char const *title, int id));
+ALLEGRO_DIALOG_FUNC(void, al_set_display_menu, (ALLEGRO_DISPLAY *display, ALLEGRO_MENU *menu));
+
 ALLEGRO_DIALOG_FUNC(uint32_t, al_get_allegro_native_dialog_version, (void));
 
 enum {
Index: addons/native_dialog/allegro5/internal/aintern_native_dialog.h
===================================================================
--- addons/native_dialog/allegro5/internal/aintern_native_dialog.h	(revision 14805)
+++ addons/native_dialog/allegro5/internal/aintern_native_dialog.h	(working copy)
@@ -2,6 +2,7 @@
 #define __al_included_allegro_aintern_native_dialog_h
 
 typedef struct ALLEGRO_NATIVE_DIALOG ALLEGRO_NATIVE_DIALOG;
+typedef struct ALLEGRO_MENU_ITEM ALLEGRO_MENU_ITEM;
 
 /* We could use different structs for the different dialogs. But why
  * bother.
@@ -40,6 +41,23 @@
    void *async_queue;
 };
 
+struct ALLEGRO_MENU_ITEM
+{
+   ALLEGRO_MENU *popup;
+   ALLEGRO_USTR *caption;
+   int id;
+	 	 
+	 void *handle;
+};
+
+struct ALLEGRO_MENU
+{
+   ALLEGRO_MENU_ITEM **items;
+   int item_count;
+   
+   void *handle;
+};
+
 extern bool _al_show_native_file_dialog(ALLEGRO_DISPLAY *display,
    ALLEGRO_NATIVE_DIALOG *fd);
 extern int _al_show_native_message_box(ALLEGRO_DISPLAY *display,
@@ -48,4 +66,9 @@
 extern void _al_close_native_text_log(ALLEGRO_NATIVE_DIALOG *textlog);
 extern void _al_append_native_text_log(ALLEGRO_NATIVE_DIALOG *textlog);
 
+extern void _al_emit_menu_event(ALLEGRO_DISPLAY *display, int id);
+extern bool _al_create_menu(ALLEGRO_MENU *menu);
+extern bool _al_append_menu(ALLEGRO_MENU *menu, ALLEGRO_MENU_ITEM *item);
+extern bool _al_set_display_menu(ALLEGRO_DISPLAY *display, ALLEGRO_MENU *menu);
+
 #endif
Index: addons/native_dialog/win_dialog.c
===================================================================
--- addons/native_dialog/win_dialog.c	(revision 14805)
+++ addons/native_dialog/win_dialog.c	(working copy)
@@ -602,4 +602,60 @@
    }
 }
 
+bool _al_create_menu(ALLEGRO_MENU *menu)
+{
+   menu->handle = CreateMenu();
+   return true;
+}
+
+bool _al_append_menu(ALLEGRO_MENU *menu, ALLEGRO_MENU_ITEM *item)
+{
+   ASSERT(menu);
+   ASSERT(menu->handle);
+   ASSERT(item);
+   
+   if (item->popup) {
+      AppendMenu(menu->handle, MF_POPUP, (UINT_PTR) item->popup->handle, al_cstr(item->caption));
+   }
+   else if (item->caption == NULL) {
+      AppendMenu(menu->handle, MF_SEPARATOR, 0, NULL);
+   }
+   else {
+      AppendMenu(menu->handle, MF_STRING, item->id, al_cstr(item->caption));
+   }
+   
+   return true;
+}
+
+static LRESULT CALLBACK window_callback(ALLEGRO_DISPLAY_WIN *win_display,
+   HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
+{
+   if (message == WM_COMMAND && lParam == 0) {
+      _al_emit_menu_event((ALLEGRO_DISPLAY *) win_display, LOWORD(wParam));
+   }
+   return FALSE;
+}
+
+bool _al_set_display_menu(ALLEGRO_DISPLAY *display, ALLEGRO_MENU *menu)
+{
+   ALLEGRO_DISPLAY_WIN *win_display = (ALLEGRO_DISPLAY_WIN *)display;
+   ASSERT(display);
+
+   if (menu) {
+      HWND h; 
+      
+      ASSERT(menu->handle);
+      
+      win_display->window_callback = window_callback;
+      
+      h = al_get_win_window_handle(display);
+      if (!h) {
+         return false;
+      }
+      SetMenu(h, menu->handle);
+   }
+   
+   return true;
+}
+
 /* vim: set sts=3 sw=3 et: */
Index: examples/ex_native_filechooser.c
===================================================================
--- examples/ex_native_filechooser.c	(revision 14805)
+++ examples/ex_native_filechooser.c	(working copy)
@@ -12,6 +12,7 @@
 #include <allegro5/allegro_font.h>
 #include <allegro5/allegro_image.h>
 #include <allegro5/allegro_color.h>
+#include <allegro5/allegro_opengl.h>
 
 #include "common.c"
 
@@ -19,6 +20,7 @@
 #define ASYNC_DIALOG_EVENT1   ALLEGRO_GET_EVENT_TYPE('e', 'N', 'F', '1')
 #define ASYNC_DIALOG_EVENT2   ALLEGRO_GET_EVENT_TYPE('e', 'N', 'F', '2')
 
+#define MENU_EXIT_ID 1
 
 typedef struct
 {
@@ -159,6 +161,7 @@
    ALLEGRO_EVENT_QUEUE *queue;
    ALLEGRO_FONT *font;
    ALLEGRO_COLOR background, active, inactive, info;
+   ALLEGRO_MENU *menu, *file_menu;
    AsyncDialog *old_dialog = NULL;
    AsyncDialog *cur_dialog = NULL;
    AsyncDialog *message_box = NULL;
@@ -187,6 +190,8 @@
 
    message("Creating 640x480 window...");
 
+   al_set_new_display_flags(ALLEGRO_OPENGL);
+
    display = al_create_display(640, 480);
    if (!display) {
       message("failure.\n");
@@ -194,6 +199,14 @@
       return 1;
    }
    message("success.\n");
+   
+   menu = al_create_menu();
+   file_menu = al_create_menu();
+   if (menu && file_menu) {
+      al_append_menu(file_menu, NULL, "E&xit", MENU_EXIT_ID);
+      al_append_menu(menu, file_menu, "&File", 0);
+      al_set_display_menu(display, menu);
+   }
 
    message("Loading font '%s'...", "data/fixed_font.tga");
    font = al_load_font("data/fixed_font.tga", 0, 0);
@@ -230,6 +243,12 @@
          if (event.keyboard.keycode == ALLEGRO_KEY_ESCAPE && !cur_dialog)
             break;
       }
+      
+      if (event.type == ALLEGRO_EVENT_MENU_CLICK) {
+         if (event.menu.id == MENU_EXIT_ID) {
+            break;
+         }
+      }
 
       /* When a mouse button is pressed, and no native dialog is
        * shown already, we show a new one.


Mail converted by MHonArc 2.6.19+ http://listengine.tuxfamily.org/