diff options
Diffstat (limited to 'pugl/pugl.hpp')
-rw-r--r-- | pugl/pugl.hpp | 437 |
1 files changed, 395 insertions, 42 deletions
diff --git a/pugl/pugl.hpp b/pugl/pugl.hpp index 73cfe2a..18451ee 100644 --- a/pugl/pugl.hpp +++ b/pugl/pugl.hpp @@ -22,6 +22,13 @@ #define PUGL_PUGL_HPP #include "pugl/pugl.h" +#include "pugl/pugl_gl.h" // FIXME + +#include <chrono> +#include <functional> +#include <ratio> +#include <stdexcept> +#include <utility> /** @defgroup puglxx C++ @@ -37,84 +44,430 @@ */ namespace pugl { -/** - A drawable region that receives events. +enum class Status { + success = PUGL_SUCCESS, + failure = PUGL_FAILURE, + unknownError = PUGL_UNKNOWN_ERROR, + badBackend = PUGL_BAD_BACKEND, + backendFailed = PUGL_BACKEND_FAILED, + registrationFailed = PUGL_REGISTRATION_FAILED, + createWindowFailed = PUGL_CREATE_WINDOW_FAILED, + setFormatFailed = PUGL_SET_FORMAT_FAILED, + createContextFailed = PUGL_CREATE_CONTEXT_FAILED, + unsupportedType = PUGL_UNSUPPORTED_TYPE, +}; - This is a thin wrapper for a PuglView that contains only a pointer. +enum class ViewHint { + useCompatProfile, ///< Use compatible (not core) OpenGL profile + useDebugContext, ///< True to use a debug OpenGL context + contextVersionMajor, ///< OpenGL context major version + contextVersionMinor, ///< OpenGL context minor version + redBits, ///< Number of bits for red channel + greenBits, ///< Number of bits for green channel + blueBits, ///< Number of bits for blue channel + alphaBits, ///< Number of bits for alpha channel + depthBits, ///< Number of bits for depth buffer + stencilBits, ///< Number of bits for stencil buffer + samples, ///< Number of samples per pixel (AA) + doubleBuffer, ///< True if double buffering should be used + swapInterval, ///< Number of frames between buffer swaps + resizable, ///< True if window should be resizable + ignoreKeyRepeat, ///< True if key repeat events are ignored +}; - @ingroup puglxx -*/ -class View { +using Rect = PuglRect; +using NativeWindow = PuglNativeWindow; +using GlFunc = PuglGlFunc; +using Event = PuglEvent; + +template<PuglEventType t, class Base> +struct TypedEvent : public Base { + static constexpr const PuglEventType type = t; +}; + +/* Strong types for every event type. */ + +using ButtonPressEvent = TypedEvent<PUGL_BUTTON_PRESS, PuglEventButton>; +using ButtonReleaseEvent = TypedEvent<PUGL_BUTTON_RELEASE, PuglEventButton>; +using CreateEvent = TypedEvent<PUGL_CREATE, PuglEventAny>; +using DestroyEvent = TypedEvent<PUGL_DESTROY, PuglEventAny>; +using MapEvent = TypedEvent<PUGL_MAP, PuglEventAny>; +using UnmapEvent = TypedEvent<PUGL_UNMAP, PuglEventAny>; +using ConfigureEvent = TypedEvent<PUGL_CONFIGURE, PuglEventConfigure>; +using ExposeEvent = TypedEvent<PUGL_EXPOSE, PuglEventExpose>; +using CloseEvent = TypedEvent<PUGL_CLOSE, PuglEventAny>; +using KeyPressEvent = TypedEvent<PUGL_KEY_PRESS, PuglEventKey>; +using KeyReleaseEvent = TypedEvent<PUGL_KEY_RELEASE, PuglEventKey>; +using TextEvent = TypedEvent<PUGL_TEXT, PuglEventText>; +using EnterEvent = TypedEvent<PUGL_ENTER_NOTIFY, PuglEventCrossing>; +using LeaveEvent = TypedEvent<PUGL_LEAVE_NOTIFY, PuglEventCrossing>; +using MotionEvent = TypedEvent<PUGL_MOTION_NOTIFY, PuglEventMotion>; +using ScrollEvent = TypedEvent<PUGL_SCROLL, PuglEventScroll>; +using FocusInEvent = TypedEvent<PUGL_FOCUS_IN, PuglEventFocus>; +using FocusOutEvent = TypedEvent<PUGL_FOCUS_OUT, PuglEventFocus>; +using ClientEvent = TypedEvent<PUGL_CLIENT, PuglEventClient>; + +static inline const char* +strerror(pugl::Status status) +{ + return puglStrerror(static_cast<PuglStatus>(status)); +} + +static inline GlFunc +getProcAddress(const char* name) +{ + return puglGetProcAddress(name); +} + +class World; + +class Clock +{ +public: + using rep = double; + using period = std::ratio<1>; + using duration = std::chrono::duration<double>; + using time_point = std::chrono::time_point<Clock>; + + static constexpr bool is_steady = true; + + explicit Clock(World& world) + : _world{world} + {} + + time_point now() const; + +private: + const pugl::World& _world; +}; + +class World +{ +public: + World() + : _clock(*this) + , _world(puglNewWorld()) + { + if (!_world) { + throw std::runtime_error("Failed to create pugl::World"); + } + } + + ~World() { puglFreeWorld(_world); } + + World(const World&) = delete; + World& operator=(const World&) = delete; + World(World&&) = delete; + World&& operator=(World&&) = delete; + + Status setClassName(const char* const name) + { + return static_cast<Status>(puglSetClassName(_world, name)); + } + + double getTime() const { return puglGetTime(_world); } + + Status pollEvents(const double timeout) + { + return static_cast<Status>(puglPollEvents(_world, timeout)); + } + + Status dispatchEvents() + { + return static_cast<Status>(puglDispatchEvents(_world)); + } + + const PuglWorld* cobj() const { return _world; } + PuglWorld* cobj() { return _world; } + + const Clock& clock() { return _clock; } + +private: + Clock _clock; + PuglWorld* const _world; +}; + +inline Clock::time_point +Clock::now() const +{ + return time_point{duration{_world.getTime()}}; +} + +class ViewBase +{ public: - View(int* pargc, char** argv) - : _view(puglInit(pargc, argv)) + explicit ViewBase(World& world) + : _world(world) + , _view(puglNewView(world.cobj())) { + if (!_view) { + throw std::runtime_error("Failed to create pugl::View"); + } + puglSetHandle(_view, this); - puglSetEventFunc(_view, _onEvent); } - virtual ~View() { puglDestroy(_view); } + ~ViewBase() { puglFreeView(_view); } + + ViewBase(const ViewBase&) = delete; + ViewBase(ViewBase&&) = delete; + ViewBase& operator=(const ViewBase&) = delete; + ViewBase&& operator=(ViewBase&&) = delete; + + Status setHint(ViewHint hint, int value) + { + return static_cast<Status>( + puglSetViewHint(_view, static_cast<PuglViewHint>(hint), value)); + } + + bool getVisible() const { return puglGetVisible(_view); } + + Status postRedisplay() + { + return static_cast<Status>(puglPostRedisplay(_view)); + } + + const pugl::World& getWorld() const { return _world; } + pugl::World& getWorld() { return _world; } + + Rect getFrame() const { return puglGetFrame(_view); } - virtual void initWindowParent(PuglNativeWindow parent) { - puglInitWindowParent(_view, parent); + Status setFrame(Rect frame) + { + return static_cast<Status>(puglSetFrame(_view, frame)); } - virtual void initWindowSize(int width, int height) { - puglInitWindowSize(_view, width, height); + Status setMinSize(int width, int height) + { + return static_cast<Status>(puglSetMinSize(_view, width, height)); } - virtual void initWindowMinSize(int width, int height) { - puglInitWindowMinSize(_view, width, height); + Status setAspectRatio(int minX, int minY, int maxX, int maxY) + { + return static_cast<Status>( + puglSetAspectRatio(_view, minX, minY, maxX, maxY)); } - virtual void initWindowAspectRatio(int min_x, int min_y, int max_x, int max_y) { - puglInitWindowAspectRatio(_view, min_x, min_y, max_x, max_y); + Status setWindowTitle(const char* title) + { + return static_cast<Status>(puglSetWindowTitle(_view, title)); } - virtual void initResizable(bool resizable) { - puglInitResizable(_view, resizable); + Status setParentWindow(NativeWindow parent) + { + return static_cast<Status>(puglSetParentWindow(_view, parent)); } - virtual void initTransientFor(uintptr_t parent) { - puglInitTransientFor(_view, parent); + Status setTransientFor(NativeWindow parent) + { + return static_cast<Status>(puglSetTransientFor(_view, parent)); } - virtual void initBackend(const PuglBackend* backend) { - puglInitBackend(_view, backend); + Status createWindow(const char* title) + { + return static_cast<Status>(puglCreateWindow(_view, title)); } - virtual void createWindow(const char* title) { - puglCreateWindow(_view, title); + Status showWindow() { return static_cast<Status>(puglShowWindow(_view)); } + + Status hideWindow() { return static_cast<Status>(puglHideWindow(_view)); } + + NativeWindow getNativeWindow() { return puglGetNativeWindow(_view); } + + Status setBackend(const PuglBackend* backend) + { + return static_cast<Status>(puglSetBackend(_view, backend)); } - virtual void showWindow() { puglShowWindow(_view); } - virtual void hideWindow() { puglHideWindow(_view); } - virtual PuglNativeWindow getNativeWindow() { return puglGetNativeWindow(_view); } + void* getContext() { return puglGetContext(_view); } - virtual void onEvent(const PuglEvent* event) = 0; + bool hasFocus() const { return puglHasFocus(_view); } - virtual void* getContext() { return puglGetContext(_view); } - virtual void ignoreKeyRepeat(bool ignore) { puglIgnoreKeyRepeat(_view, ignore); } - virtual void grabFocus() { puglGrabFocus(_view); } - virtual void requestAttention() { puglRequestAttention(_view); } - virtual PuglStatus waitForEvent() { return puglWaitForEvent(_view); } - virtual PuglStatus processEvents() { return puglProcessEvents(_view); } - virtual void postRedisplay() { puglPostRedisplay(_view); } + Status grabFocus() { return static_cast<Status>(puglGrabFocus(_view)); } + + Status requestAttention() + { + return static_cast<Status>(puglRequestAttention(_view)); + } PuglView* cobj() { return _view; } +protected: + World& _world; + PuglView* _view; +}; + +/** + A drawable region that receives events. + + This is a thin wrapper for a PuglView that contains only a pointer. + + @ingroup puglxx +*/ +template<typename Data> +class View : public ViewBase +{ +public: + template<class E> + using TypedEventFunc = std::function<pugl::Status(View&, const E&)>; + + using NothingEvent = TypedEvent<PUGL_NOTHING, PuglEvent>; + + /** + A tuple of event handlers, one for each event type. + + Note that the indices here must correspond to PuglEventType. + */ + using EventFuncs = std::tuple<TypedEventFunc<NothingEvent>, + TypedEventFunc<ButtonPressEvent>, + TypedEventFunc<ButtonReleaseEvent>, + TypedEventFunc<CreateEvent>, + TypedEventFunc<DestroyEvent>, + TypedEventFunc<MapEvent>, + TypedEventFunc<UnmapEvent>, + TypedEventFunc<ConfigureEvent>, + TypedEventFunc<ExposeEvent>, + TypedEventFunc<CloseEvent>, + TypedEventFunc<KeyPressEvent>, + TypedEventFunc<KeyReleaseEvent>, + TypedEventFunc<TextEvent>, + TypedEventFunc<EnterEvent>, + TypedEventFunc<LeaveEvent>, + TypedEventFunc<MotionEvent>, + TypedEventFunc<ScrollEvent>, + TypedEventFunc<FocusInEvent>, + TypedEventFunc<FocusOutEvent>, + TypedEventFunc<ClientEvent>>; + + using EventFunc = std::function<pugl::Status(View&, const PuglEvent&)>; + + explicit View(World& world) + : ViewBase{world} + , _data{} + { + puglSetEventFunc(_view, _onEvent); + } + + View(World& world, Data data) + : ViewBase{world} + , _data{data} + { + puglSetEventFunc(_view, _onEvent); + } + + template<class HandledEvent> + Status setEventFunc( + std::function<pugl::Status(View&, const HandledEvent&)> handler) + { + std::get<HandledEvent::type>(_eventFuncs) = handler; + + return Status::success; + } + + template<class HandledEvent> + Status setEventFunc(pugl::Status (*handler)(View&, const HandledEvent&)) + { + std::get<HandledEvent::type>(_eventFuncs) = handler; + + return Status::success; + } + + const Data& getData() const { return _data; } + Data& getData() { return _data; } + private: - static void _onEvent(PuglView* view, const PuglEvent* event) { - ((View*)puglGetHandle(view))->onEvent(event); + static PuglStatus _onEvent(PuglView* view, const PuglEvent* event) noexcept + { + View* self = static_cast<View*>(puglGetHandle(view)); + + return static_cast<PuglStatus>(self->dispatchEvent(*event)); } - PuglView* _view; + Status dispatchEvent(const PuglEvent& event) + { + switch (event.type) { + case PUGL_NOTHING: + return Status::success; + case PUGL_BUTTON_PRESS: + return dispatchTypedEvent( + static_cast<const ButtonPressEvent&>(event.button)); + case PUGL_BUTTON_RELEASE: + return dispatchTypedEvent( + static_cast<const ButtonReleaseEvent&>(event.button)); + case PUGL_CREATE: + return dispatchTypedEvent( + static_cast<const CreateEvent&>(event.any)); + case PUGL_DESTROY: + return dispatchTypedEvent( + static_cast<const DestroyEvent&>(event.any)); + case PUGL_MAP: + return dispatchTypedEvent(static_cast<const MapEvent&>(event.any)); + case PUGL_UNMAP: + return dispatchTypedEvent( + static_cast<const UnmapEvent&>(event.any)); + case PUGL_CONFIGURE: + return dispatchTypedEvent( + static_cast<const ConfigureEvent&>(event.configure)); + case PUGL_EXPOSE: + return dispatchTypedEvent( + static_cast<const ExposeEvent&>(event.expose)); + case PUGL_CLOSE: + return dispatchTypedEvent( + static_cast<const CloseEvent&>(event.any)); + case PUGL_KEY_PRESS: + return dispatchTypedEvent( + static_cast<const KeyPressEvent&>(event.key)); + case PUGL_KEY_RELEASE: + return dispatchTypedEvent( + static_cast<const KeyReleaseEvent&>(event.key)); + case PUGL_TEXT: + return dispatchTypedEvent( + static_cast<const TextEvent&>(event.text)); + case PUGL_ENTER_NOTIFY: + return dispatchTypedEvent( + static_cast<const EnterEvent&>(event.crossing)); + case PUGL_LEAVE_NOTIFY: + return dispatchTypedEvent( + static_cast<const LeaveEvent&>(event.crossing)); + case PUGL_MOTION_NOTIFY: + return dispatchTypedEvent( + static_cast<const MotionEvent&>(event.motion)); + case PUGL_SCROLL: + return dispatchTypedEvent( + static_cast<const ScrollEvent&>(event.scroll)); + case PUGL_FOCUS_IN: + return dispatchTypedEvent( + static_cast<const FocusInEvent&>(event.focus)); + case PUGL_FOCUS_OUT: + return dispatchTypedEvent( + static_cast<const FocusOutEvent&>(event.focus)); + case PUGL_CLIENT: + return dispatchTypedEvent( + static_cast<const ClientEvent&>(event.client)); + } + + return Status::failure; + } + + template<class E> + Status dispatchTypedEvent(const E& event) + { + auto& handler = std::get<E::type>(_eventFuncs); + if (handler) { + return handler(*this, event); + } + + return Status::success; + } + + Data _data; + EventFuncs _eventFuncs; }; -} // namespace pugl +} // namespace pugl /** @} */ -#endif /* PUGL_PUGL_HPP */ +#endif /* PUGL_PUGL_HPP */ |