diff --git a/modules/gui/skins2/macosx/macosx_dragdrop.hpp b/modules/gui/skins2/macosx/macosx_dragdrop.hpp new file mode 100644 index 000000000000..4b627f77c3a8 --- /dev/null +++ b/modules/gui/skins2/macosx/macosx_dragdrop.hpp @@ -0,0 +1,65 @@ +/***************************************************************************** + * macosx_dragdrop.hpp + ***************************************************************************** + * Copyright (C) 2024 the VideoLAN team + * + * Authors: VLC contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. + *****************************************************************************/ + +#ifndef MACOSX_DRAGDROP_HPP +#define MACOSX_DRAGDROP_HPP + +#include "../src/skin_common.hpp" +#include "../src/generic_window.hpp" + +#ifdef __OBJC__ +@class NSWindow; +@class VLCDropView; +#else +typedef void NSWindow; +typedef void VLCDropView; +#endif + +/// macOS drag and drop handler +class MacOSXDragDrop: public SkinObject +{ +public: + MacOSXDragDrop( intf_thread_t *pIntf, NSWindow *pWindow, + bool playOnDrop, GenericWindow *pWin ); + virtual ~MacOSXDragDrop(); + + /// Handle dropped files + void handleDrop( const char **files, int count ); + + /// Check if files should play immediately + bool getPlayOnDrop() const { return m_playOnDrop; } + + /// Get the associated GenericWindow + GenericWindow *getWindow() const { return m_pWin; } + +private: + /// Window + NSWindow *m_pWindow; + /// Drop view + VLCDropView *m_pDropView; + /// Should dropped files play immediately + bool m_playOnDrop; + /// Associated generic window + GenericWindow *m_pWin; +}; + +#endif diff --git a/modules/gui/skins2/macosx/macosx_dragdrop.mm b/modules/gui/skins2/macosx/macosx_dragdrop.mm new file mode 100644 index 000000000000..23432a518082 --- /dev/null +++ b/modules/gui/skins2/macosx/macosx_dragdrop.mm @@ -0,0 +1,185 @@ +/***************************************************************************** + * macosx_dragdrop.mm + ***************************************************************************** + * Copyright (C) 2024 the VideoLAN team + * + * Authors: VLC contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. + *****************************************************************************/ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#import + +#include "macosx_dragdrop.hpp" +#include "../commands/async_queue.hpp" +#include "../commands/cmd_add_item.hpp" + +#include + + +/// NSView subclass that handles drag and drop +@interface VLCDropView : NSView +{ + MacOSXDragDrop *m_pHandler; +} +- (instancetype)initWithFrame:(NSRect)frame handler:(MacOSXDragDrop *)handler; +@end + +@implementation VLCDropView + +- (instancetype)initWithFrame:(NSRect)frame handler:(MacOSXDragDrop *)handler +{ + self = [super initWithFrame:frame]; + if( self ) + { + m_pHandler = handler; + + // Register for drag types + [self registerForDraggedTypes:@[ + NSPasteboardTypeFileURL, + NSPasteboardTypeURL, + NSPasteboardTypeString + ]]; + } + return self; +} + +- (NSDragOperation)draggingEntered:(id)sender +{ + return NSDragOperationCopy; +} + +- (BOOL)performDragOperation:(id)sender +{ + NSPasteboard *pasteboard = [sender draggingPasteboard]; + + // Try to get file URLs + NSArray *fileURLs = [pasteboard readObjectsForClasses:@[[NSURL class]] + options:@{NSPasteboardURLReadingFileURLsOnlyKey: @YES}]; + + if( fileURLs && [fileURLs count] > 0 ) + { + NSMutableArray *paths = [NSMutableArray array]; + for( NSURL *url in fileURLs ) + { + [paths addObject:[url path]]; + } + + // Convert to C strings + int count = (int)[paths count]; + const char **files = (const char **)malloc( count * sizeof(char*) ); + for( int i = 0; i < count; i++ ) + { + files[i] = [[paths objectAtIndex:i] UTF8String]; + } + + if( m_pHandler ) + { + m_pHandler->handleDrop( files, count ); + } + + free( files ); + return YES; + } + + // Try generic URLs + NSArray *urls = [pasteboard readObjectsForClasses:@[[NSURL class]] options:nil]; + if( urls && [urls count] > 0 ) + { + int count = (int)[urls count]; + const char **files = (const char **)malloc( count * sizeof(char*) ); + for( int i = 0; i < count; i++ ) + { + NSURL *url = [urls objectAtIndex:i]; + files[i] = [[url absoluteString] UTF8String]; + } + + if( m_pHandler ) + { + m_pHandler->handleDrop( files, count ); + } + + free( files ); + return YES; + } + + return NO; +} + +@end + + +MacOSXDragDrop::MacOSXDragDrop( intf_thread_t *pIntf, NSWindow *pWindow, + bool playOnDrop, GenericWindow *pWin ): + SkinObject( pIntf ), m_pWindow( pWindow ), m_pDropView( nil ), + m_playOnDrop( playOnDrop ), m_pWin( pWin ) +{ + @autoreleasepool { + if( m_pWindow ) + { + // Create and add drop view + NSView *contentView = [m_pWindow contentView]; + NSRect frame = [contentView bounds]; + + m_pDropView = [[VLCDropView alloc] initWithFrame:frame handler:this]; + [m_pDropView setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable]; + [contentView addSubview:m_pDropView positioned:NSWindowAbove relativeTo:nil]; + } + } +} + + +MacOSXDragDrop::~MacOSXDragDrop() +{ + @autoreleasepool { + if( m_pDropView ) + { + [m_pDropView removeFromSuperview]; + m_pDropView = nil; + } + } +} + + +void MacOSXDragDrop::handleDrop( const char **files, int count ) +{ + for( int i = 0; i < count; i++ ) + { + const char *file = files[i]; + + // Convert path to URI if needed + char *uri = vlc_path2uri( file, NULL ); + if( !uri ) + { + // Might already be a URI + uri = strdup( file ); + } + + if( uri ) + { + bool playNow = m_playOnDrop && (i == 0); + + CmdAddItem *pCmd = new CmdAddItem( getIntf(), uri, playNow ); + AsyncQueue *pQueue = AsyncQueue::instance( getIntf() ); + pQueue->push( CmdGenericPtr( pCmd ) ); + + free( uri ); + } + } +} diff --git a/modules/gui/skins2/macosx/macosx_factory.hpp b/modules/gui/skins2/macosx/macosx_factory.hpp new file mode 100644 index 000000000000..463f7a2e0f62 --- /dev/null +++ b/modules/gui/skins2/macosx/macosx_factory.hpp @@ -0,0 +1,132 @@ +/***************************************************************************** + * macosx_factory.hpp + ***************************************************************************** + * Copyright (C) 2024 the VideoLAN team + * + * Authors: VLC contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. + *****************************************************************************/ + +#ifndef MACOSX_FACTORY_HPP +#define MACOSX_FACTORY_HPP + +#include "../src/os_factory.hpp" +#include "../src/generic_window.hpp" +#include + +class MacOSXTimerLoop; + +/// Class used to instantiate macOS specific objects +class MacOSXFactory: public OSFactory +{ +public: + MacOSXFactory( intf_thread_t *pIntf ); + virtual ~MacOSXFactory(); + + /// Initialization method + virtual bool init(); + + /// Instantiate an object OSGraphics + virtual OSGraphics *createOSGraphics( int width, int height ); + + /// Get the instance of the singleton OSLoop + virtual OSLoop *getOSLoop(); + + /// Destroy the instance of OSLoop + virtual void destroyOSLoop(); + + /// Instantiate an OSTimer with the given command + virtual OSTimer *createOSTimer( CmdGeneric &rCmd ); + + /// Minimize all the windows + virtual void minimize(); + + /// Restore the minimized windows + virtual void restore(); + + /// Add an icon in the system tray + virtual void addInTray(); + + /// Remove the icon from the system tray + virtual void removeFromTray(); + + /// Show the task in the task bar + virtual void addInTaskBar(); + + /// Remove the task from the task bar + virtual void removeFromTaskBar(); + + /// Instantiate an OSWindow object + virtual OSWindow *createOSWindow( GenericWindow &rWindow, + bool dragDrop, bool playOnDrop, + OSWindow *pParent, + GenericWindow::WindowType_t type ); + + /// Instantiate an object OSTooltip + virtual OSTooltip *createOSTooltip(); + + /// Instantiate an object OSPopup + virtual OSPopup *createOSPopup(); + + /// Get the directory separator + virtual const std::string &getDirSeparator() const { return m_dirSep; } + + /// Get the resource path + virtual const std::list &getResourcePath() const + { return m_resourcePath; } + + /// Get the screen size + virtual int getScreenWidth() const; + virtual int getScreenHeight() const; + + /// Get Monitor Information + virtual void getMonitorInfo( OSWindow *pWindow, + int* x, int* y, + int* width, int* height ) const; + virtual void getMonitorInfo( int numScreen, + int* x, int* y, + int* width, int* height ) const; + + /// Get the work area (screen area without taskbars) + virtual SkinsRect getWorkArea() const; + + /// Get the position of the mouse + virtual void getMousePos( int &rXPos, int &rYPos ) const; + + /// Change the cursor + virtual void changeCursor( CursorType_t type ) const; + + /// Delete a directory recursively + virtual void rmDir( const std::string &rPath ); + + /// Get the timer loop + MacOSXTimerLoop *getTimerLoop() const { return m_pTimerLoop; } + + /// Map to find the GenericWindow* associated with an NSWindow + std::map m_windowMap; + +private: + /// Timer loop + MacOSXTimerLoop *m_pTimerLoop; + /// Directory separator + const std::string m_dirSep; + /// Resource path + std::list m_resourcePath; + /// Screen dimensions + int m_screenWidth, m_screenHeight; +}; + +#endif diff --git a/modules/gui/skins2/macosx/macosx_factory.mm b/modules/gui/skins2/macosx/macosx_factory.mm new file mode 100644 index 000000000000..cb4f0ec71243 --- /dev/null +++ b/modules/gui/skins2/macosx/macosx_factory.mm @@ -0,0 +1,338 @@ +/***************************************************************************** + * macosx_factory.mm + ***************************************************************************** + * Copyright (C) 2024 the VideoLAN team + * + * Authors: VLC contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. + *****************************************************************************/ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#import + +#include "macosx_factory.hpp" +#include "macosx_graphics.hpp" +#include "macosx_loop.hpp" +#include "macosx_timer.hpp" +#include "macosx_window.hpp" +#include "macosx_tooltip.hpp" +#include "macosx_popup.hpp" +#include "../src/generic_window.hpp" + +#include +#include +#include + + +MacOSXFactory::MacOSXFactory( intf_thread_t *pIntf ): + OSFactory( pIntf ), m_pTimerLoop( NULL ), m_dirSep( "/" ) +{ + // Initialize the resource path + m_resourcePath.push_back( "share/skins2" ); + + // Get user's home directory for skins + const char *home = getenv( "HOME" ); + if( home ) + { + std::string userSkins = std::string(home) + "/Library/Application Support/org.videolan.vlc/skins2"; + m_resourcePath.push_back( userSkins ); + } + + // Add application bundle resource path + @autoreleasepool { + NSBundle *mainBundle = [NSBundle mainBundle]; + if( mainBundle ) + { + NSString *resourcePath = [mainBundle resourcePath]; + if( resourcePath ) + { + std::string bundlePath = [resourcePath UTF8String]; + bundlePath += "/share/skins2"; + m_resourcePath.push_back( bundlePath ); + } + } + } +} + + +MacOSXFactory::~MacOSXFactory() +{ + delete m_pTimerLoop; +} + + +bool MacOSXFactory::init() +{ + @autoreleasepool { + // Get screen dimensions + NSScreen *mainScreen = [NSScreen mainScreen]; + NSRect frame = [mainScreen frame]; + m_screenWidth = (int)frame.size.width; + m_screenHeight = (int)frame.size.height; + + // Create timer loop + m_pTimerLoop = new MacOSXTimerLoop( getIntf() ); + + return true; + } +} + + +OSGraphics *MacOSXFactory::createOSGraphics( int width, int height ) +{ + return new MacOSXGraphics( getIntf(), width, height ); +} + + +OSLoop *MacOSXFactory::getOSLoop() +{ + return MacOSXLoop::instance( getIntf() ); +} + + +void MacOSXFactory::destroyOSLoop() +{ + MacOSXLoop::destroy( getIntf() ); +} + + +void MacOSXFactory::minimize() +{ + @autoreleasepool { + // Minimize all application windows + for( NSWindow *window in [NSApp windows] ) + { + if( [window isVisible] && ![window isMiniaturized] ) + { + [window miniaturize:nil]; + } + } + } +} + + +void MacOSXFactory::restore() +{ + @autoreleasepool { + // Restore all minimized windows + for( NSWindow *window in [NSApp windows] ) + { + if( [window isMiniaturized] ) + { + [window deminiaturize:nil]; + } + } + } +} + + +void MacOSXFactory::addInTray() +{ + // macOS uses the Dock icon and menu bar status items + // This is a simplified implementation + msg_Dbg( getIntf(), "addInTray() called - macOS uses Dock" ); +} + + +void MacOSXFactory::removeFromTray() +{ + msg_Dbg( getIntf(), "removeFromTray() called - macOS uses Dock" ); +} + + +void MacOSXFactory::addInTaskBar() +{ + @autoreleasepool { + [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular]; + } +} + + +void MacOSXFactory::removeFromTaskBar() +{ + @autoreleasepool { + [NSApp setActivationPolicy:NSApplicationActivationPolicyAccessory]; + } +} + + +OSTimer *MacOSXFactory::createOSTimer( CmdGeneric &rCmd ) +{ + return new MacOSXTimer( getIntf(), rCmd, m_pTimerLoop ); +} + + +OSWindow *MacOSXFactory::createOSWindow( GenericWindow &rWindow, + bool dragDrop, bool playOnDrop, + OSWindow *pParent, + GenericWindow::WindowType_t type ) +{ + return new MacOSXWindow( getIntf(), rWindow, dragDrop, playOnDrop, + static_cast(pParent), type ); +} + + +OSTooltip *MacOSXFactory::createOSTooltip() +{ + return new MacOSXTooltip( getIntf() ); +} + + +OSPopup *MacOSXFactory::createOSPopup() +{ + return new MacOSXPopup( getIntf() ); +} + + +int MacOSXFactory::getScreenWidth() const +{ + return m_screenWidth; +} + + +int MacOSXFactory::getScreenHeight() const +{ + return m_screenHeight; +} + + +void MacOSXFactory::getMonitorInfo( OSWindow *pWindow, + int* x, int* y, + int* width, int* height ) const +{ + @autoreleasepool { + MacOSXWindow *pMacWindow = static_cast(pWindow); + NSWindow *nsWindow = pMacWindow ? pMacWindow->getNSWindow() : nil; + NSScreen *screen = nsWindow ? [nsWindow screen] : [NSScreen mainScreen]; + + if( !screen ) + screen = [NSScreen mainScreen]; + + NSRect frame = [screen frame]; + *x = (int)frame.origin.x; + *y = (int)(m_screenHeight - frame.origin.y - frame.size.height); + *width = (int)frame.size.width; + *height = (int)frame.size.height; + } +} + + +void MacOSXFactory::getMonitorInfo( int numScreen, + int* x, int* y, + int* width, int* height ) const +{ + @autoreleasepool { + NSArray *screens = [NSScreen screens]; + NSScreen *screen; + + if( numScreen >= 0 && numScreen < (int)[screens count] ) + screen = [screens objectAtIndex:numScreen]; + else + screen = [NSScreen mainScreen]; + + NSRect frame = [screen frame]; + *x = (int)frame.origin.x; + *y = (int)(m_screenHeight - frame.origin.y - frame.size.height); + *width = (int)frame.size.width; + *height = (int)frame.size.height; + } +} + + +SkinsRect MacOSXFactory::getWorkArea() const +{ + @autoreleasepool { + NSScreen *mainScreen = [NSScreen mainScreen]; + NSRect visibleFrame = [mainScreen visibleFrame]; + NSRect fullFrame = [mainScreen frame]; + + // Convert from Cocoa coordinates (origin at bottom-left) + // to skin coordinates (origin at top-left) + int x = (int)visibleFrame.origin.x; + int y = (int)(fullFrame.size.height - visibleFrame.origin.y - visibleFrame.size.height); + int w = (int)visibleFrame.size.width; + int h = (int)visibleFrame.size.height; + + return SkinsRect( x, y, x + w, y + h ); + } +} + + +void MacOSXFactory::getMousePos( int &rXPos, int &rYPos ) const +{ + @autoreleasepool { + NSPoint mouseLocation = [NSEvent mouseLocation]; + rXPos = (int)mouseLocation.x; + // Convert from Cocoa coordinates + rYPos = (int)(m_screenHeight - mouseLocation.y); + } +} + + +void MacOSXFactory::changeCursor( CursorType_t type ) const +{ + @autoreleasepool { + NSCursor *cursor = nil; + + switch( type ) + { + case kDefaultArrow: + cursor = [NSCursor arrowCursor]; + break; + case kResizeNS: + cursor = [NSCursor resizeUpDownCursor]; + break; + case kResizeWE: + cursor = [NSCursor resizeLeftRightCursor]; + break; + case kResizeNWSE: + // macOS doesn't have a direct equivalent, use crosshair + cursor = [NSCursor crosshairCursor]; + break; + case kResizeNESW: + cursor = [NSCursor crosshairCursor]; + break; + case kNoCursor: + [NSCursor hide]; + return; + default: + cursor = [NSCursor arrowCursor]; + break; + } + + [NSCursor unhide]; + [cursor set]; + } +} + + +void MacOSXFactory::rmDir( const std::string &rPath ) +{ + @autoreleasepool { + NSFileManager *fileManager = [NSFileManager defaultManager]; + NSString *path = [NSString stringWithUTF8String:rPath.c_str()]; + NSError *error = nil; + [fileManager removeItemAtPath:path error:&error]; + if( error ) + { + msg_Warn( getIntf(), "Failed to remove directory: %s", + [[error localizedDescription] UTF8String] ); + } + } +} diff --git a/modules/gui/skins2/macosx/macosx_graphics.hpp b/modules/gui/skins2/macosx/macosx_graphics.hpp new file mode 100644 index 000000000000..ea437f751b22 --- /dev/null +++ b/modules/gui/skins2/macosx/macosx_graphics.hpp @@ -0,0 +1,97 @@ +/***************************************************************************** + * macosx_graphics.hpp + ***************************************************************************** + * Copyright (C) 2024 the VideoLAN team + * + * Authors: VLC contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. + *****************************************************************************/ + +#ifndef MACOSX_GRAPHICS_HPP +#define MACOSX_GRAPHICS_HPP + +#include "../src/os_graphics.hpp" + +#include + +class GenericBitmap; + +/// macOS implementation of OSGraphics using Core Graphics +class MacOSXGraphics: public OSGraphics +{ +public: + MacOSXGraphics( intf_thread_t *pIntf, int width, int height ); + virtual ~MacOSXGraphics(); + + /// Clear the graphics + virtual void clear( int xDest = 0, int yDest = 0, + int width = -1, int height = -1 ); + + /// Draw another graphics on this one + virtual void drawGraphics( const OSGraphics &rGraphics, int xSrc = 0, + int ySrc = 0, int xDest = 0, int yDest = 0, + int width = -1, int height = -1 ); + + /// Render a bitmap on this graphics + virtual void drawBitmap( const GenericBitmap &rBitmap, int xSrc = 0, + int ySrc = 0, int xDest = 0, int yDest = 0, + int width = -1, int height = -1, + bool blend = false ); + + /// Draw a filled rectangle on the graphics (color is #RRGGBB) + virtual void fillRect( int left, int top, int width, int height, + uint32_t color ); + + /// Draw an empty rectangle on the graphics (color is #RRGGBB) + virtual void drawRect( int left, int top, int width, int height, + uint32_t color ); + + /// Set the shape of a window with the mask of this graphics + virtual void applyMaskToWindow( OSWindow &rWindow ); + + /// Copy the graphics on a window + virtual void copyToWindow( OSWindow &rWindow, int xSrc, + int ySrc, int width, int height, + int xDest, int yDest ); + + /// Tell whether the pixel at the given position is visible + virtual bool hit( int x, int y ) const; + + /// Getters + virtual int getWidth() const { return m_width; } + virtual int getHeight() const { return m_height; } + + /// Get the raw pixel data + const uint8_t *getData() const { return m_pData; } + + /// Get the image (for internal use) + void *getImage() const; + +private: + /// Width and height of the graphics + int m_width, m_height; + /// Raw pixel data (BGRA format) + uint8_t *m_pData; + /// Bytes per row + int m_bytesPerRow; + + /// Check and adjust boundaries + bool checkBoundaries( int x_src, int y_src, int w_src, int h_src, + int& x_target, int& y_target, + int& w_target, int& h_target ); +}; + +#endif diff --git a/modules/gui/skins2/macosx/macosx_graphics.mm b/modules/gui/skins2/macosx/macosx_graphics.mm new file mode 100644 index 000000000000..60e815ff9dcf --- /dev/null +++ b/modules/gui/skins2/macosx/macosx_graphics.mm @@ -0,0 +1,454 @@ +/***************************************************************************** + * macosx_graphics.mm + ***************************************************************************** + * Copyright (C) 2024 the VideoLAN team + * + * Authors: VLC contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. + *****************************************************************************/ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#import +#import + +#include "macosx_graphics.hpp" +#include "macosx_window.hpp" +#include "../src/generic_bitmap.hpp" + +#include + + +MacOSXGraphics::MacOSXGraphics( intf_thread_t *pIntf, int width, int height ): + OSGraphics( pIntf ), m_width( width ), m_height( height ) +{ + // Allocate BGRA pixel buffer + m_bytesPerRow = width * 4; + m_pData = new uint8_t[m_bytesPerRow * height]; + memset( m_pData, 0, m_bytesPerRow * height ); +} + + +MacOSXGraphics::~MacOSXGraphics() +{ + delete[] m_pData; +} + + +void MacOSXGraphics::clear( int xDest, int yDest, int width, int height ) +{ + if( width == -1 ) + width = m_width; + if( height == -1 ) + height = m_height; + + // Clamp to valid range + if( xDest < 0 ) + { + width += xDest; + xDest = 0; + } + if( yDest < 0 ) + { + height += yDest; + yDest = 0; + } + if( xDest + width > m_width ) + width = m_width - xDest; + if( yDest + height > m_height ) + height = m_height - yDest; + + if( width <= 0 || height <= 0 ) + return; + + // Clear the specified region to transparent + for( int y = yDest; y < yDest + height; y++ ) + { + memset( m_pData + y * m_bytesPerRow + xDest * 4, 0, width * 4 ); + } +} + + +void MacOSXGraphics::drawGraphics( const OSGraphics &rGraphics, + int xSrc, int ySrc, + int xDest, int yDest, + int width, int height ) +{ + const MacOSXGraphics &rSrc = static_cast(rGraphics); + + if( width == -1 ) + width = rSrc.getWidth(); + if( height == -1 ) + height = rSrc.getHeight(); + + int srcWidth = rSrc.getWidth(); + int srcHeight = rSrc.getHeight(); + + if( !checkBoundaries( xSrc, ySrc, srcWidth, srcHeight, + xDest, yDest, width, height ) ) + return; + + const uint8_t *pSrcData = rSrc.getData(); + + // Copy pixel data with alpha blending + for( int y = 0; y < height; y++ ) + { + for( int x = 0; x < width; x++ ) + { + int srcOffset = (ySrc + y) * rSrc.m_bytesPerRow + (xSrc + x) * 4; + int dstOffset = (yDest + y) * m_bytesPerRow + (xDest + x) * 4; + + // Simple copy (source over destination) + uint8_t srcAlpha = pSrcData[srcOffset + 3]; + if( srcAlpha == 255 ) + { + // Fully opaque - just copy + memcpy( m_pData + dstOffset, pSrcData + srcOffset, 4 ); + } + else if( srcAlpha > 0 ) + { + // Alpha blend + uint8_t dstAlpha = m_pData[dstOffset + 3]; + uint8_t outAlpha = srcAlpha + (dstAlpha * (255 - srcAlpha)) / 255; + + if( outAlpha > 0 ) + { + for( int c = 0; c < 3; c++ ) + { + m_pData[dstOffset + c] = (uint8_t)( + (pSrcData[srcOffset + c] * srcAlpha + + m_pData[dstOffset + c] * dstAlpha * (255 - srcAlpha) / 255) / outAlpha + ); + } + m_pData[dstOffset + 3] = outAlpha; + } + } + } + } +} + + +void MacOSXGraphics::drawBitmap( const GenericBitmap &rBitmap, + int xSrc, int ySrc, + int xDest, int yDest, + int width, int height, + bool blend ) +{ + int srcWidth = rBitmap.getWidth(); + int srcHeight = rBitmap.getHeight(); + + if( width == -1 ) + width = srcWidth; + if( height == -1 ) + height = srcHeight; + + if( !checkBoundaries( xSrc, ySrc, srcWidth, srcHeight, + xDest, yDest, width, height ) ) + return; + + // Get bitmap data + uint8_t *pBmpData = rBitmap.getData(); + if( !pBmpData ) + return; + + int bmpBytesPerRow = srcWidth * 4; + + // Copy/blend pixel data + for( int y = 0; y < height; y++ ) + { + for( int x = 0; x < width; x++ ) + { + int srcOffset = (ySrc + y) * bmpBytesPerRow + (xSrc + x) * 4; + int dstOffset = (yDest + y) * m_bytesPerRow + (xDest + x) * 4; + + uint8_t srcAlpha = pBmpData[srcOffset + 3]; + + if( !blend || srcAlpha == 255 ) + { + // Direct copy + m_pData[dstOffset + 0] = pBmpData[srcOffset + 0]; // B + m_pData[dstOffset + 1] = pBmpData[srcOffset + 1]; // G + m_pData[dstOffset + 2] = pBmpData[srcOffset + 2]; // R + m_pData[dstOffset + 3] = srcAlpha; + } + else if( srcAlpha > 0 ) + { + // Alpha blend + uint8_t dstAlpha = m_pData[dstOffset + 3]; + uint8_t outAlpha = srcAlpha + (dstAlpha * (255 - srcAlpha)) / 255; + + if( outAlpha > 0 ) + { + for( int c = 0; c < 3; c++ ) + { + m_pData[dstOffset + c] = (uint8_t)( + (pBmpData[srcOffset + c] * srcAlpha + + m_pData[dstOffset + c] * dstAlpha * (255 - srcAlpha) / 255) / outAlpha + ); + } + m_pData[dstOffset + 3] = outAlpha; + } + } + } + } +} + + +void MacOSXGraphics::fillRect( int left, int top, int width, int height, + uint32_t color ) +{ + // Clamp to valid range + if( left < 0 ) + { + width += left; + left = 0; + } + if( top < 0 ) + { + height += top; + top = 0; + } + if( left + width > m_width ) + width = m_width - left; + if( top + height > m_height ) + height = m_height - top; + + if( width <= 0 || height <= 0 ) + return; + + // Extract color components (color is #RRGGBB) + uint8_t r = (color >> 16) & 0xFF; + uint8_t g = (color >> 8) & 0xFF; + uint8_t b = color & 0xFF; + + // Fill the rectangle + for( int y = top; y < top + height; y++ ) + { + for( int x = left; x < left + width; x++ ) + { + int offset = y * m_bytesPerRow + x * 4; + m_pData[offset + 0] = b; + m_pData[offset + 1] = g; + m_pData[offset + 2] = r; + m_pData[offset + 3] = 255; + } + } +} + + +void MacOSXGraphics::drawRect( int left, int top, int width, int height, + uint32_t color ) +{ + // Extract color components + uint8_t r = (color >> 16) & 0xFF; + uint8_t g = (color >> 8) & 0xFF; + uint8_t b = color & 0xFF; + + // Draw the rectangle outline + // Top and bottom edges + for( int x = left; x < left + width && x < m_width; x++ ) + { + if( x >= 0 ) + { + if( top >= 0 && top < m_height ) + { + int offset = top * m_bytesPerRow + x * 4; + m_pData[offset + 0] = b; + m_pData[offset + 1] = g; + m_pData[offset + 2] = r; + m_pData[offset + 3] = 255; + } + if( top + height - 1 >= 0 && top + height - 1 < m_height ) + { + int offset = (top + height - 1) * m_bytesPerRow + x * 4; + m_pData[offset + 0] = b; + m_pData[offset + 1] = g; + m_pData[offset + 2] = r; + m_pData[offset + 3] = 255; + } + } + } + // Left and right edges + for( int y = top; y < top + height && y < m_height; y++ ) + { + if( y >= 0 ) + { + if( left >= 0 && left < m_width ) + { + int offset = y * m_bytesPerRow + left * 4; + m_pData[offset + 0] = b; + m_pData[offset + 1] = g; + m_pData[offset + 2] = r; + m_pData[offset + 3] = 255; + } + if( left + width - 1 >= 0 && left + width - 1 < m_width ) + { + int offset = y * m_bytesPerRow + (left + width - 1) * 4; + m_pData[offset + 0] = b; + m_pData[offset + 1] = g; + m_pData[offset + 2] = r; + m_pData[offset + 3] = 255; + } + } + } +} + + +void MacOSXGraphics::applyMaskToWindow( OSWindow &rWindow ) +{ + // macOS handles transparency through the alpha channel directly + // The window's opaque property should be set to NO + // This is handled in copyToWindow +} + + +void MacOSXGraphics::copyToWindow( OSWindow &rWindow, int xSrc, int ySrc, + int width, int height, + int xDest, int yDest ) +{ + @autoreleasepool { + MacOSXWindow &rMacWindow = static_cast(rWindow); + NSWindow *nsWindow = rMacWindow.getNSWindow(); + + if( !nsWindow ) + return; + + // Create CGImage from our pixel data + CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); + CGContextRef context = CGBitmapContextCreate( + m_pData, + m_width, + m_height, + 8, + m_bytesPerRow, + colorSpace, + kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Little + ); + + if( context ) + { + CGImageRef image = CGBitmapContextCreateImage( context ); + if( image ) + { + // Create subimage if needed + CGImageRef subImage = image; + if( xSrc != 0 || ySrc != 0 || width != m_width || height != m_height ) + { + CGRect subRect = CGRectMake( xSrc, ySrc, width, height ); + subImage = CGImageCreateWithImageInRect( image, subRect ); + } + + // Update the window's content view + NSView *contentView = [nsWindow contentView]; + if( [contentView isKindOfClass:[NSImageView class]] ) + { + NSImage *nsImage = [[NSImage alloc] initWithCGImage: + (subImage ? subImage : image) + size:NSMakeSize(width, height)]; + [(NSImageView *)contentView setImage:nsImage]; + } + else + { + // Create a layer-backed view for better performance + [contentView setWantsLayer:YES]; + contentView.layer.contents = (__bridge id)(subImage ? subImage : image); + } + + if( subImage != image ) + CGImageRelease( subImage ); + CGImageRelease( image ); + } + CGContextRelease( context ); + } + CGColorSpaceRelease( colorSpace ); + } +} + + +bool MacOSXGraphics::hit( int x, int y ) const +{ + if( x < 0 || y < 0 || x >= m_width || y >= m_height ) + return false; + + // Check if the pixel has non-zero alpha + int offset = y * m_bytesPerRow + x * 4; + return m_pData[offset + 3] > 0; +} + + +void *MacOSXGraphics::getImage() const +{ + @autoreleasepool { + CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); + CGContextRef context = CGBitmapContextCreate( + const_cast(m_pData), + m_width, + m_height, + 8, + m_bytesPerRow, + colorSpace, + kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Little + ); + + CGImageRef image = NULL; + if( context ) + { + image = CGBitmapContextCreateImage( context ); + CGContextRelease( context ); + } + CGColorSpaceRelease( colorSpace ); + + return image; + } +} + + +bool MacOSXGraphics::checkBoundaries( int x_src, int y_src, int w_src, int h_src, + int& x_target, int& y_target, + int& w_target, int& h_target ) +{ + // Check for valid source + if( x_src < 0 || y_src < 0 ) + return false; + + // Adjust for source boundaries + if( x_src + w_target > w_src ) + w_target = w_src - x_src; + if( y_src + h_target > h_src ) + h_target = h_src - y_src; + + // Adjust for negative destination + if( x_target < 0 ) + { + w_target += x_target; + x_target = 0; + } + if( y_target < 0 ) + { + h_target += y_target; + y_target = 0; + } + + // Adjust for destination boundaries + if( x_target + w_target > m_width ) + w_target = m_width - x_target; + if( y_target + h_target > m_height ) + h_target = m_height - y_target; + + return w_target > 0 && h_target > 0; +} diff --git a/modules/gui/skins2/macosx/macosx_loop.hpp b/modules/gui/skins2/macosx/macosx_loop.hpp new file mode 100644 index 000000000000..97a6546a065e --- /dev/null +++ b/modules/gui/skins2/macosx/macosx_loop.hpp @@ -0,0 +1,75 @@ +/***************************************************************************** + * macosx_loop.hpp + ***************************************************************************** + * Copyright (C) 2024 the VideoLAN team + * + * Authors: VLC contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. + *****************************************************************************/ + +#ifndef MACOSX_LOOP_HPP +#define MACOSX_LOOP_HPP + +#include "../src/os_loop.hpp" + +#include + +class GenericWindow; + +/// Main event loop for macOS (singleton) +class MacOSXLoop: public OSLoop +{ +public: + /// Get the instance of MacOSXLoop + static OSLoop *instance( intf_thread_t *pIntf ); + + /// Destroy the instance of MacOSXLoop + static void destroy( intf_thread_t *pIntf ); + + /// Enter the event loop + virtual void run(); + + /// Exit the main loop + virtual void exit(); + +private: + MacOSXLoop( intf_thread_t *pIntf ); + virtual ~MacOSXLoop(); + + /// Flag to exit the loop + bool m_exit; + + /// Screen height for coordinate conversion + int m_screenHeight; + + /// Date and position of the last left-click + vlc_tick_t m_lastClickTime; + int m_lastClickPosX, m_lastClickPosY; + + /// Maximum interval between clicks for a double-click (in microsec) + static int m_dblClickDelay; + + /// Handle an NSEvent + void handleEvent( void *pEvent ); + + /// Convert NSEvent modifiers to VLC key modifiers + static int cocoaModToMod( unsigned int flags ); + + /// Convert NSEvent key code to VLC key code + static int cocoaKeyToVlcKey( unsigned short keyCode, unsigned int flags ); +}; + +#endif diff --git a/modules/gui/skins2/macosx/macosx_loop.mm b/modules/gui/skins2/macosx/macosx_loop.mm new file mode 100644 index 000000000000..08b5bdd74d77 --- /dev/null +++ b/modules/gui/skins2/macosx/macosx_loop.mm @@ -0,0 +1,353 @@ +/***************************************************************************** + * macosx_loop.mm + ***************************************************************************** + * Copyright (C) 2024 the VideoLAN team + * + * Authors: VLC contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. + *****************************************************************************/ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#import +#import + +#include "macosx_loop.hpp" +#include "macosx_factory.hpp" +#include "macosx_window.hpp" +#include "../src/os_factory.hpp" +#include "../src/generic_window.hpp" +#include "../events/evt_key.hpp" +#include "../events/evt_mouse.hpp" +#include "../events/evt_scroll.hpp" +#include "../events/evt_motion.hpp" +#include "../events/evt_leave.hpp" +#include "../events/evt_focus.hpp" +#include "../events/evt_refresh.hpp" + +#include + + +// Double-click delay in microseconds +int MacOSXLoop::m_dblClickDelay = 400000; + + +MacOSXLoop::MacOSXLoop( intf_thread_t *pIntf ): + OSLoop( pIntf ), m_exit( false ), + m_lastClickTime( 0 ), m_lastClickPosX( 0 ), m_lastClickPosY( 0 ) +{ + @autoreleasepool { + NSScreen *mainScreen = [NSScreen mainScreen]; + m_screenHeight = (int)[mainScreen frame].size.height; + } +} + + +MacOSXLoop::~MacOSXLoop() +{ +} + + +OSLoop *MacOSXLoop::instance( intf_thread_t *pIntf ) +{ + if( !pIntf->p_sys->p_osLoop ) + { + pIntf->p_sys->p_osLoop.reset( new MacOSXLoop( pIntf ) ); + } + return pIntf->p_sys->p_osLoop.get(); +} + + +void MacOSXLoop::destroy( intf_thread_t *pIntf ) +{ + pIntf->p_sys->p_osLoop.reset(); +} + + +void MacOSXLoop::run() +{ + @autoreleasepool { + // Main event loop + while( !m_exit ) + { + @autoreleasepool { + NSEvent *event = [NSApp nextEventMatchingMask:NSEventMaskAny + untilDate:[NSDate dateWithTimeIntervalSinceNow:0.01] + inMode:NSDefaultRunLoopMode + dequeue:YES]; + if( event ) + { + handleEvent( (__bridge void *)event ); + [NSApp sendEvent:event]; + } + + // Process timers + MacOSXFactory *pFactory = static_cast( + OSFactory::instance( getIntf() ) ); + if( pFactory && pFactory->getTimerLoop() ) + { + pFactory->getTimerLoop()->checkTimers(); + } + } + } + } +} + + +void MacOSXLoop::exit() +{ + m_exit = true; +} + + +void MacOSXLoop::handleEvent( void *pEvent ) +{ + @autoreleasepool { + NSEvent *event = (__bridge NSEvent *)pEvent; + + // Get the window + NSWindow *nsWindow = [event window]; + if( !nsWindow ) + return; + + // Find the associated GenericWindow + MacOSXFactory *pFactory = static_cast( + OSFactory::instance( getIntf() ) ); + if( !pFactory ) + return; + + auto it = pFactory->m_windowMap.find( (__bridge void *)nsWindow ); + if( it == pFactory->m_windowMap.end() ) + return; + + GenericWindow *pWin = it->second; + if( !pWin ) + return; + + // Get mouse position in window coordinates + NSPoint locationInWindow = [event locationInWindow]; + NSView *contentView = [nsWindow contentView]; + NSRect bounds = [contentView bounds]; + + // Convert to skins coordinates (origin top-left) + int x = (int)locationInWindow.x; + int y = (int)(bounds.size.height - locationInWindow.y); + + // Handle the event + NSEventType eventType = [event type]; + + switch( eventType ) + { + case NSEventTypeLeftMouseDown: + { + vlc_tick_t time = vlc_tick_now(); + int xPos = x; + int yPos = y; + + // Check for double-click + EvtMouse::ActionType action = EvtMouse::kDown; + if( time - m_lastClickTime < m_dblClickDelay && + xPos == m_lastClickPosX && yPos == m_lastClickPosY ) + { + action = EvtMouse::kDblClick; + m_lastClickTime = 0; + } + else + { + m_lastClickTime = time; + m_lastClickPosX = xPos; + m_lastClickPosY = yPos; + } + + EvtMouse evt( getIntf(), xPos, yPos, EvtMouse::kLeft, action ); + pWin->processEvent( evt ); + break; + } + + case NSEventTypeLeftMouseUp: + { + EvtMouse evt( getIntf(), x, y, EvtMouse::kLeft, EvtMouse::kUp ); + pWin->processEvent( evt ); + break; + } + + case NSEventTypeRightMouseDown: + { + EvtMouse evt( getIntf(), x, y, EvtMouse::kRight, EvtMouse::kDown ); + pWin->processEvent( evt ); + break; + } + + case NSEventTypeRightMouseUp: + { + EvtMouse evt( getIntf(), x, y, EvtMouse::kRight, EvtMouse::kUp ); + pWin->processEvent( evt ); + break; + } + + case NSEventTypeOtherMouseDown: + { + EvtMouse evt( getIntf(), x, y, EvtMouse::kMiddle, EvtMouse::kDown ); + pWin->processEvent( evt ); + break; + } + + case NSEventTypeOtherMouseUp: + { + EvtMouse evt( getIntf(), x, y, EvtMouse::kMiddle, EvtMouse::kUp ); + pWin->processEvent( evt ); + break; + } + + case NSEventTypeMouseMoved: + case NSEventTypeLeftMouseDragged: + case NSEventTypeRightMouseDragged: + case NSEventTypeOtherMouseDragged: + { + EvtMotion evt( getIntf(), x, y ); + pWin->processEvent( evt ); + break; + } + + case NSEventTypeMouseEntered: + { + EvtMotion evt( getIntf(), x, y ); + pWin->processEvent( evt ); + break; + } + + case NSEventTypeMouseExited: + { + EvtLeave evt( getIntf() ); + pWin->processEvent( evt ); + break; + } + + case NSEventTypeScrollWheel: + { + CGFloat deltaY = [event deltaY]; + int direction = (deltaY > 0) ? EvtScroll::kUp : EvtScroll::kDown; + int mod = cocoaModToMod( [event modifierFlags] ); + EvtScroll evt( getIntf(), x, y, direction, mod ); + pWin->processEvent( evt ); + break; + } + + case NSEventTypeKeyDown: + { + int mod = cocoaModToMod( [event modifierFlags] ); + int key = cocoaKeyToVlcKey( [event keyCode], [event modifierFlags] ); + + // Also handle character input + NSString *chars = [event charactersIgnoringModifiers]; + if( [chars length] > 0 ) + { + unichar c = [chars characterAtIndex:0]; + if( c >= 32 && c < 127 ) + { + key = c; + } + } + + EvtKey evt( getIntf(), key, EvtKey::kDown, mod ); + pWin->processEvent( evt ); + break; + } + + case NSEventTypeKeyUp: + { + int mod = cocoaModToMod( [event modifierFlags] ); + int key = cocoaKeyToVlcKey( [event keyCode], [event modifierFlags] ); + + NSString *chars = [event charactersIgnoringModifiers]; + if( [chars length] > 0 ) + { + unichar c = [chars characterAtIndex:0]; + if( c >= 32 && c < 127 ) + { + key = c; + } + } + + EvtKey evt( getIntf(), key, EvtKey::kUp, mod ); + pWin->processEvent( evt ); + break; + } + + default: + break; + } + } +} + + +int MacOSXLoop::cocoaModToMod( unsigned int flags ) +{ + int mod = 0; + + if( flags & NSEventModifierFlagShift ) + mod |= KEY_MODIFIER_SHIFT; + if( flags & NSEventModifierFlagControl ) + mod |= KEY_MODIFIER_CTRL; + if( flags & NSEventModifierFlagOption ) + mod |= KEY_MODIFIER_ALT; + if( flags & NSEventModifierFlagCommand ) + mod |= KEY_MODIFIER_META; + + return mod; +} + + +int MacOSXLoop::cocoaKeyToVlcKey( unsigned short keyCode, unsigned int flags ) +{ + // Map macOS key codes to VLC key codes + switch( keyCode ) + { + case kVK_Return: return KEY_ENTER; + case kVK_Tab: return KEY_TAB; + case kVK_Delete: return KEY_BACKSPACE; + case kVK_ForwardDelete: return KEY_DELETE; + case kVK_Escape: return KEY_ESC; + case kVK_Space: return ' '; + + case kVK_UpArrow: return KEY_UP; + case kVK_DownArrow: return KEY_DOWN; + case kVK_LeftArrow: return KEY_LEFT; + case kVK_RightArrow: return KEY_RIGHT; + + case kVK_Home: return KEY_HOME; + case kVK_End: return KEY_END; + case kVK_PageUp: return KEY_PAGEUP; + case kVK_PageDown: return KEY_PAGEDOWN; + + case kVK_F1: return KEY_F1; + case kVK_F2: return KEY_F2; + case kVK_F3: return KEY_F3; + case kVK_F4: return KEY_F4; + case kVK_F5: return KEY_F5; + case kVK_F6: return KEY_F6; + case kVK_F7: return KEY_F7; + case kVK_F8: return KEY_F8; + case kVK_F9: return KEY_F9; + case kVK_F10: return KEY_F10; + case kVK_F11: return KEY_F11; + case kVK_F12: return KEY_F12; + + default: return 0; + } +} diff --git a/modules/gui/skins2/macosx/macosx_popup.hpp b/modules/gui/skins2/macosx/macosx_popup.hpp new file mode 100644 index 000000000000..f514bdc36834 --- /dev/null +++ b/modules/gui/skins2/macosx/macosx_popup.hpp @@ -0,0 +1,67 @@ +/***************************************************************************** + * macosx_popup.hpp + ***************************************************************************** + * Copyright (C) 2024 the VideoLAN team + * + * Authors: VLC contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. + *****************************************************************************/ + +#ifndef MACOSX_POPUP_HPP +#define MACOSX_POPUP_HPP + +#include "../src/os_popup.hpp" + +#include + +#ifdef __OBJC__ +@class NSMenu; +#else +typedef void NSMenu; +#endif + +/// macOS implementation of OSPopup +class MacOSXPopup: public OSPopup +{ +public: + MacOSXPopup( intf_thread_t *pIntf ); + virtual ~MacOSXPopup(); + + /// Show the popup menu at the given (absolute) coordinates + virtual void show( int xPos, int yPos ); + + /// Hide the popup menu + virtual void hide(); + + /// Append a new menu item with the given label to the popup menu + virtual void addItem( const std::string &rLabel, int pos ); + + /// Create a dummy menu item to separate sections + virtual void addSeparator( int pos ); + + /// Return the position of the item identified by the given id + virtual int getPosFromId( int id ) const; + +private: + /// The popup menu + NSMenu *m_pMenu; + /// Map of item IDs to positions + std::map m_idPosMap; + /// Screen height for coordinate conversion + int m_screenHeight; +}; + +#endif diff --git a/modules/gui/skins2/macosx/macosx_popup.mm b/modules/gui/skins2/macosx/macosx_popup.mm new file mode 100644 index 000000000000..e5aacd0b561a --- /dev/null +++ b/modules/gui/skins2/macosx/macosx_popup.mm @@ -0,0 +1,136 @@ +/***************************************************************************** + * macosx_popup.mm + ***************************************************************************** + * Copyright (C) 2024 the VideoLAN team + * + * Authors: VLC contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. + *****************************************************************************/ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#import + +#include "macosx_popup.hpp" + + +MacOSXPopup::MacOSXPopup( intf_thread_t *pIntf ): + OSPopup( pIntf ), m_pMenu( nil ) +{ + @autoreleasepool { + NSScreen *mainScreen = [NSScreen mainScreen]; + m_screenHeight = (int)[mainScreen frame].size.height; + + // Create the menu + m_pMenu = [[NSMenu alloc] initWithTitle:@""]; + [m_pMenu setAutoenablesItems:NO]; + } +} + + +MacOSXPopup::~MacOSXPopup() +{ + @autoreleasepool { + m_pMenu = nil; + } +} + + +void MacOSXPopup::show( int xPos, int yPos ) +{ + @autoreleasepool { + if( !m_pMenu ) + return; + + // Convert coordinates + int y = m_screenHeight - yPos; + + // Show the popup menu + NSPoint location = NSMakePoint( xPos, y ); + [m_pMenu popUpMenuPositioningItem:nil + atLocation:location + inView:nil]; + } +} + + +void MacOSXPopup::hide() +{ + @autoreleasepool { + if( m_pMenu ) + { + [m_pMenu cancelTracking]; + } + } +} + + +void MacOSXPopup::addItem( const std::string &rLabel, int pos ) +{ + @autoreleasepool { + if( !m_pMenu ) + return; + + NSString *title = [NSString stringWithUTF8String:rLabel.c_str()]; + NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:title + action:nil + keyEquivalent:@""]; + [item setTag:pos]; + [item setEnabled:YES]; + + // Insert at the correct position + if( pos >= 0 && pos < (int)[m_pMenu numberOfItems] ) + { + [m_pMenu insertItem:item atIndex:pos]; + } + else + { + [m_pMenu addItem:item]; + } + + // Update the ID->position map + m_idPosMap[(int)[m_pMenu indexOfItem:item]] = pos; + } +} + + +void MacOSXPopup::addSeparator( int pos ) +{ + @autoreleasepool { + if( !m_pMenu ) + return; + + NSMenuItem *separator = [NSMenuItem separatorItem]; + + if( pos >= 0 && pos < (int)[m_pMenu numberOfItems] ) + { + [m_pMenu insertItem:separator atIndex:pos]; + } + else + { + [m_pMenu addItem:separator]; + } + } +} + + +int MacOSXPopup::getPosFromId( int id ) const +{ + auto it = m_idPosMap.find( id ); + return ( it != m_idPosMap.end() ) ? it->second : -1; +} diff --git a/modules/gui/skins2/macosx/macosx_timer.hpp b/modules/gui/skins2/macosx/macosx_timer.hpp new file mode 100644 index 000000000000..bc79d9401a9a --- /dev/null +++ b/modules/gui/skins2/macosx/macosx_timer.hpp @@ -0,0 +1,89 @@ +/***************************************************************************** + * macosx_timer.hpp + ***************************************************************************** + * Copyright (C) 2024 the VideoLAN team + * + * Authors: VLC contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. + *****************************************************************************/ + +#ifndef MACOSX_TIMER_HPP +#define MACOSX_TIMER_HPP + +#include "../src/os_timer.hpp" + +#include + +class CmdGeneric; +class MacOSXTimerLoop; + +/// macOS specific timer implementation +class MacOSXTimer: public OSTimer +{ +public: + MacOSXTimer( intf_thread_t *pIntf, CmdGeneric &rCmd, MacOSXTimerLoop *pTimerLoop ); + virtual ~MacOSXTimer(); + + /// (Re)start the timer with the given delay (in ms). + /// If oneShot is true, stop it after the first execution of the callback. + virtual void start( int delay, bool oneShot ); + + /// Stop the timer + virtual void stop(); + + /// Get the next date at which the timer should fire + vlc_tick_t getNextDate() const { return m_nextDate; } + + /// Execute the callback + /// Returns false if the timer must be removed after + bool execute(); + +private: + /// Command to execute + CmdGeneric &m_rCommand; + /// Timer loop + MacOSXTimerLoop *m_pTimerLoop; + /// Interval between executions (in ticks) + vlc_tick_t m_interval; + /// Next date at which the timer must be executed + vlc_tick_t m_nextDate; + /// Flag to indicate one-shot timer + bool m_oneShot; +}; + + +/// Class to manage a set of timers +class MacOSXTimerLoop: public SkinObject +{ +public: + MacOSXTimerLoop( intf_thread_t *pIntf ); + virtual ~MacOSXTimerLoop(); + + /// Add a timer to the manager + void addTimer( MacOSXTimer &rTimer ); + + /// Remove a timer from the manager + void removeTimer( MacOSXTimer &rTimer ); + + /// Check and execute timers that have expired + void checkTimers(); + +private: + /// List of active timers + std::list m_timers; +}; + +#endif diff --git a/modules/gui/skins2/macosx/macosx_timer.mm b/modules/gui/skins2/macosx/macosx_timer.mm new file mode 100644 index 000000000000..c57f8dcce205 --- /dev/null +++ b/modules/gui/skins2/macosx/macosx_timer.mm @@ -0,0 +1,133 @@ +/***************************************************************************** + * macosx_timer.mm + ***************************************************************************** + * Copyright (C) 2024 the VideoLAN team + * + * Authors: VLC contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. + *****************************************************************************/ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "macosx_timer.hpp" +#include "../commands/cmd_generic.hpp" + +#include + + +MacOSXTimer::MacOSXTimer( intf_thread_t *pIntf, CmdGeneric &rCmd, + MacOSXTimerLoop *pTimerLoop ): + OSTimer( pIntf ), m_rCommand( rCmd ), m_pTimerLoop( pTimerLoop ), + m_interval( 0 ), m_nextDate( 0 ), m_oneShot( false ) +{ +} + + +MacOSXTimer::~MacOSXTimer() +{ + stop(); +} + + +void MacOSXTimer::start( int delay, bool oneShot ) +{ + // Stop any existing timer + stop(); + + m_interval = VLC_TICK_FROM_MS( delay ); + m_oneShot = oneShot; + m_nextDate = vlc_tick_now() + m_interval; + + // Register with the timer loop + if( m_pTimerLoop ) + { + m_pTimerLoop->addTimer( *this ); + } +} + + +void MacOSXTimer::stop() +{ + if( m_pTimerLoop ) + { + m_pTimerLoop->removeTimer( *this ); + } + m_nextDate = 0; +} + + +bool MacOSXTimer::execute() +{ + // Execute the command + m_rCommand.execute(); + + // Update for next execution + if( m_oneShot ) + { + return false; // Remove the timer + } + + m_nextDate = vlc_tick_now() + m_interval; + return true; // Keep the timer +} + + +MacOSXTimerLoop::MacOSXTimerLoop( intf_thread_t *pIntf ): + SkinObject( pIntf ) +{ +} + + +MacOSXTimerLoop::~MacOSXTimerLoop() +{ + m_timers.clear(); +} + + +void MacOSXTimerLoop::addTimer( MacOSXTimer &rTimer ) +{ + m_timers.push_back( &rTimer ); +} + + +void MacOSXTimerLoop::removeTimer( MacOSXTimer &rTimer ) +{ + m_timers.remove( &rTimer ); +} + + +void MacOSXTimerLoop::checkTimers() +{ + vlc_tick_t now = vlc_tick_now(); + + // Create a copy of the list to iterate safely + std::list timersCopy = m_timers; + + for( auto it = timersCopy.begin(); it != timersCopy.end(); ++it ) + { + MacOSXTimer *pTimer = *it; + if( pTimer->getNextDate() <= now ) + { + if( !pTimer->execute() ) + { + // Timer requested to be removed + m_timers.remove( pTimer ); + } + } + } +} diff --git a/modules/gui/skins2/macosx/macosx_tooltip.hpp b/modules/gui/skins2/macosx/macosx_tooltip.hpp new file mode 100644 index 000000000000..fc8e42364fec --- /dev/null +++ b/modules/gui/skins2/macosx/macosx_tooltip.hpp @@ -0,0 +1,54 @@ +/***************************************************************************** + * macosx_tooltip.hpp + ***************************************************************************** + * Copyright (C) 2024 the VideoLAN team + * + * Authors: VLC contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. + *****************************************************************************/ + +#ifndef MACOSX_TOOLTIP_HPP +#define MACOSX_TOOLTIP_HPP + +#include "../src/os_tooltip.hpp" + +#ifdef __OBJC__ +@class NSWindow; +#else +typedef void NSWindow; +#endif + +/// macOS implementation of OSTooltip +class MacOSXTooltip: public OSTooltip +{ +public: + MacOSXTooltip( intf_thread_t *pIntf ); + virtual ~MacOSXTooltip(); + + /// Show the tooltip + virtual void show( int left, int top, OSGraphics &rText ); + + /// Hide the tooltip + virtual void hide(); + +private: + /// Tooltip window + NSWindow *m_pWindow; + /// Screen height for coordinate conversion + int m_screenHeight; +}; + +#endif diff --git a/modules/gui/skins2/macosx/macosx_tooltip.mm b/modules/gui/skins2/macosx/macosx_tooltip.mm new file mode 100644 index 000000000000..4386f8510586 --- /dev/null +++ b/modules/gui/skins2/macosx/macosx_tooltip.mm @@ -0,0 +1,116 @@ +/***************************************************************************** + * macosx_tooltip.mm + ***************************************************************************** + * Copyright (C) 2024 the VideoLAN team + * + * Authors: VLC contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. + *****************************************************************************/ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#import + +#include "macosx_tooltip.hpp" +#include "macosx_graphics.hpp" + + +MacOSXTooltip::MacOSXTooltip( intf_thread_t *pIntf ): + OSTooltip( pIntf ), m_pWindow( nil ) +{ + @autoreleasepool { + NSScreen *mainScreen = [NSScreen mainScreen]; + m_screenHeight = (int)[mainScreen frame].size.height; + + // Create a tooltip window + m_pWindow = [[NSWindow alloc] + initWithContentRect:NSMakeRect( 0, 0, 1, 1 ) + styleMask:NSWindowStyleMaskBorderless + backing:NSBackingStoreBuffered + defer:YES]; + + [m_pWindow setOpaque:NO]; + [m_pWindow setBackgroundColor:[NSColor colorWithCalibratedWhite:0.95 alpha:0.95]]; + [m_pWindow setLevel:NSPopUpMenuWindowLevel]; + [m_pWindow setIgnoresMouseEvents:YES]; + [m_pWindow setHasShadow:YES]; + + // Create an image view for the tooltip content + NSImageView *imageView = [[NSImageView alloc] initWithFrame:NSZeroRect]; + [imageView setImageScaling:NSImageScaleNone]; + [m_pWindow setContentView:imageView]; + } +} + + +MacOSXTooltip::~MacOSXTooltip() +{ + @autoreleasepool { + if( m_pWindow ) + { + [m_pWindow orderOut:nil]; + m_pWindow = nil; + } + } +} + + +void MacOSXTooltip::show( int left, int top, OSGraphics &rText ) +{ + @autoreleasepool { + if( !m_pWindow ) + return; + + const MacOSXGraphics &rGraphics = static_cast(rText); + int width = rGraphics.getWidth(); + int height = rGraphics.getHeight(); + + // Convert coordinates + int y = m_screenHeight - top - height; + + // Resize and position the window + NSRect frame = NSMakeRect( left, y, width, height ); + [m_pWindow setFrame:frame display:NO]; + + // Create image from graphics + CGImageRef cgImage = (CGImageRef)rGraphics.getImage(); + if( cgImage ) + { + NSImage *nsImage = [[NSImage alloc] initWithCGImage:cgImage + size:NSMakeSize(width, height)]; + NSImageView *imageView = (NSImageView *)[m_pWindow contentView]; + [imageView setFrame:NSMakeRect(0, 0, width, height)]; + [imageView setImage:nsImage]; + + CGImageRelease( cgImage ); + } + + [m_pWindow orderFront:nil]; + } +} + + +void MacOSXTooltip::hide() +{ + @autoreleasepool { + if( m_pWindow ) + { + [m_pWindow orderOut:nil]; + } + } +} diff --git a/modules/gui/skins2/macosx/macosx_window.hpp b/modules/gui/skins2/macosx/macosx_window.hpp new file mode 100644 index 000000000000..6d049fad8522 --- /dev/null +++ b/modules/gui/skins2/macosx/macosx_window.hpp @@ -0,0 +1,101 @@ +/***************************************************************************** + * macosx_window.hpp + ***************************************************************************** + * Copyright (C) 2024 the VideoLAN team + * + * Authors: VLC contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. + *****************************************************************************/ + +#ifndef MACOSX_WINDOW_HPP +#define MACOSX_WINDOW_HPP + +#include "../src/generic_window.hpp" +#include "../src/os_window.hpp" + +#include + +#ifdef __OBJC__ +@class NSWindow; +@class VLCSkinsWindow; +#else +typedef void NSWindow; +typedef void VLCSkinsWindow; +#endif + +class MacOSXDragDrop; + +/// macOS implementation of OSWindow +class MacOSXWindow: public OSWindow +{ +public: + MacOSXWindow( intf_thread_t *pIntf, GenericWindow &rWindow, + bool dragDrop, bool playOnDrop, + MacOSXWindow *pParentWindow, + GenericWindow::WindowType_t type ); + + virtual ~MacOSXWindow(); + + // Show the window + virtual void show() const; + + // Hide the window + virtual void hide() const; + + /// Move and resize the window + virtual void moveResize( int left, int top, + int width, int height ) const; + + /// Bring the window on top + virtual void raise() const; + + /// Set the opacity of the window (0 = transparent, 255 = opaque) + virtual void setOpacity( uint8_t value ) const; + + /// Toggle the window on top + virtual void toggleOnTop( bool onTop ) const; + + /// Set the window handler for video output + virtual void setOSHandle( vlc_window_t *pWnd ) const; + + /// Reparent the window + virtual void reparent( OSWindow *pParent, int x, int y, int w, int h ); + + /// Invalidate a window region + virtual bool invalidateRect( int x, int y, int w, int h ) const; + + /// Get the NSWindow + NSWindow *getNSWindow() const { return m_pWindow; } + + /// Get the associated GenericWindow + GenericWindow &getGenericWindow() const { return m_rWindow; } + +private: + /// Associated GenericWindow + GenericWindow &m_rWindow; + /// NSWindow handle + VLCSkinsWindow *m_pWindow; + /// Parent window + MacOSXWindow *m_pParent; + /// Window type + GenericWindow::WindowType_t m_type; + /// Drag & drop handler + MacOSXDragDrop *m_pDropTarget; + /// Screen height (for coordinate conversion) + int m_screenHeight; +}; + +#endif diff --git a/modules/gui/skins2/macosx/macosx_window.mm b/modules/gui/skins2/macosx/macosx_window.mm new file mode 100644 index 000000000000..a3a0ac8113ea --- /dev/null +++ b/modules/gui/skins2/macosx/macosx_window.mm @@ -0,0 +1,375 @@ +/***************************************************************************** + * macosx_window.mm + ***************************************************************************** + * Copyright (C) 2024 the VideoLAN team + * + * Authors: VLC contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. + *****************************************************************************/ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#import + +#include "macosx_window.hpp" +#include "macosx_dragdrop.hpp" +#include "macosx_factory.hpp" +#include "../src/os_factory.hpp" + +// Forward declaration of the window delegate protocol handler +@class VLCSkinsWindowDelegate; + +/// Custom NSWindow subclass for VLC Skins +@interface VLCSkinsWindow : NSWindow +{ + MacOSXWindow *m_pOwner; + VLCSkinsWindowDelegate *m_delegate; +} +- (instancetype)initWithOwner:(MacOSXWindow *)owner + contentRect:(NSRect)contentRect + styleMask:(NSWindowStyleMask)style + backing:(NSBackingStoreType)backingStoreType + defer:(BOOL)flag; +- (MacOSXWindow *)owner; +@end + +@interface VLCSkinsWindowDelegate : NSObject +{ + MacOSXWindow *m_pOwner; +} +- (instancetype)initWithOwner:(MacOSXWindow *)owner; +@end + +@implementation VLCSkinsWindowDelegate + +- (instancetype)initWithOwner:(MacOSXWindow *)owner +{ + self = [super init]; + if( self ) + { + m_pOwner = owner; + } + return self; +} + +- (void)windowDidBecomeKey:(NSNotification *)notification +{ + // Window became active +} + +- (void)windowDidResignKey:(NSNotification *)notification +{ + // Window became inactive +} + +- (BOOL)windowShouldClose:(NSWindow *)sender +{ + // Let the skins system handle closing + return NO; +} + +@end + +@implementation VLCSkinsWindow + +- (instancetype)initWithOwner:(MacOSXWindow *)owner + contentRect:(NSRect)contentRect + styleMask:(NSWindowStyleMask)style + backing:(NSBackingStoreType)backingStoreType + defer:(BOOL)flag +{ + self = [super initWithContentRect:contentRect + styleMask:style + backing:backingStoreType + defer:flag]; + if( self ) + { + m_pOwner = owner; + m_delegate = [[VLCSkinsWindowDelegate alloc] initWithOwner:owner]; + [self setDelegate:m_delegate]; + + // Configure for skinned appearance + [self setOpaque:NO]; + [self setBackgroundColor:[NSColor clearColor]]; + [self setHasShadow:YES]; + [self setMovableByWindowBackground:YES]; + [self setAcceptsMouseMovedEvents:YES]; + + // Create content view + NSImageView *imageView = [[NSImageView alloc] initWithFrame:contentRect]; + [imageView setImageScaling:NSImageScaleNone]; + [self setContentView:imageView]; + } + return self; +} + +- (MacOSXWindow *)owner +{ + return m_pOwner; +} + +- (BOOL)canBecomeKeyWindow +{ + return YES; +} + +- (BOOL)canBecomeMainWindow +{ + return YES; +} + +- (void)sendEvent:(NSEvent *)event +{ + // Forward events to the skins event loop + [super sendEvent:event]; +} + +@end + + +MacOSXWindow::MacOSXWindow( intf_thread_t *pIntf, GenericWindow &rWindow, + bool dragDrop, bool playOnDrop, + MacOSXWindow *pParentWindow, + GenericWindow::WindowType_t type ): + OSWindow( pIntf ), m_rWindow( rWindow ), m_pWindow( nil ), + m_pParent( pParentWindow ), m_type( type ), m_pDropTarget( NULL ) +{ + @autoreleasepool { + // Get screen height for coordinate conversion + NSScreen *mainScreen = [NSScreen mainScreen]; + m_screenHeight = (int)[mainScreen frame].size.height; + + // Determine window style + NSWindowStyleMask styleMask; + NSRect contentRect = NSMakeRect( 0, 0, 100, 100 ); + + switch( type ) + { + case GenericWindow::FullscreenWindow: + styleMask = NSWindowStyleMaskBorderless; + contentRect = [mainScreen frame]; + break; + + case GenericWindow::ToolbarWindow: + case GenericWindow::TopWindow: + default: + styleMask = NSWindowStyleMaskBorderless; + break; + } + + // Create the window + m_pWindow = [[VLCSkinsWindow alloc] + initWithOwner:this + contentRect:contentRect + styleMask:styleMask + backing:NSBackingStoreBuffered + defer:YES]; + + if( m_pWindow ) + { + // Register with factory + MacOSXFactory *pFactory = static_cast( + OSFactory::instance( pIntf ) ); + if( pFactory ) + { + pFactory->m_windowMap[(__bridge void *)m_pWindow] = &rWindow; + } + + // Set up drag & drop if requested + if( dragDrop ) + { + m_pDropTarget = new MacOSXDragDrop( pIntf, m_pWindow, playOnDrop, &rWindow ); + } + + // Set window level based on type + if( type == GenericWindow::FullscreenWindow ) + { + [m_pWindow setLevel:NSScreenSaverWindowLevel]; + } + else if( type == GenericWindow::ToolbarWindow ) + { + [m_pWindow setLevel:NSFloatingWindowLevel]; + } + + // Set parent window + if( m_pParent && m_pParent->getNSWindow() ) + { + [m_pParent->getNSWindow() addChildWindow:m_pWindow + ordered:NSWindowAbove]; + } + } + } +} + + +MacOSXWindow::~MacOSXWindow() +{ + @autoreleasepool { + // Unregister from factory + MacOSXFactory *pFactory = static_cast( + OSFactory::instance( getIntf() ) ); + if( pFactory && m_pWindow ) + { + pFactory->m_windowMap.erase( (__bridge void *)m_pWindow ); + } + + delete m_pDropTarget; + + if( m_pWindow ) + { + if( m_pParent && m_pParent->getNSWindow() ) + { + [m_pParent->getNSWindow() removeChildWindow:m_pWindow]; + } + [m_pWindow close]; + m_pWindow = nil; + } + } +} + + +void MacOSXWindow::show() const +{ + @autoreleasepool { + if( m_pWindow ) + { + [m_pWindow makeKeyAndOrderFront:nil]; + } + } +} + + +void MacOSXWindow::hide() const +{ + @autoreleasepool { + if( m_pWindow ) + { + [m_pWindow orderOut:nil]; + } + } +} + + +void MacOSXWindow::moveResize( int left, int top, int width, int height ) const +{ + @autoreleasepool { + if( m_pWindow ) + { + // Convert from skins coordinates (origin top-left) + // to Cocoa coordinates (origin bottom-left) + NSRect frame = NSMakeRect( left, m_screenHeight - top - height, + width, height ); + [m_pWindow setFrame:frame display:YES]; + } + } +} + + +void MacOSXWindow::raise() const +{ + @autoreleasepool { + if( m_pWindow ) + { + [m_pWindow orderFront:nil]; + } + } +} + + +void MacOSXWindow::setOpacity( uint8_t value ) const +{ + @autoreleasepool { + if( m_pWindow ) + { + CGFloat alpha = (CGFloat)value / 255.0; + [m_pWindow setAlphaValue:alpha]; + } + } +} + + +void MacOSXWindow::toggleOnTop( bool onTop ) const +{ + @autoreleasepool { + if( m_pWindow ) + { + if( onTop ) + { + [m_pWindow setLevel:NSFloatingWindowLevel]; + } + else + { + [m_pWindow setLevel:NSNormalWindowLevel]; + } + } + } +} + + +void MacOSXWindow::setOSHandle( vlc_window_t *pWnd ) const +{ + if( pWnd && m_pWindow ) + { + pWnd->type = VLC_WINDOW_TYPE_NSOBJECT; + pWnd->info.has_double_click = true; + pWnd->handle.nsobject = (__bridge void *)[m_pWindow contentView]; + } +} + + +void MacOSXWindow::reparent( OSWindow *pParent, int x, int y, int w, int h ) +{ + @autoreleasepool { + MacOSXWindow *pMacParent = static_cast(pParent); + + // Remove from old parent + if( m_pParent && m_pParent->getNSWindow() && m_pWindow ) + { + [m_pParent->getNSWindow() removeChildWindow:m_pWindow]; + } + + m_pParent = pMacParent; + + // Add to new parent + if( m_pParent && m_pParent->getNSWindow() && m_pWindow ) + { + [m_pParent->getNSWindow() addChildWindow:m_pWindow + ordered:NSWindowAbove]; + } + + moveResize( x, y, w, h ); + } +} + + +bool MacOSXWindow::invalidateRect( int x, int y, int w, int h ) const +{ + @autoreleasepool { + if( m_pWindow ) + { + NSView *contentView = [m_pWindow contentView]; + if( contentView ) + { + NSRect rect = NSMakeRect( x, [contentView bounds].size.height - y - h, w, h ); + [contentView setNeedsDisplayInRect:rect]; + return true; + } + } + return false; + } +} diff --git a/modules/gui/skins2/meson.build b/modules/gui/skins2/meson.build index 5c4c12ca2ce3..0a9415dfd281 100644 --- a/modules/gui/skins2/meson.build +++ b/modules/gui/skins2/meson.build @@ -1,6 +1,6 @@ skins2_enabled = get_option('skins2') \ - .disable_auto_if(host_system != 'windows' and not x11_dep.found()).allowed() and \ - freetype_dep.found() and host_system != 'darwin' + .disable_auto_if(host_system != 'windows' and host_system != 'darwin' and not x11_dep.found()).allowed() and \ + freetype_dep.found() skins2_flags = [] skins2_deps = [ freetype_dep ] @@ -148,6 +148,21 @@ skins2_deps += [ dependency('xcursor', required: true), dependency('xext', required: true), ] +elif host_system == 'darwin' and skins2_enabled +skins2_flags += [ '-DMACOSX_SKINS' ] +skins2_sources += files( + 'macosx/macosx_dragdrop.mm', + 'macosx/macosx_factory.mm', + 'macosx/macosx_graphics.mm', + 'macosx/macosx_loop.mm', + 'macosx/macosx_popup.mm', + 'macosx/macosx_timer.mm', + 'macosx/macosx_tooltip.mm', + 'macosx/macosx_window.mm', +) +skins2_deps += [ + dependency('appleframeworks', modules: ['Cocoa', 'Carbon', 'CoreGraphics']), +] endif vlc_modules += { diff --git a/modules/gui/skins2/src/os_factory.cpp b/modules/gui/skins2/src/os_factory.cpp index 66f900344c8a..0dfbb03eee69 100644 --- a/modules/gui/skins2/src/os_factory.cpp +++ b/modules/gui/skins2/src/os_factory.cpp @@ -29,6 +29,8 @@ #include "../win32/win32_factory.hpp" #elif defined OS2_SKINS #include "../os2/os2_factory.hpp" +#elif defined MACOSX_SKINS +#include "../macosx/macosx_factory.hpp" #endif OSFactory *OSFactory::instance( intf_thread_t *pIntf ) @@ -42,6 +44,8 @@ OSFactory *OSFactory::instance( intf_thread_t *pIntf ) pOsFactory = new Win32Factory( pIntf ); #elif defined OS2_SKINS pOsFactory = new OS2Factory( pIntf ); +#elif defined MACOSX_SKINS + pOsFactory = new MacOSXFactory( pIntf ); #else #error "No OSFactory implementation !" #endif