mirror of
https://github.com/panda3d/panda3d.git
synced 2025-09-27 23:34:57 -04:00
cocoadisplay: Add support for high-dpi screens (#1308)
This commit is contained in:
parent
e55bb94996
commit
ae3cbe4b12
1
direct/src/dist/commands.py
vendored
1
direct/src/dist/commands.py
vendored
@ -717,6 +717,7 @@ class build_apps(setuptools.Command):
|
|||||||
'CFBundlePackageType': 'APPL',
|
'CFBundlePackageType': 'APPL',
|
||||||
'CFBundleSignature': '', #TODO
|
'CFBundleSignature': '', #TODO
|
||||||
'CFBundleExecutable': self.macos_main_app,
|
'CFBundleExecutable': self.macos_main_app,
|
||||||
|
'NSHighResolutionCapable': 'True',
|
||||||
}
|
}
|
||||||
|
|
||||||
icon = self.icon_objects.get(
|
icon = self.icon_objects.get(
|
||||||
|
@ -40,12 +40,36 @@ CocoaGraphicsPipe(CGDirectDisplayID display) : _display(display) {
|
|||||||
[thread start];
|
[thread start];
|
||||||
[thread autorelease];
|
[thread autorelease];
|
||||||
|
|
||||||
|
// If the application is dpi-aware, iterate over all the screens to find the
|
||||||
|
// one with our display ID and get the backing scale factor to configure the
|
||||||
|
// detected display zoom. Otherwise the detected display zoom keeps its
|
||||||
|
// default value of 1.0
|
||||||
|
|
||||||
|
if (dpi_aware) {
|
||||||
|
NSScreen *screen;
|
||||||
|
NSEnumerator *e = [[NSScreen screens] objectEnumerator];
|
||||||
|
while (screen = (NSScreen *) [e nextObject]) {
|
||||||
|
NSNumber *num = [[screen deviceDescription] objectForKey: @"NSScreenNumber"];
|
||||||
|
if (_display == (CGDirectDisplayID) [num longValue]) {
|
||||||
|
set_detected_display_zoom([screen backingScaleFactor]);
|
||||||
|
if (cocoadisplay_cat.is_debug()) {
|
||||||
|
cocoadisplay_cat.debug()
|
||||||
|
<< "Display zoom is " << [screen backingScaleFactor] << "\n";
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// We used to also obtain the corresponding NSScreen here, but this causes
|
// We used to also obtain the corresponding NSScreen here, but this causes
|
||||||
// the application icon to start bouncing, which may be undesirable for
|
// the application icon to start bouncing, which may be undesirable for
|
||||||
// apps that will never open a window.
|
// apps that will never open a window.
|
||||||
|
|
||||||
_display_width = CGDisplayPixelsWide(_display);
|
// Although the name of these functions mention pixels, they actually return
|
||||||
_display_height = CGDisplayPixelsHigh(_display);
|
// display points, we use the detected display zoom to transform the values
|
||||||
|
// into pixels.
|
||||||
|
_display_width = CGDisplayPixelsWide(_display) * _detected_display_zoom;
|
||||||
|
_display_height = CGDisplayPixelsHigh(_display) * _detected_display_zoom;
|
||||||
load_display_information();
|
load_display_information();
|
||||||
|
|
||||||
if (cocoadisplay_cat.is_debug()) {
|
if (cocoadisplay_cat.is_debug()) {
|
||||||
@ -64,19 +88,36 @@ load_display_information() {
|
|||||||
// _display_information->_device_id = CGDisplaySerialNumber(_display);
|
// _display_information->_device_id = CGDisplaySerialNumber(_display);
|
||||||
|
|
||||||
// Display modes
|
// Display modes
|
||||||
|
CFDictionaryRef options = NULL;
|
||||||
|
const CFStringRef dictkeys[] = {kCGDisplayShowDuplicateLowResolutionModes};
|
||||||
|
const CFBooleanRef dictvalues[] = {kCFBooleanTrue};
|
||||||
|
options = CFDictionaryCreate(NULL,
|
||||||
|
(const void **)dictkeys,
|
||||||
|
(const void **)dictvalues,
|
||||||
|
1,
|
||||||
|
&kCFCopyStringDictionaryKeyCallBacks,
|
||||||
|
&kCFTypeDictionaryValueCallBacks);
|
||||||
size_t num_modes = 0;
|
size_t num_modes = 0;
|
||||||
CFArrayRef modes = CGDisplayCopyAllDisplayModes(_display, NULL);
|
CFArrayRef modes = CGDisplayCopyAllDisplayModes(_display, options);
|
||||||
if (modes != NULL) {
|
if (modes != NULL) {
|
||||||
num_modes = CFArrayGetCount(modes);
|
num_modes = CFArrayGetCount(modes);
|
||||||
_display_information->_total_display_modes = num_modes;
|
_display_information->_total_display_modes = num_modes;
|
||||||
_display_information->_display_mode_array = new DisplayMode[num_modes];
|
_display_information->_display_mode_array = new DisplayMode[num_modes];
|
||||||
}
|
}
|
||||||
|
if (options != NULL) {
|
||||||
|
CFRelease(options);
|
||||||
|
}
|
||||||
|
|
||||||
for (size_t i = 0; i < num_modes; ++i) {
|
for (size_t i = 0; i < num_modes; ++i) {
|
||||||
CGDisplayModeRef mode = (CGDisplayModeRef) CFArrayGetValueAtIndex(modes, i);
|
CGDisplayModeRef mode = (CGDisplayModeRef) CFArrayGetValueAtIndex(modes, i);
|
||||||
|
|
||||||
_display_information->_display_mode_array[i].width = CGDisplayModeGetWidth(mode);
|
if (dpi_aware) {
|
||||||
_display_information->_display_mode_array[i].height = CGDisplayModeGetHeight(mode);
|
_display_information->_display_mode_array[i].width = CGDisplayModeGetPixelWidth(mode);
|
||||||
|
_display_information->_display_mode_array[i].height = CGDisplayModeGetPixelHeight(mode);
|
||||||
|
} else {
|
||||||
|
_display_information->_display_mode_array[i].width = CGDisplayModeGetWidth(mode);
|
||||||
|
_display_information->_display_mode_array[i].height = CGDisplayModeGetHeight(mode);
|
||||||
|
}
|
||||||
_display_information->_display_mode_array[i].refresh_rate = CGDisplayModeGetRefreshRate(mode);
|
_display_information->_display_mode_array[i].refresh_rate = CGDisplayModeGetRefreshRate(mode);
|
||||||
_display_information->_display_mode_array[i].fullscreen_only = false;
|
_display_information->_display_mode_array[i].fullscreen_only = false;
|
||||||
|
|
||||||
|
@ -63,6 +63,7 @@ public:
|
|||||||
void handle_minimize_event(bool minimized);
|
void handle_minimize_event(bool minimized);
|
||||||
void handle_maximize_event(bool maximized);
|
void handle_maximize_event(bool maximized);
|
||||||
void handle_foreground_event(bool foreground);
|
void handle_foreground_event(bool foreground);
|
||||||
|
void handle_backing_change_event();
|
||||||
bool handle_close_request();
|
bool handle_close_request();
|
||||||
void handle_close_event();
|
void handle_close_event();
|
||||||
void handle_key_event(NSEvent *event);
|
void handle_key_event(NSEvent *event);
|
||||||
|
@ -132,13 +132,20 @@ move_pointer(int device, int x, int y) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Mouse position is expressed in screen points and not pixels, but in Panda3D
|
||||||
|
// we are using pixel coordinates.
|
||||||
|
// Instead of using convertPointFromBacking and have complex logic to cope with
|
||||||
|
// the change of coordinate system, we cheat and directly use the contents scale
|
||||||
|
// of the view layer to convert pixel coordinates into screen point coordinates.
|
||||||
|
CGFloat contents_scale = _view.layer.contentsScale;
|
||||||
if (device == 0) {
|
if (device == 0) {
|
||||||
CGPoint point;
|
CGPoint point;
|
||||||
if (_properties.get_fullscreen()) {
|
if (_properties.get_fullscreen()) {
|
||||||
point = CGPointMake(x, y);
|
point = CGPointMake(float(x) / contents_scale,
|
||||||
|
float(y) / contents_scale);
|
||||||
} else {
|
} else {
|
||||||
point = CGPointMake(x + _properties.get_x_origin(),
|
point = CGPointMake((float(x) + _properties.get_x_origin()) / contents_scale,
|
||||||
y + _properties.get_y_origin());
|
(float(y) + _properties.get_y_origin()) / contents_scale);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (CGWarpMouseCursorPosition(point) == kCGErrorSuccess) {
|
if (CGWarpMouseCursorPosition(point) == kCGErrorSuccess) {
|
||||||
@ -302,35 +309,89 @@ open_window() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Configure the origin and the size of the window.
|
||||||
|
// On macOS, screen coordinates are expressed in "points" which are independant
|
||||||
|
// of the pixel density of the screen. Panda3D however, expresses the size and
|
||||||
|
// origin of a window in pixels.
|
||||||
|
// So, when opening a window, we need to convert the origin and size from pixel
|
||||||
|
// units into point units. However, this conversion depends on the pixel density
|
||||||
|
// of the screen, the backing scale factor.
|
||||||
|
// As the origin and size of a window depends on the size of the screen of the
|
||||||
|
// parent view, their size must be converted first from points to pixels.
|
||||||
|
// If a window (or a view) is not configured to support high-dpi screen, macOS
|
||||||
|
// will upscale the window (or view) when displayed on a high-dpi screen.
|
||||||
|
// Therefore its backing scale factor will always be 1.0
|
||||||
|
// In a Panda3D application, windows are always configured as high resolution
|
||||||
|
// capable, but the view is only configured as high resolution if the dpi-aware
|
||||||
|
// configuration flag is set.
|
||||||
|
// If the app is not dpi-aware, we must upscale its size and origin from points
|
||||||
|
// into pixels as the window is always high resolution capable.
|
||||||
|
|
||||||
// Center the window if coordinates were set to -1 or -2 TODO: perhaps in
|
// Center the window if coordinates were set to -1 or -2 TODO: perhaps in
|
||||||
// future, in the case of -1, it should use the origin used in a previous
|
// future, in the case of -1, it should use the origin used in a previous
|
||||||
// run of Panda
|
// run of Panda
|
||||||
|
|
||||||
|
// Size of the requested window
|
||||||
|
NSSize size = NSMakeSize(_properties.get_x_size(), _properties.get_y_size());
|
||||||
NSRect container;
|
NSRect container;
|
||||||
|
CGFloat backing_scale_factor = screen.backingScaleFactor;
|
||||||
if (parent_nsview != NULL) {
|
if (parent_nsview != NULL) {
|
||||||
container = [parent_nsview bounds];
|
// Convert parent view bounds into pixel units.
|
||||||
|
container = [parent_nsview convertRectToBacking:[parent_nsview bounds]];
|
||||||
|
// If the app is not dpi-aware, we must convert its size from points into
|
||||||
|
// pixels as the window is always high resolution capable
|
||||||
|
if (!dpi_aware) {
|
||||||
|
size = [parent_nsview convertSizeToBacking:size];
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
container = [screen frame];
|
container = [screen frame];
|
||||||
container.origin = NSMakePoint(0, 0);
|
container.origin = NSMakePoint(0, 0);
|
||||||
|
container = [screen convertRectToBacking:container];
|
||||||
|
if (!dpi_aware) {
|
||||||
|
// Weirdly NSScreen does not respond to convertSizeToBacking, so we have to
|
||||||
|
// create a dummy rect just for converting the window size.
|
||||||
|
NSRect rect;
|
||||||
|
rect.origin = NSMakePoint(0, 0);
|
||||||
|
rect.size = size;
|
||||||
|
rect = [screen convertRectToBacking:rect];
|
||||||
|
size = rect.size;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
int x = _properties.get_x_origin();
|
int x = _properties.get_x_origin();
|
||||||
int y = _properties.get_y_origin();
|
int y = _properties.get_y_origin();
|
||||||
|
|
||||||
|
// As we are converting a single value and the view is not created yet, it's
|
||||||
|
// easier to simply use the backing scale factor and don't bother with
|
||||||
|
// coordinate system transformations.
|
||||||
if (x < 0) {
|
if (x < 0) {
|
||||||
x = floor(container.size.width / 2 - _properties.get_x_size() / 2);
|
x = floor(container.size.width / 2 - size.width / 2);
|
||||||
|
} else if (!dpi_aware) {
|
||||||
|
x *= backing_scale_factor;
|
||||||
}
|
}
|
||||||
if (y < 0) {
|
if (y < 0) {
|
||||||
y = floor(container.size.height / 2 - _properties.get_y_size() / 2);
|
y = floor(container.size.height / 2 - size.height / 2);
|
||||||
|
} else if (!dpi_aware) {
|
||||||
|
y *= backing_scale_factor;
|
||||||
|
}
|
||||||
|
if (dpi_aware) {
|
||||||
|
_properties.set_origin(x, y);
|
||||||
|
} else {
|
||||||
|
_properties.set_origin(x / backing_scale_factor, y / backing_scale_factor);
|
||||||
}
|
}
|
||||||
_properties.set_origin(x, y);
|
|
||||||
|
|
||||||
if (_parent_window_handle == (WindowHandle *)NULL) {
|
if (_parent_window_handle == (WindowHandle *)NULL) {
|
||||||
// Content rectangle
|
// Content rectangle
|
||||||
NSRect rect;
|
NSRect rect;
|
||||||
if (_properties.get_fullscreen()) {
|
if (_properties.get_fullscreen()) {
|
||||||
rect = container;
|
rect = [screen convertRectFromBacking:container];
|
||||||
} else {
|
} else {
|
||||||
rect = NSMakeRect(x, container.size.height - _properties.get_y_size() - y,
|
rect = NSMakeRect(x, container.size.height - size.height - y,
|
||||||
_properties.get_x_size(), _properties.get_y_size());
|
size.width, size.height);
|
||||||
|
if (parent_nsview != NULL) {
|
||||||
|
rect = [parent_nsview convertRectFromBacking:rect];
|
||||||
|
} else {
|
||||||
|
rect = [screen convertRectFromBacking:rect];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Configure the window decorations
|
// Configure the window decorations
|
||||||
@ -388,8 +449,10 @@ open_window() {
|
|||||||
_parent_window_handle->attach_child(_window_handle);
|
_parent_window_handle->attach_child(_window_handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Always disable application HiDPI support, Cocoa will do the eventual upscaling for us.
|
// Configure the view to be high resolution capable using the dpi-aware
|
||||||
[_view setWantsBestResolutionOpenGLSurface:NO];
|
// configuration flag. If dpi-aware is false, macOS will upscale the view
|
||||||
|
// for us.
|
||||||
|
[_view setWantsBestResolutionOpenGLSurface:dpi_aware];
|
||||||
if (_properties.has_icon_filename()) {
|
if (_properties.has_icon_filename()) {
|
||||||
NSImage *image = load_image(_properties.get_icon_filename());
|
NSImage *image = load_image(_properties.get_icon_filename());
|
||||||
if (image != nil) {
|
if (image != nil) {
|
||||||
@ -596,22 +659,6 @@ set_properties_now(WindowProperties &properties) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (switched) {
|
if (switched) {
|
||||||
if (_window != nil) {
|
|
||||||
// For some reason, setting the style mask makes it give up its
|
|
||||||
// first-responder status. And for some reason, we need to first
|
|
||||||
// restore the window to normal level before we switch fullscreen,
|
|
||||||
// otherwise we may get a black bar if we're currently on Z_top.
|
|
||||||
if (_properties.get_z_order() != WindowProperties::Z_normal) {
|
|
||||||
[_window setLevel: NSNormalWindowLevel];
|
|
||||||
}
|
|
||||||
if ([_window respondsToSelector:@selector(setStyleMask:)]) {
|
|
||||||
[_window setStyleMask:NSBorderlessWindowMask];
|
|
||||||
}
|
|
||||||
[_window makeFirstResponder:_view];
|
|
||||||
[_window setLevel:CGShieldingWindowLevel()];
|
|
||||||
[_window makeKeyAndOrderFront:nil];
|
|
||||||
}
|
|
||||||
|
|
||||||
// We've already set the size property this way; clear it.
|
// We've already set the size property this way; clear it.
|
||||||
properties.clear_size();
|
properties.clear_size();
|
||||||
_properties.set_size(width, height);
|
_properties.set_size(width, height);
|
||||||
@ -684,6 +731,9 @@ set_properties_now(WindowProperties &properties) {
|
|||||||
NSMiniaturizableWindowMask | NSResizableWindowMask ];
|
NSMiniaturizableWindowMask | NSResizableWindowMask ];
|
||||||
}
|
}
|
||||||
[_window makeFirstResponder:_view];
|
[_window makeFirstResponder:_view];
|
||||||
|
// Resize event fired by makeFirstResponder has an invalid backing scale factor
|
||||||
|
// The actual size must be reset afterward
|
||||||
|
handle_resize_event();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -705,6 +755,9 @@ set_properties_now(WindowProperties &properties) {
|
|||||||
NSMiniaturizableWindowMask | NSResizableWindowMask ];
|
NSMiniaturizableWindowMask | NSResizableWindowMask ];
|
||||||
}
|
}
|
||||||
[_window makeFirstResponder:_view];
|
[_window makeFirstResponder:_view];
|
||||||
|
// Resize event fired by makeFirstResponder has an invalid backing scale factor
|
||||||
|
// The actual size must be reset afterward
|
||||||
|
handle_resize_event();
|
||||||
}
|
}
|
||||||
|
|
||||||
properties.clear_undecorated();
|
properties.clear_undecorated();
|
||||||
@ -715,10 +768,13 @@ set_properties_now(WindowProperties &properties) {
|
|||||||
int height = properties.get_y_size();
|
int height = properties.get_y_size();
|
||||||
|
|
||||||
if (!_properties.get_fullscreen()) {
|
if (!_properties.get_fullscreen()) {
|
||||||
|
// We use the view, not the window, to convert the frame size, expressed
|
||||||
|
// in pixels, into points as the "dpi awareness" is managed by the view.
|
||||||
|
NSSize size = [_view convertSizeFromBacking:NSMakeSize(width, height)];
|
||||||
if (_window != nil) {
|
if (_window != nil) {
|
||||||
[_window setContentSize:NSMakeSize(width, height)];
|
[_window setContentSize:size];
|
||||||
}
|
}
|
||||||
[_view setFrameSize:NSMakeSize(width, height)];
|
[_view setFrameSize:size];
|
||||||
|
|
||||||
if (cocoadisplay_cat.is_debug()) {
|
if (cocoadisplay_cat.is_debug()) {
|
||||||
cocoadisplay_cat.debug()
|
cocoadisplay_cat.debug()
|
||||||
@ -768,12 +824,14 @@ set_properties_now(WindowProperties &properties) {
|
|||||||
// Get the frame for the screen
|
// Get the frame for the screen
|
||||||
NSRect frame;
|
NSRect frame;
|
||||||
NSRect container;
|
NSRect container;
|
||||||
|
// Note again that we are using the view to convert the frame and container
|
||||||
|
// size from points into pixels.
|
||||||
if (_window != nil) {
|
if (_window != nil) {
|
||||||
NSRect window_frame = [_window frame];
|
NSRect window_frame = [_window frame];
|
||||||
frame = [_window contentRectForFrameRect:window_frame];
|
frame = [_view convertRectToBacking:[_window contentRectForFrameRect:window_frame]];
|
||||||
NSScreen *screen = [_window screen];
|
NSScreen *screen = [_window screen];
|
||||||
nassertv(screen != nil);
|
nassertv(screen != nil);
|
||||||
container = [screen frame];
|
container = [_view convertRectToBacking:[screen frame]];
|
||||||
|
|
||||||
// Prevent the centering from overlapping the Dock
|
// Prevent the centering from overlapping the Dock
|
||||||
if (y < 0) {
|
if (y < 0) {
|
||||||
@ -783,8 +841,8 @@ set_properties_now(WindowProperties &properties) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
frame = [_view frame];
|
frame = [_view convertRectToBacking:[_view frame]];
|
||||||
container = [[_view superview] frame];
|
container = [[_view superview] convertRectToBacking:[[_view superview] frame]];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (x < 0) {
|
if (x < 0) {
|
||||||
@ -795,22 +853,22 @@ set_properties_now(WindowProperties &properties) {
|
|||||||
}
|
}
|
||||||
_properties.set_origin(x, y);
|
_properties.set_origin(x, y);
|
||||||
|
|
||||||
if (!_properties.get_fullscreen()) {
|
frame.origin.x = x;
|
||||||
// Remember, Mac OS X coordinates are flipped in the vertical axis.
|
// Y coordinate in backing store is not flipped, but origin is still at the bottom left
|
||||||
frame.origin.x = x;
|
frame.origin.y = y - container.size.height;
|
||||||
frame.origin.y = container.size.height - y - frame.size.height;
|
|
||||||
|
|
||||||
if (cocoadisplay_cat.is_debug()) {
|
if (cocoadisplay_cat.is_debug()) {
|
||||||
cocoadisplay_cat.debug()
|
cocoadisplay_cat.debug()
|
||||||
<< "Setting window content origin to "
|
<< "Setting window content origin to "
|
||||||
<< frame.origin.x << ", " << frame.origin.y << "\n";
|
<< frame.origin.x << ", " << frame.origin.y << "\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_window != nil) {
|
if (_window != nil) {
|
||||||
[_window setFrame:[_window frameRectForContentRect:frame] display:NO];
|
frame = [_view convertRectFromBacking:frame];
|
||||||
} else {
|
[_window setFrame:[_window frameRectForContentRect:frame] display:NO];
|
||||||
[_view setFrame:frame];
|
} else {
|
||||||
}
|
frame = [_view convertRectFromBacking:frame];
|
||||||
|
[_view setFrame:frame];
|
||||||
}
|
}
|
||||||
properties.clear_origin();
|
properties.clear_origin();
|
||||||
}
|
}
|
||||||
@ -957,24 +1015,20 @@ unbind_context() {
|
|||||||
CFMutableArrayRef CocoaGraphicsWindow::
|
CFMutableArrayRef CocoaGraphicsWindow::
|
||||||
find_display_modes(int width, int height) {
|
find_display_modes(int width, int height) {
|
||||||
CFDictionaryRef options = NULL;
|
CFDictionaryRef options = NULL;
|
||||||
// On macOS 10.15+ (Catalina), we want to select the display mode with the
|
// We want to select the display mode with the same scaling factor as the
|
||||||
// samescaling factor as the current view to avoid cropping or scaling issues.
|
// current view to avoid cropping or scaling issues.
|
||||||
// This is a workaround until HiDPI display or scaling factor is properly
|
// CGDisplayCopyAllDisplayModes() does not return upscaled display modes
|
||||||
// handled. CGDisplayCopyAllDisplayModes() does not return upscaled display
|
// nor the current mode, unless explicitly asked with
|
||||||
// mode unless explicitly asked with kCGDisplayShowDuplicateLowResolutionModes
|
// kCGDisplayShowDuplicateLowResolutionModes
|
||||||
// (which is undocumented...).
|
// (which is undocumented...).
|
||||||
bool macos_10_15_or_higher = false;
|
const CFStringRef dictkeys[] = {kCGDisplayShowDuplicateLowResolutionModes};
|
||||||
if (@available(macOS 10.15, *)) {
|
const CFBooleanRef dictvalues[] = {kCFBooleanTrue};
|
||||||
const CFStringRef dictkeys[] = {kCGDisplayShowDuplicateLowResolutionModes};
|
options = CFDictionaryCreate(NULL,
|
||||||
const CFBooleanRef dictvalues[] = {kCFBooleanTrue};
|
(const void **)dictkeys,
|
||||||
options = CFDictionaryCreate(NULL,
|
(const void **)dictvalues,
|
||||||
(const void **)dictkeys,
|
1,
|
||||||
(const void **)dictvalues,
|
&kCFCopyStringDictionaryKeyCallBacks,
|
||||||
1,
|
&kCFTypeDictionaryValueCallBacks);
|
||||||
&kCFCopyStringDictionaryKeyCallBacks,
|
|
||||||
&kCFTypeDictionaryValueCallBacks);
|
|
||||||
macos_10_15_or_higher = true;
|
|
||||||
}
|
|
||||||
CFMutableArrayRef valid_modes;
|
CFMutableArrayRef valid_modes;
|
||||||
valid_modes = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
|
valid_modes = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
|
||||||
|
|
||||||
@ -985,29 +1039,39 @@ find_display_modes(int width, int height) {
|
|||||||
|
|
||||||
size_t num_modes = CFArrayGetCount(modes);
|
size_t num_modes = CFArrayGetCount(modes);
|
||||||
CGDisplayModeRef mode;
|
CGDisplayModeRef mode;
|
||||||
|
|
||||||
// Get the current refresh rate and pixel encoding.
|
|
||||||
CFStringRef current_pixel_encoding;
|
|
||||||
double refresh_rate;
|
|
||||||
mode = CGDisplayCopyDisplayMode(_display);
|
mode = CGDisplayCopyDisplayMode(_display);
|
||||||
|
|
||||||
|
// Calculate requested display size and pixel size
|
||||||
|
CGSize display_size;
|
||||||
|
CGSize pixel_size;
|
||||||
|
if (dpi_aware) {
|
||||||
|
pixel_size = NSMakeSize(width, height);
|
||||||
|
display_size = [_view convertSizeFromBacking:pixel_size];
|
||||||
|
} else {
|
||||||
|
display_size = NSMakeSize(width, height);
|
||||||
|
// Calculate the pixel width and height of the fullscreen mode we want using
|
||||||
|
// the current display mode dimensions and pixel dimensions.
|
||||||
|
size_t pixel_width = (size_t(width) * CGDisplayModeGetPixelWidth(mode)) / CGDisplayModeGetWidth(mode);
|
||||||
|
size_t pixel_height = (size_t(height) * CGDisplayModeGetPixelHeight(mode)) / CGDisplayModeGetHeight(mode);
|
||||||
|
pixel_size = NSMakeSize(pixel_width, pixel_height);
|
||||||
|
}
|
||||||
|
|
||||||
// First check if the current mode is adequate.
|
// First check if the current mode is adequate.
|
||||||
// This test not done for macOS 10.15 and above as the mode resolution is
|
if (CGDisplayModeGetWidth(mode) == display_size.width &&
|
||||||
// not enough to identify a mode.
|
CGDisplayModeGetHeight(mode) == display_size.height &&
|
||||||
if (!macos_10_15_or_higher &&
|
CGDisplayModeGetPixelWidth(mode) == pixel_size.width &&
|
||||||
CGDisplayModeGetWidth(mode) == width &&
|
CGDisplayModeGetPixelHeight(mode) == pixel_size.height) {
|
||||||
CGDisplayModeGetHeight(mode) == height) {
|
|
||||||
CFArrayAppendValue(valid_modes, mode);
|
CFArrayAppendValue(valid_modes, mode);
|
||||||
CGDisplayModeRelease(mode);
|
CGDisplayModeRelease(mode);
|
||||||
return valid_modes;
|
return valid_modes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get the current refresh rate and pixel encoding.
|
||||||
|
CFStringRef current_pixel_encoding;
|
||||||
|
double refresh_rate;
|
||||||
|
|
||||||
current_pixel_encoding = CGDisplayModeCopyPixelEncoding(mode);
|
current_pixel_encoding = CGDisplayModeCopyPixelEncoding(mode);
|
||||||
refresh_rate = CGDisplayModeGetRefreshRate(mode);
|
refresh_rate = CGDisplayModeGetRefreshRate(mode);
|
||||||
// Calculate the pixel width and height of the fullscreen mode we want using
|
|
||||||
// the currentdisplay mode dimensions and pixel dimensions.
|
|
||||||
size_t expected_pixel_width = (size_t(width) * CGDisplayModeGetPixelWidth(mode)) / CGDisplayModeGetWidth(mode);
|
|
||||||
size_t expected_pixel_height = (size_t(height) * CGDisplayModeGetPixelHeight(mode)) / CGDisplayModeGetHeight(mode);
|
|
||||||
CGDisplayModeRelease(mode);
|
CGDisplayModeRelease(mode);
|
||||||
|
|
||||||
for (size_t i = 0; i < num_modes; ++i) {
|
for (size_t i = 0; i < num_modes; ++i) {
|
||||||
@ -1015,17 +1079,15 @@ find_display_modes(int width, int height) {
|
|||||||
|
|
||||||
CFStringRef pixel_encoding = CGDisplayModeCopyPixelEncoding(mode);
|
CFStringRef pixel_encoding = CGDisplayModeCopyPixelEncoding(mode);
|
||||||
|
|
||||||
// As explained above, we want to select the fullscreen display mode using
|
// We select the fullscreen display mode using he same scaling factor
|
||||||
// the same scaling factor, but only for MacOS 10.15+ To do this we check
|
// To do this we check the mode width and height but also actual pixel widh
|
||||||
// the mode width and height but also actual pixel widh and height.
|
// and height.
|
||||||
if (CGDisplayModeGetWidth(mode) == width &&
|
if (CGDisplayModeGetWidth(mode) == display_size.width &&
|
||||||
CGDisplayModeGetHeight(mode) == height &&
|
CGDisplayModeGetHeight(mode) == display_size.height &&
|
||||||
(int)(CGDisplayModeGetRefreshRate(mode) + 0.5) == (int)(refresh_rate + 0.5) &&
|
(int)(CGDisplayModeGetRefreshRate(mode) + 0.5) == (int)(refresh_rate + 0.5) &&
|
||||||
(!macos_10_15_or_higher ||
|
CGDisplayModeGetPixelWidth(mode) == pixel_size.width &&
|
||||||
(CGDisplayModeGetPixelWidth(mode) == expected_pixel_width &&
|
CGDisplayModeGetPixelHeight(mode) == pixel_size.height &&
|
||||||
CGDisplayModeGetPixelHeight(mode) == expected_pixel_height)) &&
|
|
||||||
CFStringCompare(pixel_encoding, current_pixel_encoding, 0) == kCFCompareEqualTo) {
|
CFStringCompare(pixel_encoding, current_pixel_encoding, 0) == kCFCompareEqualTo) {
|
||||||
|
|
||||||
if (CGDisplayModeGetRefreshRate(mode) == refresh_rate) {
|
if (CGDisplayModeGetRefreshRate(mode) == refresh_rate) {
|
||||||
// Exact match for refresh rate, prioritize this.
|
// Exact match for refresh rate, prioritize this.
|
||||||
CFArrayInsertValueAtIndex(valid_modes, 0, mode);
|
CFArrayInsertValueAtIndex(valid_modes, 0, mode);
|
||||||
@ -1103,14 +1165,25 @@ do_switch_fullscreen(CGDisplayModeRef mode) {
|
|||||||
|
|
||||||
NSRect frame = [[[_view window] screen] frame];
|
NSRect frame = [[[_view window] screen] frame];
|
||||||
if (cocoadisplay_cat.is_debug()) {
|
if (cocoadisplay_cat.is_debug()) {
|
||||||
NSString *str = NSStringFromRect(frame);
|
NSString *str = NSStringFromSize([_view convertSizeToBacking:frame.size]);
|
||||||
cocoadisplay_cat.debug()
|
cocoadisplay_cat.debug()
|
||||||
<< "Switched to fullscreen, screen rect is now " << [str UTF8String] << "\n";
|
<< "Switched to fullscreen, screen size is now " << [str UTF8String] << "\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_window != nil) {
|
if (_window != nil) {
|
||||||
|
// For some reason, setting the style mask makes it give up its
|
||||||
|
// first-responder status.
|
||||||
|
if ([_window respondsToSelector:@selector(setStyleMask:)]) {
|
||||||
|
[_window setStyleMask:NSBorderlessWindowMask];
|
||||||
|
}
|
||||||
|
[_window makeFirstResponder:_view];
|
||||||
|
[_window setLevel:CGShieldingWindowLevel()];
|
||||||
|
[_window makeKeyAndOrderFront:nil];
|
||||||
|
|
||||||
|
// Window and view frame must be updated *after* the window reconfiguration
|
||||||
|
// or the size is not set properly !
|
||||||
[_window setFrame:frame display:YES];
|
[_window setFrame:frame display:YES];
|
||||||
[_view setFrame:NSMakeRect(0, 0, frame.size.width, frame.size.height)];
|
[_view setFrame:frame];
|
||||||
[_window update];
|
[_window update];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1252,19 +1325,25 @@ load_cursor(const Filename &filename) {
|
|||||||
*/
|
*/
|
||||||
void CocoaGraphicsWindow::
|
void CocoaGraphicsWindow::
|
||||||
handle_move_event() {
|
handle_move_event() {
|
||||||
// Remember, Mac OS X uses flipped coordinates
|
|
||||||
NSRect frame;
|
NSRect frame;
|
||||||
|
NSRect container;
|
||||||
int x, y;
|
int x, y;
|
||||||
|
// Again, we are using the view to convert the frame and container size from
|
||||||
|
// points to pixels.
|
||||||
if (_window == nil) {
|
if (_window == nil) {
|
||||||
frame = [_view frame];
|
frame = [_view convertRectToBacking:[_view frame]];
|
||||||
x = frame.origin.x;
|
container = [_view convertRectToBacking:[[_view superview] frame]];
|
||||||
y = [[_view superview] bounds].size.height - frame.origin.y - frame.size.height;
|
|
||||||
} else {
|
} else {
|
||||||
frame = [_window contentRectForFrameRect:[_window frame]];
|
frame = [_view convertRectToBacking:[_window contentRectForFrameRect:[_window frame]]];
|
||||||
x = frame.origin.x;
|
NSScreen *screen = [_window screen];
|
||||||
y = [[_window screen] frame].size.height - frame.origin.y - frame.size.height;
|
nassertv(screen != nil);
|
||||||
|
container = [_view convertRectToBacking:[screen frame]];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Y coordinate in backing store is not flipped, but origin is still at the bottom left
|
||||||
|
x = frame.origin.x;
|
||||||
|
y = container.size.height + frame.origin.y;
|
||||||
|
|
||||||
if (x != _properties.get_x_origin() ||
|
if (x != _properties.get_x_origin() ||
|
||||||
y != _properties.get_y_origin()) {
|
y != _properties.get_y_origin()) {
|
||||||
|
|
||||||
@ -1290,7 +1369,7 @@ handle_resize_event() {
|
|||||||
[_view setFrameSize:contentRect.size];
|
[_view setFrameSize:contentRect.size];
|
||||||
}
|
}
|
||||||
|
|
||||||
NSRect frame = [_view convertRect:[_view bounds] toView:nil];
|
NSRect frame = [_view convertRectToBacking:[_view bounds]];
|
||||||
|
|
||||||
WindowProperties properties;
|
WindowProperties properties;
|
||||||
bool changed = false;
|
bool changed = false;
|
||||||
@ -1403,6 +1482,22 @@ handle_foreground_event(bool foreground) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called by the window delegate when the properties of backing store of the
|
||||||
|
* window have changed.
|
||||||
|
*/
|
||||||
|
void CocoaGraphicsWindow::
|
||||||
|
handle_backing_change_event() {
|
||||||
|
if (cocoadisplay_cat.is_debug()) {
|
||||||
|
cocoadisplay_cat.debug() << "Backing store properties have changed\n";
|
||||||
|
}
|
||||||
|
// Trigger a resize event to update the window size in case the backing scale
|
||||||
|
// factor did change.
|
||||||
|
handle_resize_event();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called by the window delegate when the user requests to close the window.
|
* Called by the window delegate when the user requests to close the window.
|
||||||
* This may not always be called, which is why there is also a
|
* This may not always be called, which is why there is also a
|
||||||
@ -1693,6 +1788,13 @@ void CocoaGraphicsWindow::
|
|||||||
handle_mouse_moved_event(bool in_window, double x, double y, bool absolute) {
|
handle_mouse_moved_event(bool in_window, double x, double y, bool absolute) {
|
||||||
double nx, ny;
|
double nx, ny;
|
||||||
|
|
||||||
|
// Mouse position is received in screen points and not pixels, but in Panda3D
|
||||||
|
// we want to have the coordinates expressed in pixels.
|
||||||
|
// Instead of using convertPointFrom/toBackingStore and have complex logic to
|
||||||
|
// cope with the change of coordinate system, we cheat and directly use the
|
||||||
|
// contents scale of the view layer to convert screen point into pixels and
|
||||||
|
// vice-versa.
|
||||||
|
CGFloat contents_scale = _view.layer.contentsScale;
|
||||||
if (absolute) {
|
if (absolute) {
|
||||||
if (cocoadisplay_cat.is_spam()) {
|
if (cocoadisplay_cat.is_spam()) {
|
||||||
if (in_window != _input->get_pointer().get_in_window()) {
|
if (in_window != _input->get_pointer().get_in_window()) {
|
||||||
@ -1704,14 +1806,14 @@ handle_mouse_moved_event(bool in_window, double x, double y, bool absolute) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
nx = x;
|
nx = x * contents_scale;
|
||||||
ny = y;
|
ny = y * contents_scale;
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// We received deltas, so add it to the current mouse position.
|
// We received deltas, so add it to the current mouse position.
|
||||||
PointerData md = _input->get_pointer();
|
PointerData md = _input->get_pointer();
|
||||||
nx = md.get_x() + x;
|
nx = md.get_x() + x * contents_scale;
|
||||||
ny = md.get_y() + y;
|
ny = md.get_y() + y * contents_scale;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_properties.get_mouse_mode() == WindowProperties::M_confined
|
if (_properties.get_mouse_mode() == WindowProperties::M_confined
|
||||||
@ -1721,11 +1823,13 @@ handle_mouse_moved_event(bool in_window, double x, double y, bool absolute) {
|
|||||||
nx = std::max(0., std::min((double) get_x_size() - 1, nx));
|
nx = std::max(0., std::min((double) get_x_size() - 1, nx));
|
||||||
ny = std::max(0., std::min((double) get_y_size() - 1, ny));
|
ny = std::max(0., std::min((double) get_y_size() - 1, ny));
|
||||||
|
|
||||||
|
// Convert back mouse position to screen space using point units
|
||||||
if (_properties.get_fullscreen()) {
|
if (_properties.get_fullscreen()) {
|
||||||
point = CGPointMake(nx, ny);
|
point = CGPointMake(nx / contents_scale,
|
||||||
|
ny / contents_scale);
|
||||||
} else {
|
} else {
|
||||||
point = CGPointMake(nx + _properties.get_x_origin(),
|
point = CGPointMake((nx + _properties.get_x_origin()) / contents_scale,
|
||||||
ny + _properties.get_y_origin());
|
(ny + _properties.get_y_origin()) / contents_scale);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (CGWarpMouseCursorPosition(point) == kCGErrorSuccess) {
|
if (CGWarpMouseCursorPosition(point) == kCGErrorSuccess) {
|
||||||
|
@ -29,6 +29,7 @@ class CocoaGraphicsWindow;
|
|||||||
- (void)windowDidDeminiaturize:(NSNotification *)notification;
|
- (void)windowDidDeminiaturize:(NSNotification *)notification;
|
||||||
- (void)windowDidBecomeKey:(NSNotification *)notification;
|
- (void)windowDidBecomeKey:(NSNotification *)notification;
|
||||||
- (void)windowDidResignKey:(NSNotification *)notification;
|
- (void)windowDidResignKey:(NSNotification *)notification;
|
||||||
|
- (void)windowDidChangeBackingProperties:(NSNotification *)notification;
|
||||||
- (BOOL)windowShouldClose:(id)sender;
|
- (BOOL)windowShouldClose:(id)sender;
|
||||||
- (void)windowWillClose:(id)sender;
|
- (void)windowWillClose:(id)sender;
|
||||||
|
|
||||||
|
@ -51,6 +51,10 @@
|
|||||||
_graphicsWindow->handle_foreground_event(false);
|
_graphicsWindow->handle_foreground_event(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (void) windowDidChangeBackingProperties:(NSNotification *)notification {
|
||||||
|
_graphicsWindow->handle_backing_change_event();
|
||||||
|
}
|
||||||
|
|
||||||
- (BOOL) windowShouldClose:(id)sender {
|
- (BOOL) windowShouldClose:(id)sender {
|
||||||
if (cocoadisplay_cat.is_debug()) {
|
if (cocoadisplay_cat.is_debug()) {
|
||||||
cocoadisplay_cat.debug()
|
cocoadisplay_cat.debug()
|
||||||
|
@ -21,6 +21,7 @@
|
|||||||
NotifyCategoryDecl(cocoadisplay, EXPCL_PANDA_COCOADISPLAY, EXPTP_PANDA_COCOADISPLAY);
|
NotifyCategoryDecl(cocoadisplay, EXPCL_PANDA_COCOADISPLAY, EXPTP_PANDA_COCOADISPLAY);
|
||||||
|
|
||||||
extern ConfigVariableBool cocoa_invert_wheel_x;
|
extern ConfigVariableBool cocoa_invert_wheel_x;
|
||||||
|
extern ConfigVariableBool dpi_aware;
|
||||||
|
|
||||||
extern EXPCL_PANDA_COCOADISPLAY void init_libcocoadisplay();
|
extern EXPCL_PANDA_COCOADISPLAY void init_libcocoadisplay();
|
||||||
|
|
||||||
|
@ -32,6 +32,11 @@ ConfigVariableBool cocoa_invert_wheel_x
|
|||||||
("cocoa-invert-wheel-x", false,
|
("cocoa-invert-wheel-x", false,
|
||||||
PRC_DESC("Set this to true to swap the wheel_left and wheel_right mouse "
|
PRC_DESC("Set this to true to swap the wheel_left and wheel_right mouse "
|
||||||
"button events, to restore to the pre-1.10.12 behavior."));
|
"button events, to restore to the pre-1.10.12 behavior."));
|
||||||
|
ConfigVariableBool dpi_aware
|
||||||
|
("dpi-aware", false,
|
||||||
|
PRC_DESC("The default behavior on macOS is for Panda3D to use upscaling on"
|
||||||
|
"high DPI screen. Set this to true to let the application use the"
|
||||||
|
"actual pixel density of the screen."));
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes the library. This must be called at least once before any of
|
* Initializes the library. This must be called at least once before any of
|
||||||
|
Loading…
x
Reference in New Issue
Block a user