| /* |
| Simple DirectMedia Layer |
| Copyright (C) 1997-2014 Sam Lantinga <[email protected]> |
| |
| This software is provided 'as-is', without any express or implied |
| warranty. In no event will the authors be held liable for any damages |
| arising from the use of this software. |
| |
| Permission is granted to anyone to use this software for any purpose, |
| including commercial applications, and to alter it and redistribute it |
| freely, subject to the following restrictions: |
| |
| 1. The origin of this software must not be misrepresented; you must not |
| claim that you wrote the original software. If you use this software |
| in a product, an acknowledgment in the product documentation would be |
| appreciated but is not required. |
| 2. Altered source versions must be plainly marked as such, and must not be |
| misrepresented as being the original software. |
| 3. This notice may not be removed or altered from any source distribution. |
| */ |
| |
| #ifndef _SDL_BWin_h |
| #define _SDL_BWin_h |
| |
| #ifdef __cplusplus |
| extern "C" { |
| #endif |
| |
| #include "../../SDL_internal.h" |
| #include "SDL.h" |
| #include "SDL_syswm.h" |
| #include "SDL_bframebuffer.h" |
| |
| #ifdef __cplusplus |
| } |
| #endif |
| |
| #include <stdio.h> |
| #include <AppKit.h> |
| #include <InterfaceKit.h> |
| #include <be/game/DirectWindow.h> |
| #if SDL_VIDEO_OPENGL |
| #include <be/opengl/GLView.h> |
| #endif |
| #include "SDL_events.h" |
| #include "../../main/haiku/SDL_BApp.h" |
| |
| |
| enum WinCommands { |
| BWIN_MOVE_WINDOW, |
| BWIN_RESIZE_WINDOW, |
| BWIN_SHOW_WINDOW, |
| BWIN_HIDE_WINDOW, |
| BWIN_MAXIMIZE_WINDOW, |
| BWIN_MINIMIZE_WINDOW, |
| BWIN_RESTORE_WINDOW, |
| BWIN_SET_TITLE, |
| BWIN_SET_BORDERED, |
| BWIN_FULLSCREEN |
| }; |
| |
| |
| class SDL_BWin:public BDirectWindow |
| { |
| public: |
| /* Constructor/Destructor */ |
| SDL_BWin(BRect bounds, window_look look, uint32 flags) |
| : BDirectWindow(bounds, "Untitled", look, B_NORMAL_WINDOW_FEEL, flags) |
| { |
| _last_buttons = 0; |
| |
| #if SDL_VIDEO_OPENGL |
| _SDL_GLView = NULL; |
| #endif |
| _shown = false; |
| _inhibit_resize = false; |
| _mouse_focused = false; |
| _prev_frame = NULL; |
| |
| /* Handle framebuffer stuff */ |
| _connected = _connection_disabled = false; |
| _buffer_created = _buffer_dirty = false; |
| _trash_window_buffer = false; |
| _buffer_locker = new BLocker(); |
| _bitmap = NULL; |
| _clips = NULL; |
| |
| #ifdef DRAWTHREAD |
| _draw_thread_id = spawn_thread(BE_DrawThread, "drawing_thread", |
| B_NORMAL_PRIORITY, (void*) this); |
| resume_thread(_draw_thread_id); |
| #endif |
| } |
| |
| virtual ~ SDL_BWin() |
| { |
| Lock(); |
| _connection_disabled = true; |
| int32 result; |
| |
| #if SDL_VIDEO_OPENGL |
| if (_SDL_GLView) { |
| _SDL_GLView->UnlockGL(); |
| RemoveChild(_SDL_GLView); /* Why was this outside the if |
| statement before? */ |
| } |
| |
| #endif |
| Unlock(); |
| #if SDL_VIDEO_OPENGL |
| if (_SDL_GLView) { |
| delete _SDL_GLView; |
| } |
| #endif |
| |
| /* Clean up framebuffer stuff */ |
| _buffer_locker->Lock(); |
| #ifdef DRAWTHREAD |
| wait_for_thread(_draw_thread_id, &result); |
| #endif |
| free(_clips); |
| delete _buffer_locker; |
| } |
| |
| |
| /* * * * * OpenGL functionality * * * * */ |
| #if SDL_VIDEO_OPENGL |
| virtual BGLView *CreateGLView(Uint32 gl_flags) { |
| Lock(); |
| if (_SDL_GLView == NULL) { |
| _SDL_GLView = new BGLView(Bounds(), "SDL GLView", |
| B_FOLLOW_ALL_SIDES, |
| (B_WILL_DRAW | B_FRAME_EVENTS), |
| gl_flags); |
| } |
| AddChild(_SDL_GLView); |
| _SDL_GLView->EnableDirectMode(true); |
| _SDL_GLView->LockGL(); /* "New" GLViews are created */ |
| Unlock(); |
| return (_SDL_GLView); |
| } |
| |
| virtual void RemoveGLView() { |
| Lock(); |
| if(_SDL_GLView) { |
| _SDL_GLView->UnlockGL(); |
| RemoveChild(_SDL_GLView); |
| } |
| Unlock(); |
| } |
| |
| virtual void SwapBuffers(void) { |
| _SDL_GLView->UnlockGL(); |
| _SDL_GLView->LockGL(); |
| _SDL_GLView->SwapBuffers(); |
| } |
| #endif |
| |
| /* * * * * Framebuffering* * * * */ |
| virtual void DirectConnected(direct_buffer_info *info) { |
| if(!_connected && _connection_disabled) { |
| return; |
| } |
| |
| /* Determine if the pixel buffer is usable after this update */ |
| _trash_window_buffer = _trash_window_buffer |
| || ((info->buffer_state & B_BUFFER_RESIZED) |
| || (info->buffer_state & B_BUFFER_RESET) |
| || (info->driver_state == B_MODE_CHANGED)); |
| LockBuffer(); |
| |
| switch(info->buffer_state & B_DIRECT_MODE_MASK) { |
| case B_DIRECT_START: |
| _connected = true; |
| |
| case B_DIRECT_MODIFY: |
| if(_clips) { |
| free(_clips); |
| _clips = NULL; |
| } |
| |
| _num_clips = info->clip_list_count; |
| _clips = (clipping_rect *)malloc(_num_clips*sizeof(clipping_rect)); |
| if(_clips) { |
| memcpy(_clips, info->clip_list, |
| _num_clips*sizeof(clipping_rect)); |
| |
| _bits = (uint8*) info->bits; |
| _row_bytes = info->bytes_per_row; |
| _bounds = info->window_bounds; |
| _bytes_per_px = info->bits_per_pixel / 8; |
| _buffer_dirty = true; |
| } |
| break; |
| |
| case B_DIRECT_STOP: |
| _connected = false; |
| break; |
| } |
| #if SDL_VIDEO_OPENGL |
| if(_SDL_GLView) { |
| _SDL_GLView->DirectConnected(info); |
| } |
| #endif |
| |
| |
| /* Call the base object directconnected */ |
| BDirectWindow::DirectConnected(info); |
| |
| UnlockBuffer(); |
| |
| } |
| |
| |
| |
| |
| /* * * * * Event sending * * * * */ |
| /* Hook functions */ |
| virtual void FrameMoved(BPoint origin) { |
| /* Post a message to the BApp so that it can handle the window event */ |
| BMessage msg(BAPP_WINDOW_MOVED); |
| msg.AddInt32("window-x", (int)origin.x); |
| msg.AddInt32("window-y", (int)origin.y); |
| _PostWindowEvent(msg); |
| |
| /* Perform normal hook operations */ |
| BDirectWindow::FrameMoved(origin); |
| } |
| |
| virtual void FrameResized(float width, float height) { |
| /* Post a message to the BApp so that it can handle the window event */ |
| BMessage msg(BAPP_WINDOW_RESIZED); |
| |
| msg.AddInt32("window-w", (int)width + 1); |
| msg.AddInt32("window-h", (int)height + 1); |
| _PostWindowEvent(msg); |
| |
| /* Perform normal hook operations */ |
| BDirectWindow::FrameResized(width, height); |
| } |
| |
| virtual bool QuitRequested() { |
| BMessage msg(BAPP_WINDOW_CLOSE_REQUESTED); |
| _PostWindowEvent(msg); |
| |
| /* We won't allow a quit unless asked by DestroyWindow() */ |
| return false; |
| } |
| |
| virtual void WindowActivated(bool active) { |
| BMessage msg(BAPP_KEYBOARD_FOCUS); /* Mouse focus sold separately */ |
| _PostWindowEvent(msg); |
| } |
| |
| virtual void Zoom(BPoint origin, |
| float width, |
| float height) { |
| BMessage msg(BAPP_MAXIMIZE); /* Closest thing to maximization Haiku has */ |
| _PostWindowEvent(msg); |
| |
| /* Before the window zooms, record its size */ |
| if( !_prev_frame ) |
| _prev_frame = new BRect(Frame()); |
| |
| /* Perform normal hook operations */ |
| BDirectWindow::Zoom(origin, width, height); |
| } |
| |
| /* Member functions */ |
| virtual void Show() { |
| while(IsHidden()) { |
| BDirectWindow::Show(); |
| } |
| _shown = true; |
| |
| BMessage msg(BAPP_SHOW); |
| _PostWindowEvent(msg); |
| } |
| |
| virtual void Hide() { |
| BDirectWindow::Hide(); |
| _shown = false; |
| |
| BMessage msg(BAPP_HIDE); |
| _PostWindowEvent(msg); |
| } |
| |
| virtual void Minimize(bool minimize) { |
| BDirectWindow::Minimize(minimize); |
| int32 minState = (minimize ? BAPP_MINIMIZE : BAPP_RESTORE); |
| |
| BMessage msg(minState); |
| _PostWindowEvent(msg); |
| } |
| |
| |
| /* BView message interruption */ |
| virtual void DispatchMessage(BMessage * msg, BHandler * target) |
| { |
| BPoint where; /* Used by mouse moved */ |
| int32 buttons; /* Used for mouse button events */ |
| int32 key; /* Used for key events */ |
| |
| switch (msg->what) { |
| case B_MOUSE_MOVED: |
| int32 transit; |
| if (msg->FindPoint("where", &where) == B_OK |
| && msg->FindInt32("be:transit", &transit) == B_OK) { |
| _MouseMotionEvent(where, transit); |
| } |
| |
| /* FIXME: Apparently a button press/release event might be dropped |
| if made before before a different button is released. Does |
| B_MOUSE_MOVED have the data needed to check if a mouse button |
| state has changed? */ |
| if (msg->FindInt32("buttons", &buttons) == B_OK) { |
| _MouseButtonEvent(buttons); |
| } |
| break; |
| |
| case B_MOUSE_DOWN: |
| case B_MOUSE_UP: |
| /* _MouseButtonEvent() detects any and all buttons that may have |
| changed state, as well as that button's new state */ |
| if (msg->FindInt32("buttons", &buttons) == B_OK) { |
| _MouseButtonEvent(buttons); |
| } |
| break; |
| |
| case B_MOUSE_WHEEL_CHANGED: |
| float x, y; |
| if (msg->FindFloat("be:wheel_delta_x", &x) == B_OK |
| && msg->FindFloat("be:wheel_delta_y", &y) == B_OK) { |
| _MouseWheelEvent((int)x, (int)y); |
| } |
| break; |
| |
| case B_KEY_DOWN: |
| case B_UNMAPPED_KEY_DOWN: /* modifier keys are unmapped */ |
| if (msg->FindInt32("key", &key) == B_OK) { |
| _KeyEvent((SDL_Scancode)key, SDL_PRESSED); |
| } |
| break; |
| |
| case B_KEY_UP: |
| case B_UNMAPPED_KEY_UP: /* modifier keys are unmapped */ |
| if (msg->FindInt32("key", &key) == B_OK) { |
| _KeyEvent(key, SDL_RELEASED); |
| } |
| break; |
| |
| default: |
| /* move it after switch{} so it's always handled |
| that way we keep Haiku features like: |
| - CTRL+Q to close window (and other shortcuts) |
| - PrintScreen to make screenshot into /boot/home |
| - etc.. */ |
| /* BDirectWindow::DispatchMessage(msg, target); */ |
| break; |
| } |
| |
| BDirectWindow::DispatchMessage(msg, target); |
| } |
| |
| /* Handle command messages */ |
| virtual void MessageReceived(BMessage* message) { |
| switch (message->what) { |
| /* Handle commands from SDL */ |
| case BWIN_SET_TITLE: |
| _SetTitle(message); |
| break; |
| case BWIN_MOVE_WINDOW: |
| _MoveTo(message); |
| break; |
| case BWIN_RESIZE_WINDOW: |
| _ResizeTo(message); |
| break; |
| case BWIN_SET_BORDERED: |
| _SetBordered(message); |
| break; |
| case BWIN_SHOW_WINDOW: |
| Show(); |
| break; |
| case BWIN_HIDE_WINDOW: |
| Hide(); |
| break; |
| case BWIN_MAXIMIZE_WINDOW: |
| BWindow::Zoom(); |
| break; |
| case BWIN_MINIMIZE_WINDOW: |
| Minimize(true); |
| break; |
| case BWIN_RESTORE_WINDOW: |
| _Restore(); |
| break; |
| case BWIN_FULLSCREEN: |
| _SetFullScreen(message); |
| break; |
| default: |
| /* Perform normal message handling */ |
| BDirectWindow::MessageReceived(message); |
| break; |
| } |
| |
| } |
| |
| |
| |
| /* Accessor methods */ |
| bool IsShown() { return _shown; } |
| int32 GetID() { return _id; } |
| uint32 GetRowBytes() { return _row_bytes; } |
| int32 GetFbX() { return _bounds.left; } |
| int32 GetFbY() { return _bounds.top; } |
| bool ConnectionEnabled() { return !_connection_disabled; } |
| bool Connected() { return _connected; } |
| clipping_rect *GetClips() { return _clips; } |
| int32 GetNumClips() { return _num_clips; } |
| uint8* GetBufferPx() { return _bits; } |
| int32 GetBytesPerPx() { return _bytes_per_px; } |
| bool CanTrashWindowBuffer() { return _trash_window_buffer; } |
| bool BufferExists() { return _buffer_created; } |
| bool BufferIsDirty() { return _buffer_dirty; } |
| BBitmap *GetBitmap() { return _bitmap; } |
| #if SDL_VIDEO_OPENGL |
| BGLView *GetGLView() { return _SDL_GLView; } |
| #endif |
| |
| /* Setter methods */ |
| void SetID(int32 id) { _id = id; } |
| void SetBufferExists(bool bufferExists) { _buffer_created = bufferExists; } |
| void LockBuffer() { _buffer_locker->Lock(); } |
| void UnlockBuffer() { _buffer_locker->Unlock(); } |
| void SetBufferDirty(bool bufferDirty) { _buffer_dirty = bufferDirty; } |
| void SetTrashBuffer(bool trash) { _trash_window_buffer = trash; } |
| void SetBitmap(BBitmap *bitmap) { _bitmap = bitmap; } |
| |
| |
| private: |
| /* Event redirection */ |
| void _MouseMotionEvent(BPoint &where, int32 transit) { |
| if(transit == B_EXITED_VIEW) { |
| /* Change mouse focus */ |
| if(_mouse_focused) { |
| _MouseFocusEvent(false); |
| } |
| } else { |
| /* Change mouse focus */ |
| if (!_mouse_focused) { |
| _MouseFocusEvent(true); |
| } |
| BMessage msg(BAPP_MOUSE_MOVED); |
| msg.AddInt32("x", (int)where.x); |
| msg.AddInt32("y", (int)where.y); |
| |
| _PostWindowEvent(msg); |
| } |
| } |
| |
| void _MouseFocusEvent(bool focusGained) { |
| _mouse_focused = focusGained; |
| BMessage msg(BAPP_MOUSE_FOCUS); |
| msg.AddBool("focusGained", focusGained); |
| _PostWindowEvent(msg); |
| |
| /* FIXME: Why were these here? |
| if false: be_app->SetCursor(B_HAND_CURSOR); |
| if true: SDL_SetCursor(NULL); */ |
| } |
| |
| void _MouseButtonEvent(int32 buttons) { |
| int32 buttonStateChange = buttons ^ _last_buttons; |
| |
| /* Make sure at least one button has changed state */ |
| if( !(buttonStateChange) ) { |
| return; |
| } |
| |
| /* Add any mouse button events */ |
| if(buttonStateChange & B_PRIMARY_MOUSE_BUTTON) { |
| _SendMouseButton(SDL_BUTTON_LEFT, buttons & |
| B_PRIMARY_MOUSE_BUTTON); |
| } |
| if(buttonStateChange & B_SECONDARY_MOUSE_BUTTON) { |
| _SendMouseButton(SDL_BUTTON_RIGHT, buttons & |
| B_PRIMARY_MOUSE_BUTTON); |
| } |
| if(buttonStateChange & B_TERTIARY_MOUSE_BUTTON) { |
| _SendMouseButton(SDL_BUTTON_MIDDLE, buttons & |
| B_PRIMARY_MOUSE_BUTTON); |
| } |
| |
| _last_buttons = buttons; |
| } |
| |
| void _SendMouseButton(int32 button, int32 state) { |
| BMessage msg(BAPP_MOUSE_BUTTON); |
| msg.AddInt32("button-id", button); |
| msg.AddInt32("button-state", state); |
| _PostWindowEvent(msg); |
| } |
| |
| void _MouseWheelEvent(int32 x, int32 y) { |
| /* Create a message to pass along to the BeApp thread */ |
| BMessage msg(BAPP_MOUSE_WHEEL); |
| msg.AddInt32("xticks", x); |
| msg.AddInt32("yticks", y); |
| _PostWindowEvent(msg); |
| } |
| |
| void _KeyEvent(int32 keyCode, int32 keyState) { |
| /* Create a message to pass along to the BeApp thread */ |
| BMessage msg(BAPP_KEY); |
| msg.AddInt32("key-state", keyState); |
| msg.AddInt32("key-scancode", keyCode); |
| be_app->PostMessage(&msg); |
| /* Apparently SDL only uses the scancode */ |
| } |
| |
| void _RepaintEvent() { |
| /* Force a repaint: Call the SDL exposed event */ |
| BMessage msg(BAPP_REPAINT); |
| _PostWindowEvent(msg); |
| } |
| void _PostWindowEvent(BMessage &msg) { |
| msg.AddInt32("window-id", _id); |
| be_app->PostMessage(&msg); |
| } |
| |
| /* Command methods (functions called upon by SDL) */ |
| void _SetTitle(BMessage *msg) { |
| const char *title; |
| if( |
| msg->FindString("window-title", &title) != B_OK |
| ) { |
| return; |
| } |
| SetTitle(title); |
| } |
| |
| void _MoveTo(BMessage *msg) { |
| int32 x, y; |
| if( |
| msg->FindInt32("window-x", &x) != B_OK || |
| msg->FindInt32("window-y", &y) != B_OK |
| ) { |
| return; |
| } |
| MoveTo(x, y); |
| } |
| |
| void _ResizeTo(BMessage *msg) { |
| int32 w, h; |
| if( |
| msg->FindInt32("window-w", &w) != B_OK || |
| msg->FindInt32("window-h", &h) != B_OK |
| ) { |
| return; |
| } |
| ResizeTo(w, h); |
| } |
| |
| void _SetBordered(BMessage *msg) { |
| bool bEnabled; |
| if(msg->FindBool("window-border", &bEnabled) != B_OK) { |
| return; |
| } |
| SetLook(bEnabled ? B_BORDERED_WINDOW_LOOK : B_NO_BORDER_WINDOW_LOOK); |
| } |
| |
| void _Restore() { |
| if(IsMinimized()) { |
| Minimize(false); |
| } else if(IsHidden()) { |
| Show(); |
| } else if(_prev_frame != NULL) { /* Zoomed */ |
| MoveTo(_prev_frame->left, _prev_frame->top); |
| ResizeTo(_prev_frame->Width(), _prev_frame->Height()); |
| } |
| } |
| |
| void _SetFullScreen(BMessage *msg) { |
| bool fullscreen; |
| if( |
| msg->FindBool("fullscreen", &fullscreen) != B_OK |
| ) { |
| return; |
| } |
| SetFullScreen(fullscreen); |
| } |
| |
| /* Members */ |
| #if SDL_VIDEO_OPENGL |
| BGLView * _SDL_GLView; |
| #endif |
| |
| int32 _last_buttons; |
| int32 _id; /* Window id used by SDL_BApp */ |
| bool _mouse_focused; /* Does this window have mouse focus? */ |
| bool _shown; |
| bool _inhibit_resize; |
| |
| BRect *_prev_frame; /* Previous position and size of the window */ |
| |
| /* Framebuffer members */ |
| bool _connected, |
| _connection_disabled, |
| _buffer_created, |
| _buffer_dirty, |
| _trash_window_buffer; |
| uint8 *_bits; |
| uint32 _row_bytes; |
| clipping_rect _bounds; |
| BLocker *_buffer_locker; |
| clipping_rect *_clips; |
| int32 _num_clips; |
| int32 _bytes_per_px; |
| thread_id _draw_thread_id; |
| |
| BBitmap *_bitmap; |
| }; |
| |
| |
| /* FIXME: |
| * An explanation of framebuffer flags. |
| * |
| * _connected - Original variable used to let the drawing thread know |
| * when changes are being made to the other framebuffer |
| * members. |
| * _connection_disabled - Used to signal to the drawing thread that the window |
| * is closing, and the thread should exit. |
| * _buffer_created - True if the current buffer is valid |
| * _buffer_dirty - True if the window should be redrawn. |
| * _trash_window_buffer - True if the window buffer needs to be trashed partway |
| * through a draw cycle. Occurs when the previous |
| * buffer provided by DirectConnected() is invalidated. |
| */ |
| #endif |