[ 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.