| @@ -1,492 +0,0 @@ | |||
| /* | |||
| Copyright 2012-2014 David Robillard <http://drobilla.net> | |||
| Copyright 2012-2019 Filipe Coelho <falktx@falktx.com> | |||
| Permission to use, copy, modify, and/or distribute this software for any | |||
| purpose with or without fee is hereby granted, provided that the above | |||
| copyright notice and this permission notice appear in all copies. | |||
| THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES | |||
| WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF | |||
| MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR | |||
| ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES | |||
| WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN | |||
| ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF | |||
| OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. | |||
| */ | |||
| /** | |||
| @file pugl.h API for Pugl, a minimal portable API for OpenGL. | |||
| */ | |||
| #ifndef PUGL_H_INCLUDED | |||
| #define PUGL_H_INCLUDED | |||
| #include <stdint.h> | |||
| /* | |||
| This API is pure portable C and contains no platform specific elements, or | |||
| even a GL dependency. However, unfortunately GL includes vary across | |||
| platforms so they are included here to allow for pure portable programs. | |||
| */ | |||
| #ifdef __APPLE__ | |||
| # include <OpenGL/gl.h> | |||
| #else | |||
| # ifdef _WIN32 | |||
| # include <winsock2.h> | |||
| # include <windows.h> /* Broken Windows GL headers require this */ | |||
| # endif | |||
| # include <GL/gl.h> | |||
| #endif | |||
| #ifdef __cplusplus | |||
| extern "C" { | |||
| #else | |||
| # include <stdbool.h> | |||
| #endif | |||
| /** | |||
| @defgroup pugl Pugl | |||
| A minimal portable API for OpenGL. | |||
| @{ | |||
| */ | |||
| /** | |||
| A Pugl view. | |||
| */ | |||
| typedef struct PuglViewImpl PuglView; | |||
| /** | |||
| A native window handle. | |||
| On X11, this is a Window. | |||
| On OSX, this is an NSView*. | |||
| On Windows, this is a HWND. | |||
| */ | |||
| typedef intptr_t PuglNativeWindow; | |||
| /** | |||
| Return status code. | |||
| */ | |||
| typedef enum { | |||
| PUGL_SUCCESS = 0 | |||
| } PuglStatus; | |||
| /** | |||
| Convenience symbols for ASCII control characters. | |||
| */ | |||
| typedef enum { | |||
| PUGL_CHAR_BACKSPACE = 0x08, | |||
| PUGL_CHAR_ESCAPE = 0x1B, | |||
| PUGL_CHAR_DELETE = 0x7F | |||
| } PuglChar; | |||
| /** | |||
| Special (non-Unicode) keyboard keys. | |||
| */ | |||
| typedef enum { | |||
| PUGL_KEY_F1 = 1, | |||
| PUGL_KEY_F2, | |||
| PUGL_KEY_F3, | |||
| PUGL_KEY_F4, | |||
| PUGL_KEY_F5, | |||
| PUGL_KEY_F6, | |||
| PUGL_KEY_F7, | |||
| PUGL_KEY_F8, | |||
| PUGL_KEY_F9, | |||
| PUGL_KEY_F10, | |||
| PUGL_KEY_F11, | |||
| PUGL_KEY_F12, | |||
| PUGL_KEY_LEFT, | |||
| PUGL_KEY_UP, | |||
| PUGL_KEY_RIGHT, | |||
| PUGL_KEY_DOWN, | |||
| PUGL_KEY_PAGE_UP, | |||
| PUGL_KEY_PAGE_DOWN, | |||
| PUGL_KEY_HOME, | |||
| PUGL_KEY_END, | |||
| PUGL_KEY_INSERT, | |||
| PUGL_KEY_SHIFT, | |||
| PUGL_KEY_CTRL, | |||
| PUGL_KEY_ALT, | |||
| PUGL_KEY_SUPER | |||
| } PuglKey; | |||
| /** | |||
| Keyboard modifier flags. | |||
| */ | |||
| typedef enum { | |||
| PUGL_MOD_SHIFT = 1 << 0, /**< Shift key */ | |||
| PUGL_MOD_CTRL = 1 << 1, /**< Control key */ | |||
| PUGL_MOD_ALT = 1 << 2, /**< Alt/Option key */ | |||
| PUGL_MOD_SUPER = 1 << 3 /**< Mod4/Command/Windows key */ | |||
| } PuglMod; | |||
| /** | |||
| Handle for opaque user data. | |||
| */ | |||
| typedef void* PuglHandle; | |||
| /** | |||
| A function called when the window is closed. | |||
| */ | |||
| typedef void (*PuglCloseFunc)(PuglView* view); | |||
| /** | |||
| A function called to draw the view contents with OpenGL. | |||
| */ | |||
| typedef void (*PuglDisplayFunc)(PuglView* view); | |||
| /** | |||
| A function called when a key is pressed or released. | |||
| @param view The view the event occured in. | |||
| @param press True if the key was pressed, false if released. | |||
| @param key Unicode point of the key pressed. | |||
| @return 0 if event was handled, otherwise send event to parent window. | |||
| */ | |||
| typedef int (*PuglKeyboardFunc)(PuglView* view, bool press, uint32_t key); | |||
| /** | |||
| A function called when the pointer moves. | |||
| @param view The view the event occured in. | |||
| @param x The window-relative x coordinate of the pointer. | |||
| @param y The window-relative y coordinate of the pointer. | |||
| */ | |||
| typedef void (*PuglMotionFunc)(PuglView* view, int x, int y); | |||
| /** | |||
| A function called when a mouse button is pressed or released. | |||
| @param view The view the event occured in. | |||
| @param button The button number (1 = left, 2 = middle, 3 = right). | |||
| @param press True if the key was pressed, false if released. | |||
| @param x The window-relative x coordinate of the pointer. | |||
| @param y The window-relative y coordinate of the pointer. | |||
| */ | |||
| typedef void (*PuglMouseFunc)( | |||
| PuglView* view, int button, bool press, int x, int y); | |||
| /** | |||
| A function called when the view is resized. | |||
| @param view The view being resized. | |||
| @param width The new view width. | |||
| @param height The new view height. | |||
| */ | |||
| typedef void (*PuglReshapeFunc)(PuglView* view, int width, int height); | |||
| /** | |||
| A function called outside of gl-context when the plugin schedules a resize via puglPostResize. | |||
| @param view The view being resized. | |||
| @param width The new width to resize to (variable is initialized to current size) | |||
| @param height The new height to resize to (variable is initialized to current size) | |||
| @param set_hints If not null, set window-hints | |||
| */ | |||
| typedef void (*PuglResizeFunc)(PuglView* view, int *width, int *height, int *set_hints); | |||
| /** | |||
| A function called on scrolling (e.g. mouse wheel or track pad). | |||
| The distances used here are in "lines", a single tick of a clicking mouse | |||
| wheel. For example, @p dy = 1.0 scrolls 1 line up. Some systems and | |||
| devices support finer resolution and/or higher values for fast scrolls, | |||
| so programs should handle any value gracefully. | |||
| @param view The view being scrolled. | |||
| @param x The window-relative x coordinate of the pointer. | |||
| @param y The window-relative y coordinate of the pointer. | |||
| @param dx The scroll x distance. | |||
| @param dx The scroll y distance. | |||
| */ | |||
| typedef void (*PuglScrollFunc)(PuglView* view, int x, int y, float dx, float dy); | |||
| /** | |||
| A function called when a special key is pressed or released. | |||
| This callback allows the use of keys that do not have unicode points. | |||
| Note that some are non-printable keys. | |||
| @param view The view the event occured in. | |||
| @param press True if the key was pressed, false if released. | |||
| @param key The key pressed. | |||
| @return 0 if event was handled, otherwise send event to parent window. | |||
| */ | |||
| typedef int (*PuglSpecialFunc)(PuglView* view, bool press, PuglKey key); | |||
| /** | |||
| A function called when a filename is selected via file-browser. | |||
| @param view The view the event occured in. | |||
| @param filename The selected file name or NULL if the dialog was canceled. | |||
| */ | |||
| typedef void (*PuglFileSelectedFunc)(PuglView* view, const char* filename); | |||
| /** | |||
| @name Initialization | |||
| Configuration functions which must be called before creating a window. | |||
| @{ | |||
| */ | |||
| /** | |||
| Create a Pugl context. | |||
| To create a window, call the various puglInit* functions as necessary, then | |||
| call puglCreateWindow(). | |||
| */ | |||
| PuglView* | |||
| puglInit(void); | |||
| /** | |||
| Set the parent window before creating a window (for embedding). | |||
| */ | |||
| void | |||
| puglInitWindowParent(PuglView* view, PuglNativeWindow parent); | |||
| /** | |||
| Set the window size before creating a window. | |||
| */ | |||
| void | |||
| puglInitWindowSize(PuglView* view, int width, int height); | |||
| /** | |||
| Set the minimum window size before creating a window. | |||
| */ | |||
| void | |||
| puglInitWindowMinSize(PuglView* view, int width, int height); | |||
| /** | |||
| Enable or disable resizing before creating a window. | |||
| */ | |||
| void | |||
| puglInitUserResizable(PuglView* view, bool resizable); | |||
| /** | |||
| Set transient parent before creating a window. | |||
| On X11, parent_id must be a Window. | |||
| On OSX, parent_id must be an NSView*. | |||
| */ | |||
| void | |||
| puglInitTransientFor(PuglView* view, uintptr_t parent); | |||
| /** | |||
| @} | |||
| */ | |||
| /** | |||
| @name Windows | |||
| Window management functions. | |||
| @{ | |||
| */ | |||
| /** | |||
| Create a window with the settings given by the various puglInit functions. | |||
| @return 1 (pugl does not currently support multiple windows). | |||
| */ | |||
| int | |||
| puglCreateWindow(PuglView* view, const char* title); | |||
| /** | |||
| Create a new GL window. | |||
| @param parent Parent window, or 0 for top level. | |||
| @param title Window title, or NULL. | |||
| @param width Window width in pixels. | |||
| @param height Window height in pixels. | |||
| @param resizable Whether window should be user resizable. | |||
| */ | |||
| PuglView* | |||
| puglCreate(PuglNativeWindow parent, | |||
| const char* title, | |||
| int min_width, | |||
| int min_height, | |||
| int width, | |||
| int height, | |||
| bool resizable, | |||
| unsigned long transientId); | |||
| /** | |||
| Show Window (external ui) | |||
| */ | |||
| void | |||
| puglShowWindow(PuglView* view); | |||
| /** | |||
| Hide Window (external ui) | |||
| */ | |||
| void | |||
| puglHideWindow(PuglView* view); | |||
| /** | |||
| Return the native window handle. | |||
| */ | |||
| PuglNativeWindow | |||
| puglGetNativeWindow(PuglView* view); | |||
| /** | |||
| @} | |||
| */ | |||
| /** | |||
| Set the handle to be passed to all callbacks. | |||
| This is generally a pointer to a struct which contains all necessary state. | |||
| Everything needed in callbacks should be here, not in static variables. | |||
| Note the lack of this facility makes GLUT unsuitable for plugins or | |||
| non-trivial programs; this mistake is largely why Pugl exists. | |||
| */ | |||
| void | |||
| puglSetHandle(PuglView* view, PuglHandle handle); | |||
| /** | |||
| Get the handle to be passed to all callbacks. | |||
| */ | |||
| PuglHandle | |||
| puglGetHandle(PuglView* view); | |||
| /** | |||
| Get the drawing context. | |||
| For Cairo contexts, this returns a pointer to a cairo_t. | |||
| For everything else, this is unused and returns NULL. | |||
| */ | |||
| void* | |||
| puglGetContext(PuglView* view); | |||
| /** | |||
| Return the timestamp (if any) of the currently-processing event. | |||
| */ | |||
| uint32_t | |||
| puglGetEventTimestamp(PuglView* view); | |||
| /** | |||
| Get the currently active modifiers (PuglMod flags). | |||
| This should only be called from an event handler. | |||
| */ | |||
| int | |||
| puglGetModifiers(PuglView* view); | |||
| /** | |||
| Ignore synthetic repeated key events. | |||
| */ | |||
| void | |||
| puglIgnoreKeyRepeat(PuglView* view, bool ignore); | |||
| /** | |||
| @name Event Callbacks | |||
| Functions to set event callbacks for handling user input. | |||
| @{ | |||
| */ | |||
| /** | |||
| Set the function to call when the window is closed. | |||
| */ | |||
| void | |||
| puglSetCloseFunc(PuglView* view, PuglCloseFunc closeFunc); | |||
| /** | |||
| Set the display function which should draw the UI using GL. | |||
| */ | |||
| void | |||
| puglSetDisplayFunc(PuglView* view, PuglDisplayFunc displayFunc); | |||
| /** | |||
| Set the function to call on keyboard events. | |||
| */ | |||
| void | |||
| puglSetKeyboardFunc(PuglView* view, PuglKeyboardFunc keyboardFunc); | |||
| /** | |||
| Set the function to call on mouse motion. | |||
| */ | |||
| void | |||
| puglSetMotionFunc(PuglView* view, PuglMotionFunc motionFunc); | |||
| /** | |||
| Set the function to call on mouse button events. | |||
| */ | |||
| void | |||
| puglSetMouseFunc(PuglView* view, PuglMouseFunc mouseFunc); | |||
| /** | |||
| Set the function to call on scroll events. | |||
| */ | |||
| void | |||
| puglSetScrollFunc(PuglView* view, PuglScrollFunc scrollFunc); | |||
| /** | |||
| Set the function to call on special events. | |||
| */ | |||
| void | |||
| puglSetSpecialFunc(PuglView* view, PuglSpecialFunc specialFunc); | |||
| /** | |||
| Set the function to call when the window size changes. | |||
| */ | |||
| void | |||
| puglSetReshapeFunc(PuglView* view, PuglReshapeFunc reshapeFunc); | |||
| /** | |||
| Set callback function to change window size. | |||
| */ | |||
| void | |||
| puglSetResizeFunc(PuglView* view, PuglResizeFunc resizeFunc); | |||
| /** | |||
| Set the function to call on file-browser selections. | |||
| */ | |||
| void | |||
| puglSetFileSelectedFunc(PuglView* view, PuglFileSelectedFunc fileSelectedFunc); | |||
| /** | |||
| @} | |||
| */ | |||
| /** | |||
| TODO document this. | |||
| */ | |||
| int | |||
| puglUpdateGeometryConstraints(PuglView* view, int min_width, int min_height, bool aspect); | |||
| /** | |||
| Grab the input focus. | |||
| */ | |||
| void | |||
| puglGrabFocus(PuglView* view); | |||
| /** | |||
| Process all pending window events. | |||
| This handles input events as well as rendering, so it should be called | |||
| regularly and rapidly enough to keep the UI responsive. | |||
| */ | |||
| PuglStatus | |||
| puglProcessEvents(PuglView* view); | |||
| /** | |||
| Request a redisplay on the next call to puglProcessEvents(). | |||
| */ | |||
| void | |||
| puglPostRedisplay(PuglView* view); | |||
| /** | |||
| Request a resize on the next call to puglProcessEvents(). | |||
| */ | |||
| void | |||
| puglPostResize(PuglView* view); | |||
| /** | |||
| Destroy a GL window. | |||
| */ | |||
| void | |||
| puglDestroy(PuglView* view); | |||
| /** | |||
| @} | |||
| */ | |||
| #ifdef __cplusplus | |||
| } /* extern "C" */ | |||
| #endif | |||
| #endif /* PUGL_H_INCLUDED */ | |||
| @@ -1,441 +0,0 @@ | |||
| /* | |||
| Copyright 2019 Filipe Coelho <falktx@falktx.com> | |||
| Permission to use, copy, modify, and/or distribute this software for any | |||
| purpose with or without fee is hereby granted, provided that the above | |||
| copyright notice and this permission notice appear in all copies. | |||
| THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES | |||
| WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF | |||
| MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR | |||
| ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES | |||
| WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN | |||
| ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF | |||
| OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. | |||
| */ | |||
| /** | |||
| @file pugl_haiku.cpp BeOS/HaikuOS Pugl Implementation. | |||
| */ | |||
| #include <Application.h> | |||
| #include <interface/Window.h> | |||
| #ifdef PUGL_CAIRO | |||
| #include <cairo/cairo.h> | |||
| typedef BView BViewType; | |||
| #endif | |||
| #ifdef PUGL_OPENGL | |||
| #include <GL/gl.h> | |||
| #include <opengl/GLView.h> | |||
| typedef BGLView BViewType; | |||
| #endif | |||
| #include "pugl_internal.h" | |||
| class DWindow; | |||
| struct PuglInternalsImpl { | |||
| BApplication* app; | |||
| BViewType* view; | |||
| DWindow* window; | |||
| }; | |||
| static void | |||
| puglReshape(PuglView* view, int width, int height) | |||
| { | |||
| puglEnterContext(view); | |||
| if (view->reshapeFunc) { | |||
| view->reshapeFunc(view, width, height); | |||
| } else { | |||
| puglDefaultReshape(width, height); | |||
| } | |||
| puglLeaveContext(view, false); | |||
| view->width = width; | |||
| view->height = height; | |||
| } | |||
| static void | |||
| puglDisplay(PuglView* view) | |||
| { | |||
| puglEnterContext(view); | |||
| view->redisplay = false; | |||
| if (view->displayFunc) { | |||
| view->displayFunc(view); | |||
| } | |||
| puglLeaveContext(view, true); | |||
| } | |||
| void | |||
| puglEnterContext(PuglView* view) | |||
| { | |||
| PuglInternals* impl = view->impl; | |||
| #ifdef PUGL_OPENGL | |||
| // FIXME without the first unlock we freeze | |||
| impl->view->UnlockGL(); | |||
| impl->view->LockGL(); | |||
| #endif | |||
| } | |||
| void | |||
| puglLeaveContext(PuglView* view, bool flush) | |||
| { | |||
| PuglInternals* impl = view->impl; | |||
| #ifdef PUGL_OPENGL | |||
| if (flush) | |||
| impl->view->SwapBuffers(); | |||
| impl->view->UnlockGL(); | |||
| #endif | |||
| } | |||
| PuglInternals* | |||
| puglInitInternals() | |||
| { | |||
| return (PuglInternals*)calloc(1, sizeof(PuglInternals)); | |||
| } | |||
| class DView : public BViewType | |||
| { | |||
| public: | |||
| #ifdef PUGL_CAIRO | |||
| DView(PuglView* const v) | |||
| : BView(nullptr, | |||
| B_FULL_UPDATE_ON_RESIZE|B_WILL_DRAW|B_FRAME_EVENTS|B_NAVIGABLE|B_INPUT_METHOD_AWARE), | |||
| puglView(v) {} | |||
| #endif | |||
| #ifdef PUGL_OPENGL | |||
| DView(PuglView* const v) | |||
| : BGLView(BRect(), // causes "bitmap bounds is much too large: BRect(0.0, 0.0, 4294967296.0, 4294967296.0)" | |||
| "DPF-GLView", | |||
| 0x0, // resize mode | |||
| B_FULL_UPDATE_ON_RESIZE|B_WILL_DRAW|B_NAVIGABLE_JUMP|B_FRAME_EVENTS|B_NAVIGABLE|B_INPUT_METHOD_AWARE, | |||
| BGL_RGB|BGL_DOUBLE|BGL_ALPHA|BGL_DEPTH|BGL_STENCIL), | |||
| puglView(v) | |||
| { | |||
| } | |||
| #endif | |||
| protected: | |||
| void GetPreferredSize(float* width, float* height) override | |||
| { | |||
| d_stdout("%s %i", __func__, __LINE__); | |||
| if (width != nullptr) | |||
| *width = puglView->width; | |||
| if (height != nullptr) | |||
| *height = puglView->height; | |||
| d_stdout("%s %i", __func__, __LINE__); | |||
| } | |||
| void Draw(BRect updateRect) override | |||
| { | |||
| d_stdout("%s %i", __func__, __LINE__); | |||
| puglDisplay(puglView); | |||
| #ifdef PUGL_OPENGL | |||
| BGLView::Draw(updateRect); | |||
| d_stdout("%s %i", __func__, __LINE__); | |||
| #endif | |||
| } | |||
| void MessageReceived(BMessage* message) | |||
| { | |||
| d_stdout("MessageReceived %p", message); | |||
| BViewType::MessageReceived(message); | |||
| } | |||
| void MouseDown(BPoint where) override | |||
| { | |||
| if (puglView->mouseFunc) { | |||
| // puglView->event_timestamp_ms = GetMessageTime(); | |||
| d_stdout("MouseDown mask %u", EventMask()); | |||
| puglView->mouseFunc(puglView, 1, true, where.x, where.y); | |||
| SetMouseEventMask(B_POINTER_EVENTS, B_LOCK_WINDOW_FOCUS); | |||
| } | |||
| //BViewType::MouseDown(where); | |||
| } | |||
| void MouseUp(BPoint where) override | |||
| { | |||
| if (puglView->mouseFunc) { | |||
| d_stdout("MouseUp mask %u", EventMask()); | |||
| // puglView->event_timestamp_ms = GetMessageTime(); | |||
| puglView->mouseFunc(puglView, 1, false, where.x, where.y); | |||
| } | |||
| //BViewType::MouseUp(where); | |||
| } | |||
| void MouseMoved(BPoint where, uint32, const BMessage*) override | |||
| { | |||
| if (puglView->motionFunc) { | |||
| // puglView->event_timestamp_ms = GetMessageTime(); | |||
| puglView->motionFunc(puglView, where.x, where.y); | |||
| } | |||
| } | |||
| void KeyDown(const char* bytes, int32 numBytes) override | |||
| { | |||
| d_stdout("KeyDown %i", numBytes); | |||
| if (numBytes != 1) | |||
| return; // TODO | |||
| if (puglView->keyboardFunc) { | |||
| puglView->keyboardFunc(puglView, true, bytes[0]); | |||
| } | |||
| } | |||
| void KeyUp(const char* bytes, int32 numBytes) override | |||
| { | |||
| d_stdout("KeyUp %i", numBytes); | |||
| if (numBytes != 1) | |||
| return; // TODO | |||
| if (puglView->keyboardFunc) { | |||
| puglView->keyboardFunc(puglView, false, bytes[0]); | |||
| } | |||
| } | |||
| void ScrollTo(BPoint where) override | |||
| { | |||
| d_stdout("ScrollTo mask %u", EventMask()); | |||
| BViewType::ScrollTo(where); | |||
| } | |||
| void FrameResized(float newWidth, float newHeight) override | |||
| { | |||
| d_stdout("%s %i", __func__, __LINE__); | |||
| puglReshape(puglView, static_cast<int>(newWidth), static_cast<int>(newHeight)); | |||
| #ifdef PUGL_OPENGL | |||
| BGLView::FrameResized(newWidth, newHeight); | |||
| #endif | |||
| d_stdout("%s %i", __func__, __LINE__); | |||
| } | |||
| private: | |||
| PuglView* const puglView; | |||
| }; | |||
| class DWindow : public BWindow | |||
| { | |||
| public: | |||
| DWindow(PuglView* const v) | |||
| : BWindow(BRect(1.0f), "DPF-Window", B_TITLED_WINDOW, 0x0), | |||
| puglView(v), | |||
| needsQuit(true) | |||
| { | |||
| } | |||
| bool NeedsQuit() const | |||
| { | |||
| return needsQuit; | |||
| } | |||
| protected: | |||
| bool QuitRequested() override | |||
| { | |||
| d_stdout("%s %i", __func__, __LINE__); | |||
| if (puglView->closeFunc) { | |||
| puglView->closeFunc(puglView); | |||
| puglView->redisplay = false; | |||
| } | |||
| needsQuit = false; | |||
| d_stdout("%s %i", __func__, __LINE__); | |||
| return true; | |||
| } | |||
| private: | |||
| PuglView* const puglView; | |||
| bool needsQuit; | |||
| }; | |||
| int | |||
| puglCreateWindow(PuglView* view, const char* title) | |||
| { | |||
| PuglInternals* impl = view->impl; | |||
| if (be_app == nullptr) | |||
| { | |||
| d_stdout("creating app"); | |||
| status_t status; | |||
| BApplication* const app = new BApplication("application/x-vnd.dpf-application", &status); | |||
| if (status != B_OK) | |||
| { | |||
| d_stdout("app status error %u", status); | |||
| delete app; | |||
| return 1; | |||
| } | |||
| impl->app = app; | |||
| } | |||
| else | |||
| { | |||
| d_stdout("using existing app"); | |||
| } | |||
| if (view->parent == 0) { | |||
| impl->window = new DWindow(view); | |||
| impl->window->Lock(); | |||
| } | |||
| impl->view = new DView(view); | |||
| if (view->parent != 0) { | |||
| BView* const pview = (BView*)view->parent; | |||
| pview->AddChild(impl->view); | |||
| impl->view->LockGL(); | |||
| return 0; | |||
| } | |||
| if (title != nullptr) { | |||
| impl->window->SetTitle(title); | |||
| } | |||
| impl->window->AddChild(impl->view); | |||
| impl->view->LockGL(); | |||
| //puglEnterContext(view); | |||
| impl->window->Unlock(); | |||
| return 0; | |||
| } | |||
| void | |||
| puglShowWindow(PuglView* view) | |||
| { | |||
| PuglInternals* impl = view->impl; | |||
| if (impl->window != nullptr) | |||
| { | |||
| if (impl->window->LockLooper()) | |||
| { | |||
| impl->window->Show(); | |||
| impl->window->UnlockLooper(); | |||
| } | |||
| } | |||
| else | |||
| { | |||
| impl->view->Show(); | |||
| } | |||
| } | |||
| void | |||
| puglHideWindow(PuglView* view) | |||
| { | |||
| PuglInternals* impl = view->impl; | |||
| if (impl->window != nullptr) | |||
| { | |||
| if (impl->window->LockLooper()) | |||
| { | |||
| impl->window->Hide(); | |||
| impl->window->UnlockLooper(); | |||
| } | |||
| } | |||
| else | |||
| { | |||
| impl->view->Show(); | |||
| } | |||
| } | |||
| void | |||
| puglDestroy(PuglView* view) | |||
| { | |||
| PuglInternals* impl = view->impl; | |||
| if (impl->window != nullptr) | |||
| { | |||
| // impl->window->Lock(); | |||
| puglLeaveContext(view, false); | |||
| impl->window->RemoveChild(impl->view); | |||
| // impl->window->Unlock(); | |||
| if (impl->window->NeedsQuit()) | |||
| impl->window->Quit(); | |||
| } | |||
| delete impl->view; | |||
| impl->view = nullptr; | |||
| impl->window = nullptr; | |||
| if (impl->app != nullptr && impl->app->CountWindows() == 0) | |||
| { | |||
| d_stdout("deleting app"); | |||
| delete impl->app; | |||
| impl->app = nullptr; | |||
| } else | |||
| d_stdout("NOT deleting app"); | |||
| } | |||
| PuglStatus | |||
| puglProcessEvents(PuglView* view) | |||
| { | |||
| return PUGL_SUCCESS; | |||
| } | |||
| void | |||
| puglPostRedisplay(PuglView* view) | |||
| { | |||
| PuglInternals* impl = view->impl; | |||
| view->redisplay = true; | |||
| if (impl->window != nullptr) | |||
| { | |||
| if (impl->window->LockLooper()) | |||
| { | |||
| impl->view->Invalidate(); | |||
| impl->window->UnlockLooper(); | |||
| } | |||
| } | |||
| else | |||
| { | |||
| impl->view->Invalidate(); | |||
| } | |||
| } | |||
| PuglNativeWindow | |||
| puglGetNativeWindow(PuglView* view) | |||
| { | |||
| PuglInternals* impl = view->impl; | |||
| #ifdef PUGL_OPENGL | |||
| // return (PuglNativeWindow)impl->view->EmbeddedView(); | |||
| #endif | |||
| return (PuglNativeWindow)(BView*)impl->view; | |||
| } | |||
| void* | |||
| puglGetContext(PuglView* view) | |||
| { | |||
| return NULL; | |||
| } | |||
| int | |||
| puglUpdateGeometryConstraints(PuglView* view, int min_width, int min_height, bool aspect) | |||
| { | |||
| PuglInternals* impl = view->impl; | |||
| d_stdout("puglUpdateGeometryConstraints %i %i %i %i", min_width, min_height, view->width, view->height); | |||
| if (impl->window->LockLooper()) | |||
| { | |||
| impl->window->SetSizeLimits(min_width, | |||
| view->user_resizable ? 4096 : min_width, | |||
| min_height, | |||
| view->user_resizable ? 4096 : min_height); | |||
| impl->window->UnlockLooper(); | |||
| return 0; | |||
| } | |||
| return 1; | |||
| // TODO | |||
| (void)aspect; | |||
| } | |||
| @@ -1,263 +0,0 @@ | |||
| /* | |||
| Copyright 2012-2014 David Robillard <http://drobilla.net> | |||
| Copyright 2012-2019 Filipe Coelho <falktx@falktx.com> | |||
| Permission to use, copy, modify, and/or distribute this software for any | |||
| purpose with or without fee is hereby granted, provided that the above | |||
| copyright notice and this permission notice appear in all copies. | |||
| THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES | |||
| WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF | |||
| MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR | |||
| ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES | |||
| WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN | |||
| ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF | |||
| OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. | |||
| */ | |||
| /** | |||
| @file pugl_internal.h Private platform-independent definitions. | |||
| Note this file contains function definitions, so it must be compiled into | |||
| the final binary exactly once. Each platform specific implementation file | |||
| including it once should achieve this. | |||
| */ | |||
| #include "pugl.h" | |||
| typedef struct PuglInternalsImpl PuglInternals; | |||
| struct PuglViewImpl { | |||
| PuglHandle handle; | |||
| PuglCloseFunc closeFunc; | |||
| PuglDisplayFunc displayFunc; | |||
| PuglKeyboardFunc keyboardFunc; | |||
| PuglMotionFunc motionFunc; | |||
| PuglMouseFunc mouseFunc; | |||
| PuglReshapeFunc reshapeFunc; | |||
| PuglResizeFunc resizeFunc; | |||
| PuglScrollFunc scrollFunc; | |||
| PuglSpecialFunc specialFunc; | |||
| PuglFileSelectedFunc fileSelectedFunc; | |||
| PuglInternals* impl; | |||
| PuglNativeWindow parent; | |||
| uintptr_t transient_parent; | |||
| int width; | |||
| int height; | |||
| int min_width; | |||
| int min_height; | |||
| int mods; | |||
| bool mouse_in_view; | |||
| bool ignoreKeyRepeat; | |||
| bool redisplay; | |||
| bool user_resizable; | |||
| bool pending_resize; | |||
| uint32_t event_timestamp_ms; | |||
| }; | |||
| PuglInternals* puglInitInternals(void); | |||
| PuglView* | |||
| puglInit(void) | |||
| { | |||
| PuglView* view = (PuglView*)calloc(1, sizeof(PuglView)); | |||
| if (!view) { | |||
| return NULL; | |||
| } | |||
| PuglInternals* impl = puglInitInternals(); | |||
| if (!impl) { | |||
| free(view); | |||
| return NULL; | |||
| } | |||
| view->impl = impl; | |||
| view->width = 640; | |||
| view->height = 480; | |||
| return view; | |||
| } | |||
| void | |||
| puglInitWindowSize(PuglView* view, int width, int height) | |||
| { | |||
| view->width = width; | |||
| view->height = height; | |||
| } | |||
| void | |||
| puglInitWindowMinSize(PuglView* view, int width, int height) | |||
| { | |||
| view->min_width = width; | |||
| view->min_height = height; | |||
| } | |||
| void | |||
| puglInitWindowParent(PuglView* view, PuglNativeWindow parent) | |||
| { | |||
| view->parent = parent; | |||
| } | |||
| void | |||
| puglInitUserResizable(PuglView* view, bool resizable) | |||
| { | |||
| view->user_resizable = resizable; | |||
| } | |||
| void | |||
| puglInitTransientFor(PuglView* view, uintptr_t parent) | |||
| { | |||
| view->transient_parent = parent; | |||
| } | |||
| PuglView* | |||
| puglCreate(PuglNativeWindow parent, | |||
| const char* title, | |||
| int min_width, | |||
| int min_height, | |||
| int width, | |||
| int height, | |||
| bool resizable, | |||
| unsigned long transientId) | |||
| { | |||
| PuglView* view = puglInit(); | |||
| if (!view) { | |||
| return NULL; | |||
| } | |||
| puglInitWindowParent(view, parent); | |||
| puglInitWindowMinSize(view, min_width, min_height); | |||
| puglInitWindowSize(view, width, height); | |||
| puglInitUserResizable(view, resizable); | |||
| puglInitTransientFor(view, transientId); | |||
| if (!puglCreateWindow(view, title)) { | |||
| free(view); | |||
| return NULL; | |||
| } | |||
| return view; | |||
| } | |||
| void | |||
| puglSetHandle(PuglView* view, PuglHandle handle) | |||
| { | |||
| view->handle = handle; | |||
| } | |||
| PuglHandle | |||
| puglGetHandle(PuglView* view) | |||
| { | |||
| return view->handle; | |||
| } | |||
| uint32_t | |||
| puglGetEventTimestamp(PuglView* view) | |||
| { | |||
| return view->event_timestamp_ms; | |||
| } | |||
| int | |||
| puglGetModifiers(PuglView* view) | |||
| { | |||
| return view->mods; | |||
| } | |||
| void | |||
| puglIgnoreKeyRepeat(PuglView* view, bool ignore) | |||
| { | |||
| view->ignoreKeyRepeat = ignore; | |||
| } | |||
| void | |||
| puglSetCloseFunc(PuglView* view, PuglCloseFunc closeFunc) | |||
| { | |||
| view->closeFunc = closeFunc; | |||
| } | |||
| void | |||
| puglSetDisplayFunc(PuglView* view, PuglDisplayFunc displayFunc) | |||
| { | |||
| view->displayFunc = displayFunc; | |||
| } | |||
| void | |||
| puglSetKeyboardFunc(PuglView* view, PuglKeyboardFunc keyboardFunc) | |||
| { | |||
| view->keyboardFunc = keyboardFunc; | |||
| } | |||
| void | |||
| puglSetMotionFunc(PuglView* view, PuglMotionFunc motionFunc) | |||
| { | |||
| view->motionFunc = motionFunc; | |||
| } | |||
| void | |||
| puglSetMouseFunc(PuglView* view, PuglMouseFunc mouseFunc) | |||
| { | |||
| view->mouseFunc = mouseFunc; | |||
| } | |||
| void | |||
| puglSetReshapeFunc(PuglView* view, PuglReshapeFunc reshapeFunc) | |||
| { | |||
| view->reshapeFunc = reshapeFunc; | |||
| } | |||
| void | |||
| puglSetResizeFunc(PuglView* view, PuglResizeFunc resizeFunc) | |||
| { | |||
| view->resizeFunc = resizeFunc; | |||
| } | |||
| void | |||
| puglSetScrollFunc(PuglView* view, PuglScrollFunc scrollFunc) | |||
| { | |||
| view->scrollFunc = scrollFunc; | |||
| } | |||
| void | |||
| puglSetSpecialFunc(PuglView* view, PuglSpecialFunc specialFunc) | |||
| { | |||
| view->specialFunc = specialFunc; | |||
| } | |||
| void | |||
| puglSetFileSelectedFunc(PuglView* view, PuglFileSelectedFunc fileSelectedFunc) | |||
| { | |||
| view->fileSelectedFunc = fileSelectedFunc; | |||
| } | |||
| void | |||
| puglEnterContext(PuglView* view); | |||
| void | |||
| puglLeaveContext(PuglView* view, bool flush); | |||
| static void | |||
| puglDefaultReshape(int width, int height) | |||
| { | |||
| #ifdef PUGL_OPENGL | |||
| #ifdef ROBTK_HERE | |||
| glViewport(0, 0, width, height); | |||
| glMatrixMode(GL_PROJECTION); | |||
| glLoadIdentity(); | |||
| glOrtho(-1.0f, 1.0f, -1.0f, 1.0f, -1.0f, 1.0f); | |||
| glClear(GL_COLOR_BUFFER_BIT); | |||
| glMatrixMode(GL_MODELVIEW); | |||
| glLoadIdentity(); | |||
| #else | |||
| glMatrixMode(GL_PROJECTION); | |||
| glLoadIdentity(); | |||
| glOrtho(0, width, height, 0, 0, 1); | |||
| glViewport(0, 0, width, height); | |||
| glMatrixMode(GL_MODELVIEW); | |||
| glLoadIdentity(); | |||
| #endif | |||
| #endif // PUGL_OPENGL | |||
| } | |||
| @@ -1,974 +0,0 @@ | |||
| /* | |||
| Copyright 2012 David Robillard <http://drobilla.net> | |||
| Copyright 2012-2019 Filipe Coelho <falktx@falktx.com> | |||
| Permission to use, copy, modify, and/or distribute this software for any | |||
| purpose with or without fee is hereby granted, provided that the above | |||
| copyright notice and this permission notice appear in all copies. | |||
| THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES | |||
| WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF | |||
| MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR | |||
| ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES | |||
| WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN | |||
| ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF | |||
| OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. | |||
| */ | |||
| /** | |||
| @file pugl_osx.m OSX/Cocoa Pugl Implementation. | |||
| */ | |||
| #include <stdlib.h> | |||
| #ifdef PUGL_CAIRO | |||
| #import <cairo.h> | |||
| #import <cairo-quartz.h> | |||
| #endif | |||
| #import <Cocoa/Cocoa.h> | |||
| #include "pugl_internal.h" | |||
| @interface PuglWindow : NSWindow | |||
| { | |||
| @public | |||
| PuglView* puglview; | |||
| } | |||
| - (id) initWithContentRect:(NSRect)contentRect | |||
| styleMask:(unsigned int)aStyle | |||
| backing:(NSBackingStoreType)bufferingType | |||
| defer:(BOOL)flag; | |||
| - (void) setPuglview:(PuglView*)view; | |||
| - (BOOL) canBecomeKeyWindow; | |||
| - (BOOL) windowShouldClose:(id)sender; | |||
| @end | |||
| @implementation PuglWindow | |||
| - (id)initWithContentRect:(NSRect)contentRect | |||
| styleMask:(unsigned int)aStyle | |||
| backing:(NSBackingStoreType)bufferingType | |||
| defer:(BOOL)flag | |||
| { | |||
| NSWindow* result = [super initWithContentRect:contentRect | |||
| styleMask:(NSClosableWindowMask | | |||
| NSTitledWindowMask | | |||
| NSResizableWindowMask) | |||
| backing:NSBackingStoreBuffered defer:NO]; | |||
| [result setAcceptsMouseMovedEvents:YES]; | |||
| [result setLevel: CGShieldingWindowLevel() + 1]; | |||
| return (PuglWindow*)result; | |||
| // unused | |||
| (void)aStyle; (void)bufferingType; (void)flag; | |||
| } | |||
| - (void)setPuglview:(PuglView*)view | |||
| { | |||
| puglview = view; | |||
| [self setContentSize:NSMakeSize(view->width, view->height)]; | |||
| } | |||
| - (BOOL)canBecomeKeyWindow | |||
| { | |||
| return YES; | |||
| } | |||
| - (BOOL)windowShouldClose:(id)sender | |||
| { | |||
| if (puglview->closeFunc) | |||
| puglview->closeFunc(puglview); | |||
| return YES; | |||
| // unused | |||
| (void)sender; | |||
| } | |||
| @end | |||
| static void | |||
| puglDisplay(PuglView* view) | |||
| { | |||
| view->redisplay = false; | |||
| if (view->displayFunc) { | |||
| view->displayFunc(view); | |||
| } | |||
| } | |||
| @protocol PuglGenericView | |||
| @required | |||
| - (PuglView *) puglview; | |||
| - (void) setPuglview:(PuglView *)pv; | |||
| - (NSTrackingArea *) puglTrackingArea; | |||
| - (void) setPuglTrackingArea:(NSTrackingArea *)area; | |||
| @end | |||
| static unsigned | |||
| getModifiers(PuglView* view, NSEvent* ev) | |||
| { | |||
| const unsigned modifierFlags = [ev modifierFlags]; | |||
| view->event_timestamp_ms = fmod([ev timestamp] * 1000.0, UINT32_MAX); | |||
| unsigned mods = 0; | |||
| mods |= (modifierFlags & NSShiftKeyMask) ? PUGL_MOD_SHIFT : 0; | |||
| mods |= (modifierFlags & NSControlKeyMask) ? PUGL_MOD_CTRL : 0; | |||
| mods |= (modifierFlags & NSAlternateKeyMask) ? PUGL_MOD_ALT : 0; | |||
| mods |= (modifierFlags & NSCommandKeyMask) ? PUGL_MOD_SUPER : 0; | |||
| return mods; | |||
| } | |||
| static int | |||
| getFixedAppKitButton(NSInteger button) | |||
| { | |||
| switch (button) { | |||
| case 0: return 1; | |||
| case 1: return 3; | |||
| case 2: return 2; | |||
| default: return button; | |||
| } | |||
| } | |||
| static void | |||
| cursorUpdate(NSView<PuglGenericView> *self, NSEvent* event) | |||
| { | |||
| [[NSCursor arrowCursor] set]; | |||
| (void)self; | |||
| (void)event; | |||
| } | |||
| static void | |||
| updateTrackingAreas(NSView<PuglGenericView> *self) | |||
| { | |||
| static const int opts = NSTrackingMouseEnteredAndExited | |||
| | NSTrackingMouseMoved | |||
| | NSTrackingEnabledDuringMouseDrag | |||
| | NSTrackingInVisibleRect | |||
| | NSTrackingActiveAlways | |||
| | NSTrackingCursorUpdate; | |||
| NSTrackingArea *trackingArea = [self puglTrackingArea]; | |||
| if (trackingArea != nil) { | |||
| [self removeTrackingArea:trackingArea]; | |||
| [trackingArea release]; | |||
| } | |||
| trackingArea = [[NSTrackingArea alloc] initWithRect:[self bounds] | |||
| options:opts | |||
| owner:self | |||
| userInfo:nil]; | |||
| [self setPuglTrackingArea:trackingArea]; | |||
| [self addTrackingArea:trackingArea]; | |||
| } | |||
| static void | |||
| viewWillMoveToWindow(NSView<PuglGenericView> *self, NSWindow* newWindow) | |||
| { | |||
| if (newWindow != nil) { | |||
| [newWindow setAcceptsMouseMovedEvents:YES]; | |||
| [newWindow makeFirstResponder:self]; | |||
| } | |||
| } | |||
| static void | |||
| reshape(NSView<PuglGenericView> *self) | |||
| { | |||
| PuglView* puglview = [self puglview]; | |||
| NSRect bounds = [self bounds]; | |||
| int width = bounds.size.width; | |||
| int height = bounds.size.height; | |||
| puglEnterContext(puglview); | |||
| if (puglview->reshapeFunc) { | |||
| puglview->reshapeFunc(puglview, width, height); | |||
| } else { | |||
| puglDefaultReshape(width, height); | |||
| } | |||
| puglLeaveContext(puglview, false); | |||
| puglview->width = width; | |||
| puglview->height = height; | |||
| } | |||
| static void | |||
| mouseMoved(NSView<PuglGenericView> *self, NSEvent *event) | |||
| { | |||
| PuglView* puglview = [self puglview]; | |||
| if (puglview->motionFunc) { | |||
| NSPoint loc = [self convertPoint:[event locationInWindow] fromView:nil]; | |||
| puglview->mods = getModifiers(puglview, event); | |||
| puglview->motionFunc(puglview, loc.x, loc.y); | |||
| } | |||
| } | |||
| static void | |||
| mouseDown(NSView<PuglGenericView> *self, NSEvent *event) | |||
| { | |||
| PuglView* puglview = [self puglview]; | |||
| if (puglview->mouseFunc) { | |||
| NSPoint loc = [self convertPoint:[event locationInWindow] fromView:nil]; | |||
| puglview->mods = getModifiers(puglview, event); | |||
| puglview->mouseFunc(puglview, getFixedAppKitButton([event buttonNumber]), true, loc.x, loc.y); | |||
| } | |||
| } | |||
| static void | |||
| mouseUp(NSView<PuglGenericView> *self, NSEvent *event) | |||
| { | |||
| PuglView* puglview = [self puglview]; | |||
| if (puglview->mouseFunc) { | |||
| NSPoint loc = [self convertPoint:[event locationInWindow] fromView:nil]; | |||
| puglview->mods = getModifiers(puglview, event); | |||
| puglview->mouseFunc(puglview, getFixedAppKitButton([event buttonNumber]), false, loc.x, loc.y); | |||
| } | |||
| } | |||
| static void | |||
| scrollWheel(NSView<PuglGenericView> *self, NSEvent *event) | |||
| { | |||
| PuglView* puglview = [self puglview]; | |||
| if (puglview->scrollFunc) { | |||
| NSPoint loc = [self convertPoint:[event locationInWindow] fromView:nil]; | |||
| puglview->mods = getModifiers(puglview, event); | |||
| puglview->scrollFunc(puglview, | |||
| loc.x, loc.y, | |||
| [event deltaX], [event deltaY]); | |||
| } | |||
| } | |||
| static void | |||
| keyDown(NSView<PuglGenericView> *self, NSEvent *event) | |||
| { | |||
| PuglView* puglview = [self puglview]; | |||
| if (puglview->keyboardFunc && !(puglview->ignoreKeyRepeat && [event isARepeat])) { | |||
| NSString* chars = [event characters]; | |||
| puglview->mods = getModifiers(puglview, event); | |||
| puglview->keyboardFunc(puglview, true, [chars characterAtIndex:0]); | |||
| } | |||
| } | |||
| static void | |||
| keyUp(NSView<PuglGenericView> *self, NSEvent *event) | |||
| { | |||
| PuglView* puglview = [self puglview]; | |||
| if (puglview->keyboardFunc) { | |||
| NSString* chars = [event characters]; | |||
| puglview->mods = getModifiers(puglview, event); | |||
| puglview->keyboardFunc(puglview, false, [chars characterAtIndex:0]); | |||
| } | |||
| } | |||
| static void | |||
| flagsChanged(NSView<PuglGenericView> *self, NSEvent *event) | |||
| { | |||
| PuglView* puglview = [self puglview]; | |||
| if (puglview->specialFunc) { | |||
| const unsigned mods = getModifiers(puglview, event); | |||
| if ((mods & PUGL_MOD_SHIFT) != (puglview->mods & PUGL_MOD_SHIFT)) { | |||
| puglview->specialFunc(puglview, mods & PUGL_MOD_SHIFT, PUGL_KEY_SHIFT); | |||
| } else if ((mods & PUGL_MOD_CTRL) != (puglview->mods & PUGL_MOD_CTRL)) { | |||
| puglview->specialFunc(puglview, mods & PUGL_MOD_CTRL, PUGL_KEY_CTRL); | |||
| } else if ((mods & PUGL_MOD_ALT) != (puglview->mods & PUGL_MOD_ALT)) { | |||
| puglview->specialFunc(puglview, mods & PUGL_MOD_ALT, PUGL_KEY_ALT); | |||
| } else if ((mods & PUGL_MOD_SUPER) != (puglview->mods & PUGL_MOD_SUPER)) { | |||
| puglview->specialFunc(puglview, mods & PUGL_MOD_SUPER, PUGL_KEY_SUPER); | |||
| } | |||
| puglview->mods = mods; | |||
| } | |||
| } | |||
| #ifdef PUGL_OPENGL | |||
| @interface PuglOpenGLView : NSOpenGLView<PuglGenericView> | |||
| { | |||
| @public | |||
| PuglView* puglview; | |||
| NSTrackingArea* trackingArea; | |||
| bool doubleBuffered; | |||
| } | |||
| - (PuglView *) puglview; | |||
| - (void) setPuglview:(PuglView *)pv; | |||
| - (NSTrackingArea *) puglTrackingArea; | |||
| - (void) setPuglTrackingArea:(NSTrackingArea *)area; | |||
| - (BOOL) acceptsFirstMouse:(NSEvent*)e; | |||
| - (BOOL) acceptsFirstResponder; | |||
| - (BOOL) isFlipped; | |||
| - (BOOL) isOpaque; | |||
| - (BOOL) preservesContentInLiveResize; | |||
| - (id) initWithFrame:(NSRect)frame; | |||
| - (void) reshape; | |||
| - (void) drawRect:(NSRect)r; | |||
| - (void) cursorUpdate:(NSEvent*)e; | |||
| - (void) updateTrackingAreas; | |||
| - (void) viewWillMoveToWindow:(NSWindow*)newWindow; | |||
| - (void) mouseMoved:(NSEvent*)event; | |||
| - (void) mouseDragged:(NSEvent*)event; | |||
| - (void) rightMouseDragged:(NSEvent*)event; | |||
| - (void) otherMouseDragged:(NSEvent*)event; | |||
| - (void) mouseDown:(NSEvent*)event; | |||
| - (void) rightMouseDown:(NSEvent*)event; | |||
| - (void) otherMouseDown:(NSEvent*)event; | |||
| - (void) mouseUp:(NSEvent*)event; | |||
| - (void) rightMouseUp:(NSEvent*)event; | |||
| - (void) otherMouseUp:(NSEvent*)event; | |||
| - (void) scrollWheel:(NSEvent*)event; | |||
| - (void) keyDown:(NSEvent*)event; | |||
| - (void) keyUp:(NSEvent*)event; | |||
| - (void) flagsChanged:(NSEvent*)event; | |||
| - (void) resizeWithOldSuperviewSize:(NSSize)oldSize; | |||
| @end | |||
| @implementation PuglOpenGLView | |||
| - (PuglView *) puglview { | |||
| return self->puglview; | |||
| } | |||
| - (void) setPuglview:(PuglView *)pv { | |||
| self->puglview = pv; | |||
| } | |||
| - (NSTrackingArea *) puglTrackingArea { | |||
| return self->trackingArea; | |||
| } | |||
| - (void) setPuglTrackingArea:(NSTrackingArea *)area { | |||
| self->trackingArea = area; | |||
| } | |||
| - (BOOL) acceptsFirstMouse:(NSEvent*)e | |||
| { | |||
| return YES; | |||
| // unused | |||
| (void)e; | |||
| } | |||
| - (BOOL) acceptsFirstResponder | |||
| { | |||
| return YES; | |||
| } | |||
| - (BOOL) isFlipped | |||
| { | |||
| return YES; | |||
| } | |||
| - (BOOL) isOpaque | |||
| { | |||
| return YES; | |||
| } | |||
| - (BOOL) preservesContentInLiveResize | |||
| { | |||
| return NO; | |||
| } | |||
| - (id) initWithFrame:(NSRect)frame | |||
| { | |||
| puglview = nil; | |||
| trackingArea = nil; | |||
| doubleBuffered = true; | |||
| NSOpenGLPixelFormatAttribute pixelAttribs[] = { | |||
| NSOpenGLPFAColorSize, 24, | |||
| NSOpenGLPFAAlphaSize, 8, | |||
| NSOpenGLPFADepthSize, 16, | |||
| NSOpenGLPFADoubleBuffer, | |||
| NSOpenGLPFAAccelerated, | |||
| 0 | |||
| }; | |||
| NSOpenGLPixelFormat* pixelFormat = [[NSOpenGLPixelFormat alloc] | |||
| initWithAttributes:pixelAttribs]; | |||
| if (pixelFormat) { | |||
| self = [super initWithFrame:frame pixelFormat:pixelFormat]; | |||
| [pixelFormat release]; | |||
| printf("Is doubleBuffered? TRUE\n"); | |||
| } else { | |||
| self = [super initWithFrame:frame]; | |||
| doubleBuffered = false; | |||
| printf("Is doubleBuffered? FALSE\n"); | |||
| } | |||
| if (self) { | |||
| GLint swapInterval = 1; | |||
| [[self openGLContext] setValues:&swapInterval forParameter:NSOpenGLCPSwapInterval]; | |||
| [self reshape]; | |||
| } | |||
| return self; | |||
| } | |||
| - (void) reshape | |||
| { | |||
| if (!puglview) { | |||
| /* NOTE: Apparently reshape gets called when the GC gets around to | |||
| deleting the view (?), so we must have reset puglview to NULL when | |||
| this comes around. | |||
| */ | |||
| return; | |||
| } | |||
| [[self openGLContext] update]; | |||
| reshape(self); | |||
| } | |||
| - (void) drawRect:(NSRect)r | |||
| { | |||
| puglEnterContext(puglview); | |||
| puglDisplay(puglview); | |||
| puglLeaveContext(puglview, true); | |||
| // unused | |||
| return; (void)r; | |||
| } | |||
| - (void) cursorUpdate:(NSEvent*)e | |||
| { | |||
| cursorUpdate(self, e); | |||
| } | |||
| - (void) updateTrackingAreas | |||
| { | |||
| updateTrackingAreas(self); | |||
| [super updateTrackingAreas]; | |||
| } | |||
| - (void) viewWillMoveToWindow:(NSWindow*)newWindow | |||
| { | |||
| viewWillMoveToWindow(self, newWindow); | |||
| [super viewWillMoveToWindow:newWindow]; | |||
| } | |||
| - (void) mouseMoved:(NSEvent*)event | |||
| { | |||
| mouseMoved(self, event); | |||
| } | |||
| - (void) mouseDragged:(NSEvent*)event | |||
| { | |||
| mouseMoved(self, event); | |||
| } | |||
| - (void) rightMouseDragged:(NSEvent*)event | |||
| { | |||
| mouseMoved(self, event); | |||
| } | |||
| - (void) otherMouseDragged:(NSEvent*)event | |||
| { | |||
| mouseMoved(self, event); | |||
| } | |||
| - (void) mouseDown:(NSEvent*)event | |||
| { | |||
| mouseDown(self, event); | |||
| } | |||
| - (void) rightMouseDown:(NSEvent*)event | |||
| { | |||
| mouseDown(self, event); | |||
| } | |||
| - (void) otherMouseDown:(NSEvent*)event | |||
| { | |||
| mouseDown(self, event); | |||
| } | |||
| - (void) mouseUp:(NSEvent*)event | |||
| { | |||
| mouseUp(self, event); | |||
| } | |||
| - (void) rightMouseUp:(NSEvent*)event | |||
| { | |||
| mouseUp(self, event); | |||
| } | |||
| - (void) otherMouseUp:(NSEvent*)event | |||
| { | |||
| mouseUp(self, event); | |||
| } | |||
| - (void) scrollWheel:(NSEvent*)event | |||
| { | |||
| scrollWheel(self, event); | |||
| } | |||
| - (void) keyDown:(NSEvent*)event | |||
| { | |||
| keyDown(self, event); | |||
| } | |||
| - (void) keyUp:(NSEvent*)event | |||
| { | |||
| keyUp(self, event); | |||
| } | |||
| - (void) flagsChanged:(NSEvent*)event | |||
| { | |||
| flagsChanged(self, event); | |||
| } | |||
| - (void) resizeWithOldSuperviewSize:(NSSize)oldSize | |||
| { | |||
| PuglView *pv = self->puglview; | |||
| if (pv->width <= 1 && pv->height <= 1) | |||
| { | |||
| /* NOTE: if the view size was not initialized yet, don't perform an | |||
| autoresize; it fixes manual resizing in Reaper. | |||
| */ | |||
| return; | |||
| } | |||
| [super resizeWithOldSuperviewSize:oldSize]; | |||
| } | |||
| @end | |||
| #endif | |||
| #ifdef PUGL_CAIRO | |||
| @interface PuglCairoView : NSView<PuglGenericView> | |||
| { | |||
| PuglView* puglview; | |||
| cairo_t* cr; | |||
| NSTrackingArea* trackingArea; | |||
| } | |||
| - (PuglView *) puglview; | |||
| - (void) setPuglview:(PuglView *)pv; | |||
| - (NSTrackingArea *) puglTrackingArea; | |||
| - (void) setPuglTrackingArea:(NSTrackingArea *)area; | |||
| - (cairo_t *) cairoContext; | |||
| - (BOOL) acceptsFirstMouse:(NSEvent*)e; | |||
| - (BOOL) acceptsFirstResponder; | |||
| - (BOOL) isFlipped; | |||
| - (BOOL) isOpaque; | |||
| - (BOOL) preservesContentInLiveResize; | |||
| - (id) initWithFrame:(NSRect)frame; | |||
| - (void) reshape; | |||
| - (void) drawRect:(NSRect)r; | |||
| - (void) cursorUpdate:(NSEvent*)e; | |||
| - (void) updateTrackingAreas; | |||
| - (void) viewWillMoveToWindow:(NSWindow*)newWindow; | |||
| - (void) mouseMoved:(NSEvent*)event; | |||
| - (void) mouseDragged:(NSEvent*)event; | |||
| - (void) rightMouseDragged:(NSEvent*)event; | |||
| - (void) otherMouseDragged:(NSEvent*)event; | |||
| - (void) mouseDown:(NSEvent*)event; | |||
| - (void) rightMouseDown:(NSEvent*)event; | |||
| - (void) otherMouseDown:(NSEvent*)event; | |||
| - (void) mouseUp:(NSEvent*)event; | |||
| - (void) rightMouseUp:(NSEvent*)event; | |||
| - (void) otherMouseUp:(NSEvent*)event; | |||
| - (void) scrollWheel:(NSEvent*)event; | |||
| - (void) keyDown:(NSEvent*)event; | |||
| - (void) keyUp:(NSEvent*)event; | |||
| - (void) flagsChanged:(NSEvent*)event; | |||
| - (void) resizeWithOldSuperviewSize:(NSSize)oldSize; | |||
| @end | |||
| @implementation PuglCairoView | |||
| - (PuglView *) puglview { | |||
| return self->puglview; | |||
| } | |||
| - (void) setPuglview:(PuglView *)pv { | |||
| self->puglview = pv; | |||
| } | |||
| - (NSTrackingArea *) puglTrackingArea { | |||
| return self->trackingArea; | |||
| } | |||
| - (void) setPuglTrackingArea:(NSTrackingArea *)area { | |||
| self->trackingArea = area; | |||
| } | |||
| - (cairo_t *) cairoContext { | |||
| return cr; | |||
| } | |||
| - (BOOL) acceptsFirstMouse:(NSEvent*)e | |||
| { | |||
| return YES; | |||
| // unused | |||
| (void)e; | |||
| } | |||
| - (BOOL) acceptsFirstResponder | |||
| { | |||
| return YES; | |||
| } | |||
| - (BOOL) isFlipped | |||
| { | |||
| return YES; | |||
| } | |||
| - (BOOL) isOpaque | |||
| { | |||
| return YES; | |||
| } | |||
| - (BOOL) preservesContentInLiveResize | |||
| { | |||
| return NO; | |||
| } | |||
| - (id) initWithFrame:(NSRect)frame { | |||
| puglview = nil; | |||
| cr = NULL; | |||
| trackingArea = nil; | |||
| [super initWithFrame:frame]; | |||
| return self; | |||
| } | |||
| - (void) reshape | |||
| { | |||
| if (!puglview) { | |||
| /* NOTE: Apparently reshape gets called when the GC gets around to | |||
| deleting the view (?), so we must have reset puglview to NULL when | |||
| this comes around. | |||
| */ | |||
| return; | |||
| } | |||
| reshape(self); | |||
| } | |||
| - (void) drawRect:(NSRect)r { | |||
| CGContextRef ctx = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort]; | |||
| NSRect bounds = [self bounds]; | |||
| cairo_surface_t* surface; | |||
| cairo_t* cairo; | |||
| surface = cairo_quartz_surface_create_for_cg_context(ctx, bounds.size.width, bounds.size.height); | |||
| if (surface) { | |||
| cairo = cairo_create(surface); | |||
| if (cairo) { | |||
| self->cr = cairo; | |||
| puglEnterContext(puglview); | |||
| puglDisplay(puglview); | |||
| puglLeaveContext(puglview, true); | |||
| self->cr = NULL; | |||
| cairo_destroy(cairo); | |||
| } | |||
| cairo_surface_destroy(surface); | |||
| } | |||
| } | |||
| - (void) cursorUpdate:(NSEvent*)e | |||
| { | |||
| cursorUpdate(self, e); | |||
| } | |||
| - (void) updateTrackingAreas | |||
| { | |||
| updateTrackingAreas(self); | |||
| [super updateTrackingAreas]; | |||
| } | |||
| - (void) viewWillMoveToWindow:(NSWindow*)newWindow | |||
| { | |||
| viewWillMoveToWindow(self, newWindow); | |||
| [super viewWillMoveToWindow:newWindow]; | |||
| } | |||
| - (void) mouseMoved:(NSEvent*)event | |||
| { | |||
| mouseMoved(self, event); | |||
| } | |||
| - (void) mouseDragged:(NSEvent*)event | |||
| { | |||
| mouseMoved(self, event); | |||
| } | |||
| - (void) rightMouseDragged:(NSEvent*)event | |||
| { | |||
| mouseMoved(self, event); | |||
| } | |||
| - (void) otherMouseDragged:(NSEvent*)event | |||
| { | |||
| mouseMoved(self, event); | |||
| } | |||
| - (void) mouseDown:(NSEvent*)event | |||
| { | |||
| mouseDown(self, event); | |||
| } | |||
| - (void) rightMouseDown:(NSEvent*)event | |||
| { | |||
| mouseDown(self, event); | |||
| } | |||
| - (void) otherMouseDown:(NSEvent*)event | |||
| { | |||
| mouseDown(self, event); | |||
| } | |||
| - (void) mouseUp:(NSEvent*)event | |||
| { | |||
| mouseUp(self, event); | |||
| } | |||
| - (void) rightMouseUp:(NSEvent*)event | |||
| { | |||
| mouseUp(self, event); | |||
| } | |||
| - (void) otherMouseUp:(NSEvent*)event | |||
| { | |||
| mouseUp(self, event); | |||
| } | |||
| - (void) scrollWheel:(NSEvent*)event | |||
| { | |||
| scrollWheel(self, event); | |||
| } | |||
| - (void) keyDown:(NSEvent*)event | |||
| { | |||
| keyDown(self, event); | |||
| } | |||
| - (void) keyUp:(NSEvent*)event | |||
| { | |||
| keyUp(self, event); | |||
| } | |||
| - (void) flagsChanged:(NSEvent*)event | |||
| { | |||
| flagsChanged(self, event); | |||
| } | |||
| - (void) resizeWithOldSuperviewSize:(NSSize)oldSize | |||
| { | |||
| PuglView *pv = self->puglview; | |||
| if (pv->width <= 1 && pv->height <= 1) | |||
| { | |||
| /* NOTE: if the view size was not initialized yet, don't perform an | |||
| autoresize; it fixes manual resizing in Reaper. | |||
| */ | |||
| return; | |||
| } | |||
| [super resizeWithOldSuperviewSize:oldSize]; | |||
| } | |||
| @end | |||
| #endif | |||
| struct PuglInternalsImpl { | |||
| union { | |||
| NSView<PuglGenericView>* view; | |||
| #ifdef PUGL_OPENGL | |||
| PuglOpenGLView* glview; | |||
| #endif | |||
| #ifdef PUGL_CAIRO | |||
| PuglCairoView* cairoview; | |||
| #endif | |||
| }; | |||
| id window; | |||
| }; | |||
| PuglInternals* | |||
| puglInitInternals() | |||
| { | |||
| return (PuglInternals*)calloc(1, sizeof(PuglInternals)); | |||
| } | |||
| void | |||
| puglEnterContext(PuglView* view) | |||
| { | |||
| #ifdef PUGL_OPENGL | |||
| [[view->impl->glview openGLContext] makeCurrentContext]; | |||
| #endif | |||
| } | |||
| void | |||
| puglLeaveContext(PuglView* view, bool flush) | |||
| { | |||
| if (flush) { | |||
| #ifdef PUGL_OPENGL | |||
| if (view->impl->glview->doubleBuffered) { | |||
| [[view->impl->glview openGLContext] flushBuffer]; | |||
| } else { | |||
| glFlush(); | |||
| } | |||
| //[NSOpenGLContext clearCurrentContext]; | |||
| #endif | |||
| } | |||
| } | |||
| int | |||
| puglCreateWindow(PuglView* view, const char* title) | |||
| { | |||
| PuglInternals* impl = view->impl; | |||
| [NSAutoreleasePool new]; | |||
| [NSApplication sharedApplication]; | |||
| #ifdef PUGL_OPENGL | |||
| impl->glview = [PuglOpenGLView new]; | |||
| #endif | |||
| #ifdef PUGL_CAIRO | |||
| impl->cairoview = [PuglCairoView new]; | |||
| #endif | |||
| if (!impl->view) { | |||
| return 1; | |||
| } | |||
| [impl->view setPuglview:view]; | |||
| if (view->user_resizable) { | |||
| [impl->view setAutoresizingMask:NSViewWidthSizable|NSViewHeightSizable]; | |||
| } | |||
| if (view->parent) { | |||
| [impl->view retain]; | |||
| NSView* pview = (NSView*)view->parent; | |||
| [pview addSubview:impl->view]; | |||
| return 0; | |||
| } | |||
| id window = [[PuglWindow new]retain]; | |||
| if (title) { | |||
| NSString* titleString = [[NSString alloc] | |||
| initWithBytes:title | |||
| length:strlen(title) | |||
| encoding:NSUTF8StringEncoding]; | |||
| [window setTitle:titleString]; | |||
| } | |||
| [window setPuglview:view]; | |||
| [window setContentView:impl->view]; | |||
| [window makeFirstResponder:impl->view]; | |||
| [window makeKeyAndOrderFront:window]; | |||
| // wait for first puglShowWindow | |||
| [window setIsVisible:NO]; | |||
| [NSApp activateIgnoringOtherApps:YES]; | |||
| [window center]; | |||
| impl->window = window; | |||
| return 0; | |||
| } | |||
| void | |||
| puglShowWindow(PuglView* view) | |||
| { | |||
| PuglInternals* impl = view->impl; | |||
| if (impl->window) { | |||
| [impl->window setIsVisible:YES]; | |||
| } else { | |||
| [view->impl->view setHidden:NO]; | |||
| } | |||
| } | |||
| void | |||
| puglHideWindow(PuglView* view) | |||
| { | |||
| PuglInternals* impl = view->impl; | |||
| if (impl->window) { | |||
| [impl->window setIsVisible:NO]; | |||
| } else { | |||
| [impl->view setHidden:YES]; | |||
| } | |||
| } | |||
| void | |||
| puglDestroy(PuglView* view) | |||
| { | |||
| [view->impl->view setPuglview:NULL]; | |||
| if (view->impl->window) { | |||
| [view->impl->window close]; | |||
| [view->impl->view release]; | |||
| [view->impl->window release]; | |||
| } else { | |||
| [view->impl->view release]; | |||
| } | |||
| free(view->impl); | |||
| free(view); | |||
| } | |||
| PuglStatus | |||
| puglProcessEvents(PuglView* view) | |||
| { | |||
| return PUGL_SUCCESS; | |||
| // unused | |||
| (void)view; | |||
| } | |||
| void | |||
| puglPostRedisplay(PuglView* view) | |||
| { | |||
| view->redisplay = true; | |||
| [view->impl->view setNeedsDisplay:YES]; | |||
| } | |||
| PuglNativeWindow | |||
| puglGetNativeWindow(PuglView* view) | |||
| { | |||
| return (PuglNativeWindow)view->impl->view; | |||
| } | |||
| void* | |||
| puglGetContext(PuglView* view) | |||
| { | |||
| #ifdef PUGL_CAIRO | |||
| return [view->impl->cairoview cairoContext]; | |||
| #endif | |||
| return NULL; | |||
| // may be unused | |||
| (void)view; | |||
| } | |||
| int | |||
| puglUpdateGeometryConstraints(PuglView* view, int min_width, int min_height, bool aspect) | |||
| { | |||
| // TODO | |||
| return 1; | |||
| (void)view; | |||
| (void)min_width; | |||
| (void)min_height; | |||
| (void)aspect; | |||
| } | |||
| @@ -1,565 +0,0 @@ | |||
| /* | |||
| Copyright 2012-2014 David Robillard <http://drobilla.net> | |||
| Copyright 2012-2019 Filipe Coelho <falktx@falktx.com> | |||
| Permission to use, copy, modify, and/or distribute this software for any | |||
| purpose with or without fee is hereby granted, provided that the above | |||
| copyright notice and this permission notice appear in all copies. | |||
| THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES | |||
| WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF | |||
| MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR | |||
| ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES | |||
| WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN | |||
| ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF | |||
| OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. | |||
| */ | |||
| /** | |||
| @file pugl_win.cpp Windows/WGL Pugl Implementation. | |||
| */ | |||
| #include <ctime> | |||
| #include <cstdio> | |||
| #include <cstdlib> | |||
| #include <winsock2.h> | |||
| #include <windows.h> | |||
| #include <windowsx.h> | |||
| #ifdef PUGL_CAIRO | |||
| #include <cairo/cairo.h> | |||
| #include <cairo/cairo-win32.h> | |||
| #endif | |||
| #ifdef PUGL_OPENGL | |||
| #include <GL/gl.h> | |||
| #endif | |||
| #include "pugl_internal.h" | |||
| #ifndef WM_MOUSEWHEEL | |||
| # define WM_MOUSEWHEEL 0x020A | |||
| #endif | |||
| #ifndef WM_MOUSEHWHEEL | |||
| # define WM_MOUSEHWHEEL 0x020E | |||
| #endif | |||
| #ifndef WHEEL_DELTA | |||
| # define WHEEL_DELTA 120 | |||
| #endif | |||
| #ifndef GWLP_USERDATA | |||
| # define GWLP_USERDATA (-21) | |||
| #endif | |||
| #define PUGL_LOCAL_CLOSE_MSG (WM_USER + 50) | |||
| HINSTANCE hInstance = NULL; | |||
| struct PuglInternalsImpl { | |||
| HWND hwnd; | |||
| #ifdef PUGL_OPENGL | |||
| HDC hdc; | |||
| HGLRC hglrc; | |||
| #endif | |||
| #ifdef PUGL_CAIRO | |||
| cairo_t* buffer_cr; | |||
| cairo_surface_t* buffer_surface; | |||
| #endif | |||
| HDC paintHdc; | |||
| WNDCLASS wc; | |||
| }; | |||
| LRESULT CALLBACK | |||
| wndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam); | |||
| #if 0 | |||
| extern "C" { | |||
| BOOL WINAPI | |||
| DllMain(HINSTANCE hInst, DWORD, LPVOID) | |||
| { | |||
| hInstance = hInst; | |||
| return 1; | |||
| } | |||
| } // extern "C" | |||
| #endif | |||
| PuglInternals* | |||
| puglInitInternals() | |||
| { | |||
| return (PuglInternals*)calloc(1, sizeof(PuglInternals)); | |||
| } | |||
| void | |||
| puglEnterContext(PuglView* view) | |||
| { | |||
| #ifdef PUGL_OPENGL | |||
| wglMakeCurrent(view->impl->hdc, view->impl->hglrc); | |||
| #endif | |||
| } | |||
| void | |||
| puglLeaveContext(PuglView* view, bool flush) | |||
| { | |||
| #ifdef PUGL_OPENGL | |||
| if (flush) { | |||
| glFlush(); | |||
| SwapBuffers(view->impl->hdc); | |||
| } | |||
| wglMakeCurrent(NULL, NULL); | |||
| #endif | |||
| } | |||
| int | |||
| puglCreateWindow(PuglView* view, const char* title) | |||
| { | |||
| PuglInternals* impl = view->impl; | |||
| if (!title) { | |||
| title = "Window"; | |||
| } | |||
| // FIXME: This is nasty, and pugl should not have static anything. | |||
| // Should class be a parameter? Does this make sense on other platforms? | |||
| static int wc_count = 0; | |||
| char classNameBuf[256]; | |||
| std::srand((std::time(NULL))); | |||
| #ifdef __WINE__ | |||
| std::snprintf(classNameBuf, sizeof(classNameBuf), "%s_%d-%d", title, std::rand(), ++wc_count); | |||
| #else | |||
| _snprintf(classNameBuf, sizeof(classNameBuf), "%s_%d-%d", title, std::rand(), ++wc_count); | |||
| #endif | |||
| classNameBuf[sizeof(classNameBuf)-1] = '\0'; | |||
| impl->wc.style = CS_OWNDC; | |||
| impl->wc.lpfnWndProc = wndProc; | |||
| impl->wc.cbClsExtra = 0; | |||
| impl->wc.cbWndExtra = 0; | |||
| impl->wc.hInstance = hInstance; | |||
| impl->wc.hIcon = LoadIcon(hInstance, IDI_APPLICATION); | |||
| impl->wc.hCursor = LoadCursor(hInstance, IDC_ARROW); | |||
| impl->wc.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH); | |||
| impl->wc.lpszMenuName = NULL; | |||
| impl->wc.lpszClassName = strdup(classNameBuf); | |||
| if (!RegisterClass(&impl->wc)) { | |||
| free((void*)impl->wc.lpszClassName); | |||
| free(impl); | |||
| return 1; | |||
| } | |||
| int winFlags = WS_POPUPWINDOW | WS_CAPTION; | |||
| if (view->user_resizable) { | |||
| winFlags |= WS_SIZEBOX; | |||
| if (view->min_width > 0 && view->min_height > 0) { | |||
| // Adjust the minimum window size to accomodate requested view size | |||
| RECT mr = { 0, 0, view->min_width, view->min_height }; | |||
| AdjustWindowRectEx(&mr, view->parent ? WS_CHILD : winFlags, FALSE, WS_EX_TOPMOST); | |||
| view->min_width = mr.right - mr.left; | |||
| view->min_height = mr.bottom - mr.top; | |||
| } | |||
| } | |||
| // Adjust the window size to accomodate requested view size | |||
| RECT wr = { 0, 0, view->width, view->height }; | |||
| AdjustWindowRectEx(&wr, view->parent ? WS_CHILD : winFlags, FALSE, WS_EX_TOPMOST); | |||
| impl->hwnd = CreateWindowEx( | |||
| WS_EX_TOPMOST, | |||
| classNameBuf, title, | |||
| view->parent ? (WS_CHILD | WS_VISIBLE) : winFlags, | |||
| CW_USEDEFAULT, CW_USEDEFAULT, wr.right-wr.left, wr.bottom-wr.top, | |||
| (HWND)view->parent, NULL, hInstance, NULL); | |||
| if (!impl->hwnd) { | |||
| UnregisterClass(impl->wc.lpszClassName, NULL); | |||
| free((void*)impl->wc.lpszClassName); | |||
| free(impl); | |||
| return 1; | |||
| } | |||
| SetWindowLongPtr(impl->hwnd, GWLP_USERDATA, (LONG_PTR)view); | |||
| #ifdef PUGL_OPENGL | |||
| impl->hdc = GetDC(impl->hwnd); | |||
| PIXELFORMATDESCRIPTOR pfd; | |||
| ZeroMemory(&pfd, sizeof(pfd)); | |||
| pfd.nSize = sizeof(pfd); | |||
| pfd.nVersion = 1; | |||
| pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER; | |||
| pfd.iPixelType = PFD_TYPE_RGBA; | |||
| pfd.cColorBits = 24; | |||
| pfd.cDepthBits = 16; | |||
| pfd.iLayerType = PFD_MAIN_PLANE; | |||
| int format = ChoosePixelFormat(impl->hdc, &pfd); | |||
| SetPixelFormat(impl->hdc, format, &pfd); | |||
| impl->hglrc = wglCreateContext(impl->hdc); | |||
| if (!impl->hglrc) { | |||
| ReleaseDC (impl->hwnd, impl->hdc); | |||
| DestroyWindow (impl->hwnd); | |||
| UnregisterClass (impl->wc.lpszClassName, NULL); | |||
| free((void*)impl->wc.lpszClassName); | |||
| free(impl); | |||
| return 1; | |||
| } | |||
| #endif | |||
| return PUGL_SUCCESS; | |||
| } | |||
| void | |||
| puglShowWindow(PuglView* view) | |||
| { | |||
| ShowWindow(view->impl->hwnd, SW_SHOWNORMAL); | |||
| } | |||
| void | |||
| puglHideWindow(PuglView* view) | |||
| { | |||
| ShowWindow(view->impl->hwnd, SW_HIDE); | |||
| } | |||
| void | |||
| puglDestroy(PuglView* view) | |||
| { | |||
| if (!view) { | |||
| return; | |||
| } | |||
| PuglInternals* const impl = view->impl; | |||
| #ifdef PUGL_OPENGL | |||
| wglMakeCurrent(NULL, NULL); | |||
| wglDeleteContext(impl->hglrc); | |||
| ReleaseDC(impl->hwnd, impl->hdc); | |||
| #endif | |||
| #ifdef PUGL_CAIRO | |||
| cairo_destroy(impl->buffer_cr); | |||
| cairo_surface_destroy(impl->buffer_surface); | |||
| #endif | |||
| DestroyWindow(impl->hwnd); | |||
| UnregisterClass(impl->wc.lpszClassName, NULL); | |||
| free((void*)impl->wc.lpszClassName); | |||
| free(impl); | |||
| free(view); | |||
| } | |||
| static void | |||
| puglReshape(PuglView* view, int width, int height) | |||
| { | |||
| puglEnterContext(view); | |||
| if (view->reshapeFunc) { | |||
| view->reshapeFunc(view, width, height); | |||
| } else { | |||
| puglDefaultReshape(width, height); | |||
| } | |||
| view->width = width; | |||
| view->height = height; | |||
| } | |||
| static void | |||
| puglDisplay(PuglView* view) | |||
| { | |||
| PuglInternals* impl = view->impl; | |||
| bool success = true; | |||
| puglEnterContext(view); | |||
| #ifdef PUGL_CAIRO | |||
| cairo_t *wc = NULL; | |||
| cairo_t *bc = NULL; | |||
| cairo_surface_t *ws = NULL; | |||
| cairo_surface_t *bs = NULL; | |||
| HDC hdc = impl->paintHdc; | |||
| bc = impl->buffer_cr; | |||
| bs = impl->buffer_surface; | |||
| int w = view->width; | |||
| int h = view->height; | |||
| int bw = bs ? cairo_image_surface_get_width(bs) : -1; | |||
| int bh = bs ? cairo_image_surface_get_height(bs) : -1; | |||
| ws = hdc ? cairo_win32_surface_create(hdc) : NULL; | |||
| wc = ws ? cairo_create(ws) : NULL; | |||
| if (wc && (!bc || bw != w || bh != h)) { | |||
| cairo_destroy(bc); | |||
| cairo_surface_destroy(bs); | |||
| bs = cairo_surface_create_similar_image(ws, CAIRO_FORMAT_ARGB32, w, h); | |||
| bc = bs ? cairo_create(bs) : NULL; | |||
| impl->buffer_cr = bc; | |||
| impl->buffer_surface = bs; | |||
| } | |||
| success = wc != NULL && bc != NULL; | |||
| #endif | |||
| if (success) { | |||
| view->redisplay = false; | |||
| if (view->displayFunc) { | |||
| view->displayFunc(view); | |||
| } | |||
| #ifdef PUGL_CAIRO | |||
| cairo_set_source_surface(wc, bs, 0, 0); | |||
| cairo_paint(wc); | |||
| #endif | |||
| } | |||
| puglLeaveContext(view, success); | |||
| #ifdef PUGL_CAIRO | |||
| cairo_destroy(wc); | |||
| cairo_surface_destroy(ws); | |||
| #endif | |||
| return; | |||
| (void)impl; | |||
| } | |||
| static PuglKey | |||
| keySymToSpecial(int sym) | |||
| { | |||
| switch (sym) { | |||
| case VK_F1: return PUGL_KEY_F1; | |||
| case VK_F2: return PUGL_KEY_F2; | |||
| case VK_F3: return PUGL_KEY_F3; | |||
| case VK_F4: return PUGL_KEY_F4; | |||
| case VK_F5: return PUGL_KEY_F5; | |||
| case VK_F6: return PUGL_KEY_F6; | |||
| case VK_F7: return PUGL_KEY_F7; | |||
| case VK_F8: return PUGL_KEY_F8; | |||
| case VK_F9: return PUGL_KEY_F9; | |||
| case VK_F10: return PUGL_KEY_F10; | |||
| case VK_F11: return PUGL_KEY_F11; | |||
| case VK_F12: return PUGL_KEY_F12; | |||
| case VK_LEFT: return PUGL_KEY_LEFT; | |||
| case VK_UP: return PUGL_KEY_UP; | |||
| case VK_RIGHT: return PUGL_KEY_RIGHT; | |||
| case VK_DOWN: return PUGL_KEY_DOWN; | |||
| case VK_PRIOR: return PUGL_KEY_PAGE_UP; | |||
| case VK_NEXT: return PUGL_KEY_PAGE_DOWN; | |||
| case VK_HOME: return PUGL_KEY_HOME; | |||
| case VK_END: return PUGL_KEY_END; | |||
| case VK_INSERT: return PUGL_KEY_INSERT; | |||
| case VK_SHIFT: return PUGL_KEY_SHIFT; | |||
| case VK_CONTROL: return PUGL_KEY_CTRL; | |||
| case VK_MENU: return PUGL_KEY_ALT; | |||
| case VK_LWIN: return PUGL_KEY_SUPER; | |||
| case VK_RWIN: return PUGL_KEY_SUPER; | |||
| } | |||
| return (PuglKey)0; | |||
| } | |||
| static void | |||
| processMouseEvent(PuglView* view, int button, bool press, LPARAM lParam) | |||
| { | |||
| view->event_timestamp_ms = GetMessageTime(); | |||
| if (press) { | |||
| SetCapture(view->impl->hwnd); | |||
| } else { | |||
| ReleaseCapture(); | |||
| } | |||
| if (view->mouseFunc) { | |||
| view->mouseFunc(view, button, press, | |||
| GET_X_LPARAM(lParam), | |||
| GET_Y_LPARAM(lParam)); | |||
| } | |||
| } | |||
| static void | |||
| setModifiers(PuglView* view) | |||
| { | |||
| view->mods = 0; | |||
| view->mods |= (GetKeyState(VK_SHIFT) < 0) ? PUGL_MOD_SHIFT : 0; | |||
| view->mods |= (GetKeyState(VK_CONTROL) < 0) ? PUGL_MOD_CTRL : 0; | |||
| view->mods |= (GetKeyState(VK_MENU) < 0) ? PUGL_MOD_ALT : 0; | |||
| view->mods |= (GetKeyState(VK_LWIN) < 0) ? PUGL_MOD_SUPER : 0; | |||
| view->mods |= (GetKeyState(VK_RWIN) < 0) ? PUGL_MOD_SUPER : 0; | |||
| } | |||
| static LRESULT | |||
| handleMessage(PuglView* view, UINT message, WPARAM wParam, LPARAM lParam) | |||
| { | |||
| PAINTSTRUCT ps; | |||
| PuglKey key; | |||
| RECT rect; | |||
| MINMAXINFO* mmi; | |||
| setModifiers(view); | |||
| switch (message) { | |||
| case WM_CREATE: | |||
| case WM_SHOWWINDOW: | |||
| case WM_SIZE: | |||
| GetClientRect(view->impl->hwnd, &rect); | |||
| puglReshape(view, rect.right, rect.bottom); | |||
| break; | |||
| case WM_GETMINMAXINFO: | |||
| mmi = (MINMAXINFO*)lParam; | |||
| mmi->ptMinTrackSize.x = view->min_width; | |||
| mmi->ptMinTrackSize.y = view->min_height; | |||
| break; | |||
| case WM_PAINT: | |||
| view->impl->paintHdc = BeginPaint(view->impl->hwnd, &ps); | |||
| puglDisplay(view); | |||
| view->impl->paintHdc = NULL; | |||
| EndPaint(view->impl->hwnd, &ps); | |||
| break; | |||
| case WM_MOUSEMOVE: | |||
| if (view->motionFunc) { | |||
| view->event_timestamp_ms = GetMessageTime(); | |||
| view->motionFunc(view, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)); | |||
| } | |||
| break; | |||
| case WM_LBUTTONDOWN: | |||
| processMouseEvent(view, 1, true, lParam); | |||
| break; | |||
| case WM_MBUTTONDOWN: | |||
| processMouseEvent(view, 2, true, lParam); | |||
| break; | |||
| case WM_RBUTTONDOWN: | |||
| processMouseEvent(view, 3, true, lParam); | |||
| break; | |||
| case WM_LBUTTONUP: | |||
| processMouseEvent(view, 1, false, lParam); | |||
| break; | |||
| case WM_MBUTTONUP: | |||
| processMouseEvent(view, 2, false, lParam); | |||
| break; | |||
| case WM_RBUTTONUP: | |||
| processMouseEvent(view, 3, false, lParam); | |||
| break; | |||
| case WM_MOUSEWHEEL: | |||
| if (view->scrollFunc) { | |||
| view->event_timestamp_ms = GetMessageTime(); | |||
| POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) }; | |||
| ScreenToClient(view->impl->hwnd, &pt); | |||
| view->scrollFunc( | |||
| view, pt.x, pt.y, | |||
| 0.0f, GET_WHEEL_DELTA_WPARAM(wParam) / (float)WHEEL_DELTA); | |||
| } | |||
| break; | |||
| case WM_MOUSEHWHEEL: | |||
| if (view->scrollFunc) { | |||
| view->event_timestamp_ms = GetMessageTime(); | |||
| POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) }; | |||
| ScreenToClient(view->impl->hwnd, &pt); | |||
| view->scrollFunc( | |||
| view, pt.x, pt.y, | |||
| GET_WHEEL_DELTA_WPARAM(wParam) / (float)WHEEL_DELTA, 0.0f); | |||
| } | |||
| break; | |||
| case WM_KEYDOWN: | |||
| if (view->ignoreKeyRepeat && (lParam & (1 << 30))) { | |||
| break; | |||
| } // else nobreak | |||
| case WM_KEYUP: | |||
| view->event_timestamp_ms = GetMessageTime(); | |||
| if ((key = keySymToSpecial(wParam))) { | |||
| if (view->specialFunc) { | |||
| view->specialFunc(view, message == WM_KEYDOWN, key); | |||
| } | |||
| } else if (view->keyboardFunc) { | |||
| static BYTE kbs[256]; | |||
| if (GetKeyboardState(kbs) != FALSE) { | |||
| char lb[2]; | |||
| UINT scanCode = (lParam >> 8) & 0xFFFFFF00; | |||
| if ( 1 == ToAscii(wParam, scanCode, kbs, (LPWORD)lb, 0)) { | |||
| view->keyboardFunc(view, message == WM_KEYDOWN, (char)lb[0]); | |||
| } | |||
| } | |||
| } | |||
| break; | |||
| case WM_QUIT: | |||
| case PUGL_LOCAL_CLOSE_MSG: | |||
| if (view->closeFunc) { | |||
| view->closeFunc(view); | |||
| view->redisplay = false; | |||
| } | |||
| break; | |||
| default: | |||
| return DefWindowProc( | |||
| view->impl->hwnd, message, wParam, lParam); | |||
| } | |||
| return 0; | |||
| } | |||
| void | |||
| puglGrabFocus(PuglView* /*view*/) | |||
| { | |||
| // TODO | |||
| } | |||
| PuglStatus | |||
| puglProcessEvents(PuglView* view) | |||
| { | |||
| MSG msg; | |||
| while (PeekMessage(&msg, view->impl->hwnd, 0, 0, PM_REMOVE)) { | |||
| handleMessage(view, msg.message, msg.wParam, msg.lParam); | |||
| } | |||
| if (view->redisplay) { | |||
| InvalidateRect(view->impl->hwnd, NULL, FALSE); | |||
| } | |||
| return PUGL_SUCCESS; | |||
| } | |||
| LRESULT CALLBACK | |||
| wndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) | |||
| { | |||
| PuglView* view = (PuglView*)GetWindowLongPtr(hwnd, GWLP_USERDATA); | |||
| switch (message) { | |||
| case WM_CREATE: | |||
| PostMessage(hwnd, WM_SHOWWINDOW, TRUE, 0); | |||
| return 0; | |||
| case WM_CLOSE: | |||
| PostMessage(hwnd, PUGL_LOCAL_CLOSE_MSG, wParam, lParam); | |||
| return 0; | |||
| case WM_DESTROY: | |||
| return 0; | |||
| default: | |||
| if (view && hwnd == view->impl->hwnd) { | |||
| return handleMessage(view, message, wParam, lParam); | |||
| } else { | |||
| return DefWindowProc(hwnd, message, wParam, lParam); | |||
| } | |||
| } | |||
| } | |||
| void | |||
| puglPostRedisplay(PuglView* view) | |||
| { | |||
| view->redisplay = true; | |||
| } | |||
| PuglNativeWindow | |||
| puglGetNativeWindow(PuglView* view) | |||
| { | |||
| return (PuglNativeWindow)view->impl->hwnd; | |||
| } | |||
| void* | |||
| puglGetContext(PuglView* view) | |||
| { | |||
| #ifdef PUGL_CAIRO | |||
| return view->impl->buffer_cr; | |||
| #endif | |||
| return NULL; | |||
| // may be unused | |||
| (void)view; | |||
| } | |||
| int | |||
| puglUpdateGeometryConstraints(PuglView* view, int min_width, int min_height, bool aspect) | |||
| { | |||
| // TODO | |||
| return 1; | |||
| (void)view; | |||
| (void)min_width; | |||
| (void)min_height; | |||
| (void)aspect; | |||
| } | |||
| @@ -1,739 +0,0 @@ | |||
| /* | |||
| Copyright 2012-2014 David Robillard <http://drobilla.net> | |||
| Copyright 2011-2012 Ben Loftis, Harrison Consoles | |||
| Copyright 2013,2015 Robin Gareus <robin@gareus.org> | |||
| Copyright 2012-2019 Filipe Coelho <falktx@falktx.com> | |||
| Permission to use, copy, modify, and/or distribute this software for any | |||
| purpose with or without fee is hereby granted, provided that the above | |||
| copyright notice and this permission notice appear in all copies. | |||
| THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES | |||
| WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF | |||
| MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR | |||
| ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES | |||
| WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN | |||
| ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF | |||
| OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. | |||
| */ | |||
| /** | |||
| @file pugl_x11.c X11 Pugl Implementation. | |||
| */ | |||
| #include <stdio.h> | |||
| #include <stdlib.h> | |||
| #include <string.h> | |||
| #ifdef PUGL_CAIRO | |||
| #include <cairo/cairo.h> | |||
| #include <cairo/cairo-xlib.h> | |||
| #endif | |||
| #ifdef PUGL_OPENGL | |||
| #include <GL/gl.h> | |||
| #include <GL/glx.h> | |||
| #endif | |||
| #include <X11/Xatom.h> | |||
| #include <X11/Xlib.h> | |||
| #include <X11/Xutil.h> | |||
| #include <X11/keysym.h> | |||
| #include "pugl_internal.h" | |||
| #ifndef DGL_FILE_BROWSER_DISABLED | |||
| #define SOFD_HAVE_X11 | |||
| #include "../sofd/libsofd.h" | |||
| #include "../sofd/libsofd.c" | |||
| #endif | |||
| /* work around buggy re-parent & focus issues on some systems | |||
| * where no keyboard events are passed through even if the | |||
| * app has mouse-focus and all other events are working. | |||
| */ | |||
| //#define PUGL_GRAB_FOCUS | |||
| /* show messages during initalization | |||
| */ | |||
| //#define PUGL_VERBOSE | |||
| struct PuglInternalsImpl { | |||
| Display* display; | |||
| int screen; | |||
| Window win; | |||
| #ifdef PUGL_CAIRO | |||
| cairo_t* xlib_cr; | |||
| cairo_t* buffer_cr; | |||
| cairo_surface_t* xlib_surface; | |||
| cairo_surface_t* buffer_surface; | |||
| #endif | |||
| #ifdef PUGL_OPENGL | |||
| GLXContext ctx; | |||
| Bool doubleBuffered; | |||
| #endif | |||
| }; | |||
| #ifdef PUGL_OPENGL | |||
| /** | |||
| Attributes for single-buffered RGBA with at least | |||
| 4 bits per color and a 16 bit depth buffer. | |||
| */ | |||
| static int attrListSgl[] = { | |||
| GLX_RGBA, | |||
| GLX_RED_SIZE, 4, | |||
| GLX_GREEN_SIZE, 4, | |||
| GLX_BLUE_SIZE, 4, | |||
| GLX_DEPTH_SIZE, 16, | |||
| GLX_STENCIL_SIZE, 8, | |||
| GLX_ARB_multisample, 1, | |||
| None | |||
| }; | |||
| /** | |||
| Attributes for double-buffered RGBA with at least | |||
| 4 bits per color and a 16 bit depth buffer. | |||
| */ | |||
| static int attrListDbl[] = { | |||
| GLX_RGBA, | |||
| GLX_DOUBLEBUFFER, True, | |||
| GLX_RED_SIZE, 4, | |||
| GLX_GREEN_SIZE, 4, | |||
| GLX_BLUE_SIZE, 4, | |||
| GLX_DEPTH_SIZE, 16, | |||
| GLX_STENCIL_SIZE, 8, | |||
| GLX_ARB_multisample, 1, | |||
| None | |||
| }; | |||
| /** | |||
| Attributes for double-buffered RGBA with multi-sampling | |||
| (antialiasing) | |||
| */ | |||
| static int attrListDblMS[] = { | |||
| GLX_RGBA, | |||
| GLX_DOUBLEBUFFER, True, | |||
| GLX_RED_SIZE, 4, | |||
| GLX_GREEN_SIZE, 4, | |||
| GLX_BLUE_SIZE, 4, | |||
| GLX_ALPHA_SIZE, 4, | |||
| GLX_DEPTH_SIZE, 16, | |||
| GLX_STENCIL_SIZE, 8, | |||
| GLX_SAMPLE_BUFFERS, 1, | |||
| GLX_SAMPLES, 4, | |||
| None | |||
| }; | |||
| #endif | |||
| PuglInternals* | |||
| puglInitInternals(void) | |||
| { | |||
| return (PuglInternals*)calloc(1, sizeof(PuglInternals)); | |||
| } | |||
| void | |||
| puglEnterContext(PuglView* view) | |||
| { | |||
| #ifdef PUGL_OPENGL | |||
| glXMakeCurrent(view->impl->display, view->impl->win, view->impl->ctx); | |||
| #endif | |||
| } | |||
| void | |||
| puglLeaveContext(PuglView* view, bool flush) | |||
| { | |||
| #ifdef PUGL_OPENGL | |||
| if (flush) { | |||
| glFlush(); | |||
| if (view->impl->doubleBuffered) { | |||
| glXSwapBuffers(view->impl->display, view->impl->win); | |||
| } | |||
| } | |||
| glXMakeCurrent(view->impl->display, None, NULL); | |||
| #endif | |||
| } | |||
| int | |||
| puglCreateWindow(PuglView* view, const char* title) | |||
| { | |||
| PuglInternals* impl = view->impl; | |||
| if (!impl) { | |||
| return 1; | |||
| } | |||
| view->impl = impl; | |||
| impl->display = XOpenDisplay(NULL); | |||
| if (!impl->display) { | |||
| free(impl); | |||
| return 1; | |||
| } | |||
| impl->screen = DefaultScreen(impl->display); | |||
| XVisualInfo* vi = NULL; | |||
| #ifdef PUGL_OPENGL | |||
| impl->doubleBuffered = True; | |||
| vi = glXChooseVisual(impl->display, impl->screen, attrListDblMS); | |||
| if (!vi) { | |||
| vi = glXChooseVisual(impl->display, impl->screen, attrListDbl); | |||
| #ifdef PUGL_VERBOSE | |||
| printf("puGL: multisampling (antialiasing) is not available\n"); | |||
| #endif | |||
| } | |||
| if (!vi) { | |||
| vi = glXChooseVisual(impl->display, impl->screen, attrListSgl); | |||
| impl->doubleBuffered = False; | |||
| } | |||
| #endif | |||
| #ifdef PUGL_CAIRO | |||
| XVisualInfo pat; | |||
| int n; | |||
| pat.screen = impl->screen; | |||
| vi = XGetVisualInfo(impl->display, VisualScreenMask, &pat, &n); | |||
| #endif | |||
| if (!vi) { | |||
| XCloseDisplay(impl->display); | |||
| free(impl); | |||
| return 1; | |||
| } | |||
| #ifdef PUGL_VERBOSE | |||
| #ifdef PUGL_OPENGL | |||
| int glxMajor, glxMinor; | |||
| glXQueryVersion(impl->display, &glxMajor, &glxMinor); | |||
| printf("puGL: GLX-Version : %d.%d\n", glxMajor, glxMinor); | |||
| #endif | |||
| #endif | |||
| #ifdef PUGL_OPENGL | |||
| impl->ctx = glXCreateContext(impl->display, vi, 0, GL_TRUE); | |||
| if (!impl->ctx) { | |||
| XFree(vi); | |||
| XCloseDisplay(impl->display); | |||
| free(impl); | |||
| return 1; | |||
| } | |||
| #endif | |||
| Window xParent = view->parent | |||
| ? (Window)view->parent | |||
| : RootWindow(impl->display, impl->screen); | |||
| Colormap cmap = XCreateColormap( | |||
| impl->display, xParent, vi->visual, AllocNone); | |||
| XSetWindowAttributes attr; | |||
| memset(&attr, 0, sizeof(XSetWindowAttributes)); | |||
| attr.border_pixel = BlackPixel(impl->display, impl->screen); | |||
| attr.colormap = cmap; | |||
| attr.event_mask = (ExposureMask | StructureNotifyMask | | |||
| EnterWindowMask | LeaveWindowMask | | |||
| KeyPressMask | KeyReleaseMask | | |||
| ButtonPressMask | ButtonReleaseMask | | |||
| PointerMotionMask | FocusChangeMask); | |||
| impl->win = XCreateWindow( | |||
| impl->display, xParent, | |||
| 0, 0, view->width, view->height, 0, vi->depth, InputOutput, vi->visual, | |||
| CWBorderPixel | CWColormap | CWEventMask, &attr); | |||
| if (!impl->win) { | |||
| #ifdef PUGL_OPENGL | |||
| glXDestroyContext(impl->display, impl->ctx); | |||
| #endif | |||
| XFree(vi); | |||
| XCloseDisplay(impl->display); | |||
| free(impl); | |||
| return 1; | |||
| } | |||
| #ifdef PUGL_CAIRO | |||
| impl->xlib_surface = cairo_xlib_surface_create( | |||
| impl->display, impl->win, vi->visual, view->width, view->height); | |||
| if (impl->xlib_surface == NULL || cairo_surface_status(impl->xlib_surface) != CAIRO_STATUS_SUCCESS) { | |||
| printf("puGL: failed to create cairo surface\n"); | |||
| } | |||
| else { | |||
| impl->xlib_cr = cairo_create(impl->xlib_surface); | |||
| } | |||
| if (impl->xlib_cr == NULL || cairo_status(impl->xlib_cr) != CAIRO_STATUS_SUCCESS) { | |||
| cairo_destroy(impl->xlib_cr); | |||
| cairo_surface_destroy(impl->xlib_surface); | |||
| XDestroyWindow(impl->display, impl->win); | |||
| XFree(vi); | |||
| XCloseDisplay(impl->display); | |||
| free(impl); | |||
| printf("puGL: failed to create cairo context\n"); | |||
| return 1; | |||
| } | |||
| #endif | |||
| if (view->width > 1 || view->height > 1) { | |||
| puglUpdateGeometryConstraints(view, view->min_width, view->min_height, view->min_width != view->width); | |||
| XResizeWindow(view->impl->display, view->impl->win, view->width, view->height); | |||
| } | |||
| if (title) { | |||
| XStoreName(impl->display, impl->win, title); | |||
| Atom netWmName = XInternAtom(impl->display, "_NET_WM_NAME", False); | |||
| Atom utf8String = XInternAtom(impl->display, "UTF8_STRING", False); | |||
| XChangeProperty(impl->display, impl->win, netWmName, utf8String, 8, PropModeReplace, (unsigned char *)title, strlen(title)); | |||
| } | |||
| if (view->transient_parent > 0) { | |||
| XSetTransientForHint(impl->display, impl->win, (Window)view->transient_parent); | |||
| } | |||
| if (view->parent) { | |||
| XMapRaised(impl->display, impl->win); | |||
| } else { | |||
| Atom wmDelete = XInternAtom(impl->display, "WM_DELETE_WINDOW", True); | |||
| XSetWMProtocols(impl->display, impl->win, &wmDelete, 1); | |||
| } | |||
| #ifdef PUGL_VERBOSE | |||
| #ifdef PUGL_OPENGL | |||
| if (glXIsDirect(impl->display, impl->ctx)) { | |||
| printf("puGL: DRI enabled (to disable, set LIBGL_ALWAYS_INDIRECT=1\n"); | |||
| } else { | |||
| printf("puGL: No DRI available\n"); | |||
| } | |||
| #endif | |||
| #endif | |||
| XFree(vi); | |||
| return 0; | |||
| } | |||
| void | |||
| puglDestroy(PuglView* view) | |||
| { | |||
| if (!view) { | |||
| return; | |||
| } | |||
| PuglInternals* const impl = view->impl; | |||
| #ifndef DGL_FILE_BROWSER_DISABLED | |||
| x_fib_close(impl->display); | |||
| #endif | |||
| #ifdef PUGL_OPENGL | |||
| glXDestroyContext(impl->display, impl->ctx); | |||
| #endif | |||
| #ifdef PUGL_CAIRO | |||
| cairo_destroy(impl->xlib_cr); | |||
| cairo_destroy(impl->buffer_cr); | |||
| cairo_surface_destroy(impl->xlib_surface); | |||
| cairo_surface_destroy(impl->buffer_surface); | |||
| #endif | |||
| XDestroyWindow(impl->display, impl->win); | |||
| XCloseDisplay(impl->display); | |||
| free(impl); | |||
| free(view); | |||
| } | |||
| void | |||
| puglShowWindow(PuglView* view) | |||
| { | |||
| XMapRaised(view->impl->display, view->impl->win); | |||
| } | |||
| void | |||
| puglHideWindow(PuglView* view) | |||
| { | |||
| XUnmapWindow(view->impl->display, view->impl->win); | |||
| } | |||
| static void | |||
| puglReshape(PuglView* view, int width, int height) | |||
| { | |||
| puglEnterContext(view); | |||
| if (view->reshapeFunc) { | |||
| view->reshapeFunc(view, width, height); | |||
| } else { | |||
| puglDefaultReshape(width, height); | |||
| } | |||
| puglLeaveContext(view, false); | |||
| view->width = width; | |||
| view->height = height; | |||
| } | |||
| static void | |||
| puglDisplay(PuglView* view) | |||
| { | |||
| PuglInternals* impl = view->impl; | |||
| puglEnterContext(view); | |||
| #ifdef PUGL_CAIRO | |||
| cairo_t* bc = impl->buffer_cr; | |||
| cairo_surface_t* xs = impl->xlib_surface; | |||
| cairo_surface_t* bs = impl->buffer_surface; | |||
| int w = cairo_xlib_surface_get_width(xs); | |||
| int h = cairo_xlib_surface_get_height(xs); | |||
| int bw = bs ? cairo_image_surface_get_width(bs) : -1; | |||
| int bh = bs ? cairo_image_surface_get_height(bs) : -1; | |||
| if (!bc || bw != w || bh != h) { | |||
| cairo_destroy(bc); | |||
| cairo_surface_destroy(bs); | |||
| bs = cairo_surface_create_similar_image(xs, CAIRO_FORMAT_ARGB32, w, h); | |||
| bc = bs ? cairo_create(bs) : NULL; | |||
| impl->buffer_cr = bc; | |||
| impl->buffer_surface = bs; | |||
| } | |||
| if (!bc) { | |||
| puglLeaveContext(view, false); | |||
| return; | |||
| } | |||
| #endif | |||
| view->redisplay = false; | |||
| if (view->displayFunc) { | |||
| view->displayFunc(view); | |||
| } | |||
| #ifdef PUGL_CAIRO | |||
| cairo_t* xc = impl->xlib_cr; | |||
| cairo_set_source_surface(xc, impl->buffer_surface, 0, 0); | |||
| cairo_paint(xc); | |||
| #endif | |||
| puglLeaveContext(view, true); | |||
| (void)impl; | |||
| } | |||
| static void | |||
| puglResize(PuglView* view) | |||
| { | |||
| int set_hints = 1; | |||
| view->pending_resize = false; | |||
| if (!view->resizeFunc) { return; } | |||
| /* ask the plugin about the new size */ | |||
| view->resizeFunc(view, &view->width, &view->height, &set_hints); | |||
| if (set_hints) { | |||
| XSizeHints sizeHints; | |||
| memset(&sizeHints, 0, sizeof(sizeHints)); | |||
| sizeHints.flags = PMinSize|PMaxSize; | |||
| sizeHints.min_width = view->width; | |||
| sizeHints.min_height = view->height; | |||
| sizeHints.max_width = view->user_resizable ? 4096 : view->width; | |||
| sizeHints.max_height = view->user_resizable ? 4096 : view->height; | |||
| XSetWMNormalHints(view->impl->display, view->impl->win, &sizeHints); | |||
| } | |||
| XResizeWindow(view->impl->display, view->impl->win, view->width, view->height); | |||
| XFlush(view->impl->display); | |||
| #ifdef PUGL_VERBOSE | |||
| printf("puGL: window resize (%dx%d)\n", view->width, view->height); | |||
| #endif | |||
| /* and call Reshape in glX context */ | |||
| puglReshape(view, view->width, view->height); | |||
| } | |||
| static PuglKey | |||
| keySymToSpecial(KeySym sym) | |||
| { | |||
| switch (sym) { | |||
| case XK_F1: return PUGL_KEY_F1; | |||
| case XK_F2: return PUGL_KEY_F2; | |||
| case XK_F3: return PUGL_KEY_F3; | |||
| case XK_F4: return PUGL_KEY_F4; | |||
| case XK_F5: return PUGL_KEY_F5; | |||
| case XK_F6: return PUGL_KEY_F6; | |||
| case XK_F7: return PUGL_KEY_F7; | |||
| case XK_F8: return PUGL_KEY_F8; | |||
| case XK_F9: return PUGL_KEY_F9; | |||
| case XK_F10: return PUGL_KEY_F10; | |||
| case XK_F11: return PUGL_KEY_F11; | |||
| case XK_F12: return PUGL_KEY_F12; | |||
| case XK_Left: return PUGL_KEY_LEFT; | |||
| case XK_Up: return PUGL_KEY_UP; | |||
| case XK_Right: return PUGL_KEY_RIGHT; | |||
| case XK_Down: return PUGL_KEY_DOWN; | |||
| case XK_Page_Up: return PUGL_KEY_PAGE_UP; | |||
| case XK_Page_Down: return PUGL_KEY_PAGE_DOWN; | |||
| case XK_Home: return PUGL_KEY_HOME; | |||
| case XK_End: return PUGL_KEY_END; | |||
| case XK_Insert: return PUGL_KEY_INSERT; | |||
| case XK_Shift_L: return PUGL_KEY_SHIFT; | |||
| case XK_Shift_R: return PUGL_KEY_SHIFT; | |||
| case XK_Control_L: return PUGL_KEY_CTRL; | |||
| case XK_Control_R: return PUGL_KEY_CTRL; | |||
| case XK_Alt_L: return PUGL_KEY_ALT; | |||
| case XK_Alt_R: return PUGL_KEY_ALT; | |||
| case XK_Super_L: return PUGL_KEY_SUPER; | |||
| case XK_Super_R: return PUGL_KEY_SUPER; | |||
| } | |||
| return (PuglKey)0; | |||
| } | |||
| static void | |||
| setModifiers(PuglView* view, unsigned xstate, unsigned xtime) | |||
| { | |||
| view->event_timestamp_ms = xtime; | |||
| view->mods = 0; | |||
| view->mods |= (xstate & ShiftMask) ? PUGL_MOD_SHIFT : 0; | |||
| view->mods |= (xstate & ControlMask) ? PUGL_MOD_CTRL : 0; | |||
| view->mods |= (xstate & Mod1Mask) ? PUGL_MOD_ALT : 0; | |||
| view->mods |= (xstate & Mod4Mask) ? PUGL_MOD_SUPER : 0; | |||
| } | |||
| static void | |||
| dispatchKey(PuglView* view, XEvent* event, bool press) | |||
| { | |||
| KeySym sym; | |||
| char str[5]; | |||
| PuglKey special; | |||
| const int n = XLookupString(&event->xkey, str, 4, &sym, NULL); | |||
| if (sym == XK_Escape && view->closeFunc && !press && !view->parent) { | |||
| view->closeFunc(view); | |||
| view->redisplay = false; | |||
| return; | |||
| } | |||
| if (n == 0 && sym == 0) { | |||
| goto send_event; | |||
| return; | |||
| } | |||
| if (n > 1) { | |||
| fprintf(stderr, "warning: Unsupported multi-byte key %X\n", (int)sym); | |||
| goto send_event; | |||
| return; | |||
| } | |||
| special = keySymToSpecial(sym); | |||
| if (special && view->specialFunc) { | |||
| if (view->specialFunc(view, press, special) == 0) { | |||
| return; | |||
| } | |||
| } else if (!special && view->keyboardFunc) { | |||
| if (view->keyboardFunc(view, press, str[0]) == 0) { | |||
| return; | |||
| } | |||
| } | |||
| send_event: | |||
| if (view->parent != 0) { | |||
| event->xkey.time = 0; // purposefully set an invalid time, used for feedback detection on bad hosts | |||
| event->xany.window = view->parent; | |||
| XSendEvent(view->impl->display, view->parent, False, NoEventMask, event); | |||
| } | |||
| } | |||
| PuglStatus | |||
| puglProcessEvents(PuglView* view) | |||
| { | |||
| int conf_width = -1; | |||
| int conf_height = -1; | |||
| XEvent event; | |||
| while (XPending(view->impl->display) > 0) { | |||
| XNextEvent(view->impl->display, &event); | |||
| #ifndef DGL_FILE_BROWSER_DISABLED | |||
| if (x_fib_handle_events(view->impl->display, &event)) { | |||
| const int status = x_fib_status(); | |||
| if (status > 0) { | |||
| char* const filename = x_fib_filename(); | |||
| x_fib_close(view->impl->display); | |||
| if (view->fileSelectedFunc) { | |||
| view->fileSelectedFunc(view, filename); | |||
| } | |||
| free(filename); | |||
| } else if (status < 0) { | |||
| x_fib_close(view->impl->display); | |||
| if (view->fileSelectedFunc) { | |||
| view->fileSelectedFunc(view, NULL); | |||
| } | |||
| } | |||
| break; | |||
| } | |||
| #endif | |||
| if (event.xany.window != view->impl->win && | |||
| (view->parent == 0 || event.xany.window != (Window)view->parent)) { | |||
| continue; | |||
| } | |||
| if ((event.type == KeyPress || event.type == KeyRelease) && event.xkey.time == 0) { | |||
| continue; | |||
| } | |||
| switch (event.type) { | |||
| case UnmapNotify: | |||
| if (view->motionFunc) { | |||
| view->motionFunc(view, -1, -1); | |||
| } | |||
| break; | |||
| case MapNotify: | |||
| puglReshape(view, view->width, view->height); | |||
| break; | |||
| case ConfigureNotify: | |||
| if ((event.xconfigure.width != view->width) || | |||
| (event.xconfigure.height != view->height)) { | |||
| conf_width = event.xconfigure.width; | |||
| conf_height = event.xconfigure.height; | |||
| } | |||
| break; | |||
| case Expose: | |||
| if (event.xexpose.count != 0) { | |||
| break; | |||
| } | |||
| view->redisplay = true; | |||
| break; | |||
| case MotionNotify: | |||
| setModifiers(view, event.xmotion.state, event.xmotion.time); | |||
| if (view->motionFunc) { | |||
| view->motionFunc(view, event.xmotion.x, event.xmotion.y); | |||
| } | |||
| break; | |||
| case ButtonPress: | |||
| setModifiers(view, event.xbutton.state, event.xbutton.time); | |||
| if (event.xbutton.button >= 4 && event.xbutton.button <= 7) { | |||
| if (view->scrollFunc) { | |||
| float dx = 0, dy = 0; | |||
| switch (event.xbutton.button) { | |||
| case 4: dy = 1.0f; break; | |||
| case 5: dy = -1.0f; break; | |||
| case 6: dx = -1.0f; break; | |||
| case 7: dx = 1.0f; break; | |||
| } | |||
| view->scrollFunc(view, event.xbutton.x, event.xbutton.y, dx, dy); | |||
| } | |||
| break; | |||
| } | |||
| // nobreak | |||
| case ButtonRelease: | |||
| setModifiers(view, event.xbutton.state, event.xbutton.time); | |||
| if (view->mouseFunc && | |||
| (event.xbutton.button < 4 || event.xbutton.button > 7)) { | |||
| view->mouseFunc(view, | |||
| event.xbutton.button, event.type == ButtonPress, | |||
| event.xbutton.x, event.xbutton.y); | |||
| } | |||
| break; | |||
| case KeyPress: | |||
| setModifiers(view, event.xkey.state, event.xkey.time); | |||
| dispatchKey(view, &event, true); | |||
| break; | |||
| case KeyRelease: { | |||
| setModifiers(view, event.xkey.state, event.xkey.time); | |||
| bool repeated = false; | |||
| if (view->ignoreKeyRepeat && | |||
| XEventsQueued(view->impl->display, QueuedAfterReading)) { | |||
| XEvent next; | |||
| XPeekEvent(view->impl->display, &next); | |||
| if (next.type == KeyPress && | |||
| next.xkey.time == event.xkey.time && | |||
| next.xkey.keycode == event.xkey.keycode) { | |||
| XNextEvent(view->impl->display, &event); | |||
| repeated = true; | |||
| } | |||
| } | |||
| if (!repeated) { | |||
| dispatchKey(view, &event, false); | |||
| } | |||
| } break; | |||
| case ClientMessage: { | |||
| char* type = XGetAtomName(view->impl->display, | |||
| event.xclient.message_type); | |||
| if (!strcmp(type, "WM_PROTOCOLS")) { | |||
| if (view->closeFunc) { | |||
| view->closeFunc(view); | |||
| view->redisplay = false; | |||
| } | |||
| } | |||
| XFree(type); | |||
| } break; | |||
| #ifdef PUGL_GRAB_FOCUS | |||
| case EnterNotify: | |||
| XSetInputFocus(view->impl->display, view->impl->win, RevertToPointerRoot, CurrentTime); | |||
| break; | |||
| #endif | |||
| default: | |||
| break; | |||
| } | |||
| } | |||
| if (conf_width != -1) { | |||
| #ifdef PUGL_CAIRO | |||
| // Resize surfaces/contexts before dispatching | |||
| view->redisplay = true; | |||
| cairo_xlib_surface_set_size(view->impl->xlib_surface, | |||
| conf_width, conf_height); | |||
| #endif | |||
| puglReshape(view, conf_width, conf_height); | |||
| } | |||
| if (view->pending_resize) { | |||
| puglResize(view); | |||
| } | |||
| if (view->redisplay) { | |||
| puglDisplay(view); | |||
| } | |||
| return PUGL_SUCCESS; | |||
| } | |||
| void | |||
| puglPostRedisplay(PuglView* view) | |||
| { | |||
| view->redisplay = true; | |||
| } | |||
| void | |||
| puglPostResize(PuglView* view) | |||
| { | |||
| view->pending_resize = true; | |||
| } | |||
| PuglNativeWindow | |||
| puglGetNativeWindow(PuglView* view) | |||
| { | |||
| return view->impl->win; | |||
| } | |||
| void* | |||
| puglGetContext(PuglView* view) | |||
| { | |||
| #ifdef PUGL_CAIRO | |||
| return view->impl->buffer_cr; | |||
| #endif | |||
| return NULL; | |||
| // may be unused | |||
| (void)view; | |||
| } | |||
| int | |||
| puglUpdateGeometryConstraints(PuglView* view, int min_width, int min_height, bool aspect) | |||
| { | |||
| XSizeHints sizeHints; | |||
| memset(&sizeHints, 0, sizeof(sizeHints)); | |||
| sizeHints.flags = PMinSize|PMaxSize; | |||
| sizeHints.min_width = min_width; | |||
| sizeHints.min_height = min_height; | |||
| sizeHints.max_width = view->user_resizable ? 4096 : min_width; | |||
| sizeHints.max_height = view->user_resizable ? 4096 : min_height; | |||
| if (aspect) { | |||
| sizeHints.flags |= PAspect; | |||
| sizeHints.min_aspect.x = min_width; | |||
| sizeHints.min_aspect.y = min_height; | |||
| sizeHints.max_aspect.x = min_width; | |||
| sizeHints.max_aspect.y = min_height; | |||
| } | |||
| XSetWMNormalHints(view->impl->display, view->impl->win, &sizeHints); | |||
| return 0; | |||
| } | |||