Detecting Retina question

In detecting Mac Retina screens, I know from reading on the forum. that the method mentioned in the some versions of the Xojo dos is not the best way to do this:

Declare Function BackingScaleFactor Lib “AppKit” Selector “backingScaleFactor” (target As WindowPtr) As Double
…and so on…

and that the other method I found posted is more preferable:

Declare Function CGContextConvertSizeToDeviceSpace Lib “ApplicationServices” (Context As Integer, UserSize As CGSize) As CGSize
…and so on…

But as far as I can tell, the second method, expects me to have access to the Graphics object (g) in the Windows Paint Event, which gets called and re-evaluated constantly when something is moved or changed. The first method you can just pass the Window once and get the value and cache it somewhere. Is that the reason to not use it? Is constantly re-evaluating the Window to see if it is on a Retina screen the best way to do this? Seems like a lot of cycles are used to keep monitoring this. Any help is appreciated.

your window may be moved to other position. Or screen changes from retina to non retina due to a resolution change.

Or moved from one screen to another, one being retina, the other not… Will be nice when Xojo supports it transparently.

You can register for an NSWindowDidChangeBackingPropertiesNotification. It works! I have an example project I can share… You do have to be sure to run it in NSHighResolutionCapable mode to receive the notification. I would just use BackingScaleFactor at launch and whenever you get a notification for the window and store that as a property of the window.

Thanks all. Yes, that would be great, Jim, do you have a link of where it is posted?

At the moment it’s just a test project. I’ll clean it up and post it shortly

Here you go. I made it a class that can be dragged to a window. It will fire the backingScaleChanged event when going to/from retina, and has a backingScaleFactor property that gets updated when the scale changes.

RetinaNotifier.zip

Thanks, Jim, I just got the file will try it shortly this evening. Thank you.

If you do Retina the right way, you don’t need to detect for it, let the OS manage it for you. Check this forum for previous posts on how to do Retina.

Declare Function CGContextConvertSizeToDeviceSpace Lib "ApplicationServices" (Context As Integer, UserSize As CGSize) As CGSize

Is used to determine what source dimensions you need. You pass in the dimensions that you want to draw too, and it returns the dimensions of the required source. If you’re using this to detect if Retina is enabled, that’s not what its designed for.

Example, you want to draw a picture in a rect of 200 x 200, so you pass it 200 x 200 and it will return the size of the image that is required to meet the current screen resolution, On a traditional screen, you’ll get 200 x 200, on a Retina MBP you’ll get 400 x 400. The reason this calculation is live, is the user can change the screen resolution, move the window to a different screen or who else knows what? Don’t forget that the paint even is only called when needed or you request it.

Don’t forget that ‘Retina’ is not just @2x, we’re starting to see @3x and who knows how long before @4x

For almost all common cases, developers should avoid using the backingScaleFactor as an input to layout or drawing calculations. Developers should instead use the backing coordinate space conversion methods instead, as the resulting code will more likely work consistently and correctly under both standard and high-resolution operation.

Sorry for the delay in responding Jim, the holidays are running me ragged, all over the place the past few days. I have it working on the Mac, thanks. On Windows it is crashing, but I haven’t had time to investigate whether the code is usable across both as it is written.

I’ve just been using the RetinaNotifier in my project and it’s working like a charm… on one window. But if I add it to more than one window, there’s a hard crash when the second window’s shown.

I’m not in any way an expert on declares and the like, but I think this is due to a typo in the code - at least, changing the line as I suggest below makes everything work happily.

In the Open event of the RetinaNotifier, the line
NotificationReceiverClass=objc_allocateClassPair(objectClass, "BackingNotificationReceiver", 0)
should be
NotificationReceiverClass=objc_allocateClassPair(objectClass, "BackingNotificationReceiverClass", 0)

Do correct me if I’m wrong :slight_smile:

What are you doing that requires notification of when the screen resolution changes? Ideally Retina should be transparent to you the developer.

I have separate ‘standard’ and ‘2x’ graphics for toolbar icons, which don’t have a Paint event. So, if the screen resolution changes to 2x, I need to swap in my 2x graphics into the toolButton.Icon property. Our app has a main window and a prefs window, both of which have toolbars, which is why I had two instances of the RetinaNotifier.

I have created a separate Toolbar control that will take a @2x picture rather than the standard size.

It is a free control. You can download from www.simcarsoftware.com

[quote=206431:@Hamish Symington]should be

NotificationReceiverClass=objc_allocateClassPair(objectClass, “BackingNotificationReceiverClass”, 0)
[/quote]

Yes, that’s right. Otherwise, the class gets created/registered twice, and will crash. I’ll update the project file for future downloaders.

I also added the removeObserver: call to the close event and fixed the declares to be more correct.

What if I told you, that you don’t need to do that? If you use the right code to begin with, you let the OS worry about which element to display when, and for Toolbars its pretty easy?

You can use either the MBS plugins, MacOSLib or my own Retina Kit classes, all three use the correct APIs for adding and displaying Retina elements when they’re required, when you use the correct APIs it becomes transparent, then you don’t need hacks for this situation and that situation, it also means that if Apple go to 3x (like they’ve done with the phone) you only need to add the extra artwork as the OS if handling Retina for you.

Sam,
since you mentioned MacOSLib, making a search for “Retina” I get two independent instances:

  1. in the open event of the NSwindow example:
    dim factor as double = m_NSWindow.BackingScaleFactor

  2. the result of the following function:
    Function ScalingFactor(Extends w as Window) As Single
    //# Determines if the window is displayed on a Retina screen or other HiDPI mode.

//@result The ScalingFactor is 2 for a Retina MacBook Pro (or other HiDPI modes) and 1 for anything else

//@ The following property needs to be added to the app’s Info.plist in order to make your Cocoa app Retina capable:
// NSHighResolutionCapable
//

dim r as Single = 1. // Default response

#if TargetCocoa
if IsLion then
declare function BackingScaleFactor lib CocoaLib selector “backingScaleFactor” (target as WindowPtr) as Double
r = BackingScaleFactor(w)
end if
#else
#pragma Unused w
#endif

return r
End Function

Which one should be used? Thanks,
BTW: in the functions I guess the “If isLion” conditional could be deleted…

Neither, now I sound like an arse. Hopefully someone who uses MacOSLib can assist you with the MacOSLib implementation.

With the Retina Kit, I simply use code like below to set the toolbar icon (in the open event of the toolbar).

me.seticon 0, retinaKit.imageNamed( "aw3" )

I have two images in the resources folder, “aw3.png” & “aw3@2x.png”.

I’m sure when Christian is awake, he can also explain how to do it with his MBS plugin.