We need three files:
* RetinaHelper.h, which is your standard c++ header
* RetinaHelperImpl.h, which is the header for the objective-c++ part of the class.
* RetinaHelperImpl.mm, the objective-c++ "meat".
In RetinaHelper.h we declare the c++-class and it's methods that we need exposed to our c++ code:
Code: Select all
// file RetinaHelper.h
#ifndef RetinaHelper_h
#def RetinaHelper_h
class wxWindow;
class RetinaHelper
{
public:
RetinaHelper(wxWindow* window);
~RetinaHelper();
void setViewWantsBestResolutionOpenGLSurface(bool value);
bool getViewWantsBestResolutionOpenGLSurface();
float getBackingScaleFactor();
private:
wxWindow* _window;
void* _self; // pointer to obj-c++ implementation
};
#endif // RetinaHelper_h
Code: Select all
// file RetinaHelperImpl.h
#import "RetinaHelper.h"
#import <Cocoa/Cocoa.h>
// forward declaration
class wxEvtHandler;
@interface RetinaHelperImpl : NSObject
{
NSView *view;
wxEvtHandler* handler;
}
// designated initializer
-(id)initWithView:(NSView *)view handler:(wxEvtHandler *)handler;
-(void)setViewWantsBestResolutionOpenGLSurface:(BOOL)value;
-(BOOL)getViewWantsBestResolutionOpenGLSurface;
-(float)getBackingScaleFactor;
@end
Code: Select all
// file RetinaHelper.mm
#import "RetinaHelperImpl.h"
#import <OpenGL/OpenGL.h>
#import "wx/window.h"
@implementation RetinaHelperImpl
RetinaHelper::RetinaHelper(wxWindow* window) :
_window(window)
{
_self = nullptr;
_self = [[RetinaHelperImpl alloc] initWithView:window->GetHandle() handler:window->GetEventHandler()];
}
RetinaHelper::~RetinaHelper()
{
[_self release];
}
void RetinaHelper::setViewWantsBestResolutionOpenGLSurface (bool aValue)
{
[(id)_self setViewWantsBestResolutionOpenGLSurface:aValue];
}
bool RetinaHelper::getViewWantsBestResolutionOpenGLSurface()
{
return [(id)_self getViewWantsBestResolutionOpenGLSurface];
}
float RetinaHelper::getBackingScaleFactor()
{
return [(id)_self getBackingScaleFactor];
}
-(id)initWithView:(NSView *)aView handler:(wxEvtHandler *)aHandler
{
self = [super init];
if(self)
{
handler = aHandler;
view = aView;
// register for backing change notifications
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
if(nc){
[nc addObserver:self
selector:@selector(windowDidChangeBackingProperties:)
name:NSWindowDidChangeBackingPropertiesNotification
object:nil];
}
}
return self;
}
-(void) dealloc
{
// unregister from all notifications
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
if(nc){
[nc removeObserver:self];
}
[super dealloc];
}
-(void)setViewWantsBestResolutionOpenGLSurface:(BOOL)value
{
[view setWantsBestResolutionOpenGLSurface:value];
}
-(BOOL)getViewWantsBestResolutionOpenGLSurface
{
return [view wantsBestResolutionOpenGLSurface];
}
-(float)getBackingScaleFactor
{
return [[view window] backingScaleFactor];
}
- (void)windowDidChangeBackingProperties:(NSNotification *)notification {
NSWindow *theWindow = (NSWindow *)[notification object];
if(theWindow == [view window])
{
CGFloat newBackingScaleFactor = [theWindow backingScaleFactor];
CGFloat oldBackingScaleFactor = [[[notification userInfo]
objectForKey:@"NSBackingPropertyOldScaleFactorKey"]
doubleValue];
if (newBackingScaleFactor != oldBackingScaleFactor) {
// generate a wx resize event and pass it to the handler's queue
wxSizeEvent *event = new wxSizeEvent();
// use the following line if this resize event should have the physical pixel resolution
// but that is not recommended, because ordinary resize events won't do so either
// which would necessitate a case-by-case switch in the resize handler method.
// NSRect nsrect = [view convertRectToBacking:[view bounds]];
NSRect nsrect = [view bounds];
wxRect rect = wxRect(nsrect.origin.x, nsrect.origin.y, nsrect.size.width, nsrect.size.height);
event->SetRect(rect);
event->SetSize(rect.GetSize());
handler->QueueEvent(event);
}
}
}
@end
But, as of now here's the header file for a Retina-ready 3D-Viewer class:
Code: Select all
// file My3DFrame.h
#include "wx/frame.h"
class RetinaHelper;
class wxGLCanvas;
class My3DFrame :: public wxFrame
{
My3DFrame(wxWindow* parent);
//
// additional code omitted
//
void OnCanvasSize(wxSizeEvent& event);
private:
wxGLCanvas* _pCanvas;
#ifdef __APPLE__
RetinaHelper* _pRetinaHelper;
#endif
// for use with glViewport.
int _width;
int _height;
bool _viewportNeedsRefresh;
};
Code: Select all
#include "My3DFrame.h"
#include "wx/glcanvas.h"
My3DFrame::My3DFrame(wxWindow *parent)
{
//... initialisation code for glCanvas omitted ...
// Canvas Resize
_pCanvas->Bind(wxEVT_SIZE, &My3DFrame::OnCanvasSize, this);
// on os x: install retina helper
#ifdef __APPLE__
_pRetinaHelper = new RetinaHelper(_pCanvas);
_retinaHelper->setViewWantsBestResolutionOpenGLSurface(true);
#endif
}
My3DFrame::~My3DFrame()
{
#ifdef __APPLE__
delete _pRetinaHelper;
#endif
}
My3DFrame::OnCanvasResize(wxSizeEvent &event)
{
wxSize newSize = event.GetSize();
_width = newSize.GetWidth();
_height = newSize.GetHeight();
#ifdef __APPLE__
float scaleFactor = _retinaHelper->getBackingScaleFactor();
_width *= scaleFactor;
_height *= scaleFactor;
#endif
// flag to reset glViewport on next render tick, I'm sure you'll have something similar.
_viewportNeedsRefresh = true;
event.Skip();
}
Edit: fixed a memory leak from not calling release on _self in the dtor of RetinaHelper.