mirror of
https://gitlab.gnome.org/GNOME/gtk.git
synced 2025-01-15 14:50:06 +00:00
8b71cff71d
This provides a major shift in how we draw both when accelerated OpenGL as well as software rendering with Cairo. In short, it uses tiles of Core Animation's CALayer to display contents from an OpenGL or Cairo rendering so that the window can provide partial damage updates. Partial damage is not generally available when using OpenGL as the whole buffer is flipped even if you only submitted a small change using a scissor rect. Thankfully, this speeds up Cairo rendering a bit too by using IOSurface to upload contents to the display server. We use the tiling system we do for OpenGL which reduces overall complexity and differences between them. A New Buffer ============ GdkMacosBuffer is a wrapper around an IOSurfaceRef. The term buffer was used because 1) surface is already used and 2) it loosely maps to a front/back buffer semantic. However, it appears that IOSurfaceRef contents are being retained in some fashion (likely in the compositor result) so we can update the same IOSurfaceRef without flipping as long as we're fast. This appears to be what Chromium does as well, but Firefox uses two IOSurfaceRef and flips between them. We would like to avoid two surfaces because it doubles the GPU VRAM requirements of the application. Changes to Windows ================== Previously, the NSWindow would dynamically change between different types of NSView based on the renderer being used. This is no longer necessary as we just have a single NSView type, GdkMacosView, which inherits from GdkMacosBaseView just to keep the tedius stuff separate from the machinery of GdkMacosView. We can merge those someday if we are okay with that. Changes to Views ================ GdkMacosCairoView, GdkMacosCairoSubView, GdkMacosGLView have all been removed and replaced with GdkMacosView. This new view has a single CALayer (GdkMacosLayer) attached to it which itself has sublayers. The contents of the CALayer is populated with an IOSurfaceRef which we allocated with the GdkMacosSurface. The surface is replaced when the NSWindow resizes. Changes to Layers ================= We now have a dedicated GdkMacosLayer which contains sublayers of GdkMacosTile. The tile has a maximum size of 128x128 pixels in device units. The GdkMacosTile is partitioned by splitting both the transparent region (window bounds minus opaque area) and then by splitting the opaque area. A tile has either translucent contents (and therefore is not opaque) or has opaque contents (and therefore is opaque). An opaque tile never contains transparent contents. As such, the opaque tiles contain a black background so that Core Animation will consider the tile's bounds as opaque. This can be verified with "Quartz Debug -> Show opaque regions". Changes to Cairo ================ GTK 4 cannot currently use cairo-quartz because of how CSS borders are rendered. It simply causes errors in the cairo_quartz_surface_t backend. Since we are restricted to using cairo_image_surface_t (which happens to be faster anyway) we can use the IOSurfaceBaseAddress() to obtain a mapping of the IOSurfaceRef in user-space. It always uses BGRA 32-bit with alpha channel even if we will discard the alpha channel as that is necessary to hit the fast paths in other parts of the platform. Note that while Cairo says CAIRO_FORMAT_ARGB32, it is really 32-bit BGRA on little-endian as we expect. OpenGL will render flipped (Quartz Native Co-ordinates) while Cairo renders with 0,O in the top-left. We could use cairo_translate() and cairo_scale() to reverse this, but it looks like some cairo things may not look quite as right if we do so. To reduce the chances of one-off bugs this continues to draw as Cairo would normally, but instead uses an CGAffineTransform in the tiles and some CGRect translation when swapping buffers to get the same effect. Changes to OpenGL ================= To simplify things, removal of all NSOpenGL* related components have been removed and we strictly use the Core GL (CGL*) API. This probably should have been done long ago anyay. Most examples found in the browsers to use IOSurfaceRef with OpenGL are using Legacy GL and there is still work underway to make this fit in with the rest of how the GSK GL renderer works. Since IOSurfaceRef bound to a texture/framebuffer will not have a default framebuffer ID of 0, we needed to add a default framebuffer id to the GdkGLContext. GskGLRenderer can use this to setup the command queue in such a way that our IOSurface destination has been glBindFramebuffer() as if it were the default drawable. This stuff is pretty slight-of-hand, so where things are and what needs flushing when and where has been a bit of an experiment to see what actually works to get synchronization across subsystems. Efficient Damages ================= After we draw with Cairo, we unlock the IOSurfaceRef and the contents are uploaded to the GPU. To make the contents visible to the app, we must clear the tiles contents with `layer.contents=nil;` and then re-apply the IOSurfaceRef. Since the buffer has likely not changed, we only do this if the tile overlaps the damage region. This gives the effect of having more tightly controlled damage regions even though updating the layer would damage be the whole window (as it is with OpenGL/Metal today with the exception of scissor-rect). This too can be verified usign "Quartz Debug -> Flash screen udpates". Frame Synchronized Resize ========================= In GTK 4, we have the ability to perform sizing changes from compute-size during the layout phase. Since the macOS backend already tracks window resizes manually, we can avoid doing the setFrame: immediately and instead do it within the frame clock's layout phase. Doing so gives us vastly better resize experience as we're more likely to get the size-change and updated-contents in the same frame on screen. It makes things feel "connected" in a way they weren't before. Some additional effort to tweak gravity during the process is also necessary but we were already doing that in the GTK 4 backend. Backporting =========== The design here has made an attempt to make it possible to backport by keeping GdkMacosBuffer, GdkMacosLayer, and GdkMacosTile fairly independent. There may be an opportunity to integrate this into GTK 3's quartz backend with a fair bit of work. Doing so could improve the situation for applications which are damage-rich such as The GIMP.
818 lines
23 KiB
C
818 lines
23 KiB
C
/* GdkMacosWindow.m
|
|
*
|
|
* Copyright © 2020 Red Hat, Inc.
|
|
* Copyright © 2005-2007 Imendio AB
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2 of the License, or (at your option) any later version.
|
|
*
|
|
* This library 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
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
|
*
|
|
* SPDX-License-Identifier: LGPL-2.1-or-later
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include <gdk/gdk.h>
|
|
|
|
#import "GdkMacosBaseView.h"
|
|
#import "GdkMacosView.h"
|
|
#import "GdkMacosWindow.h"
|
|
|
|
#include "gdkmacosclipboard-private.h"
|
|
#include "gdkmacosdisplay-private.h"
|
|
#include "gdkmacosdrop-private.h"
|
|
#include "gdkmacosmonitor-private.h"
|
|
#include "gdkmacossurface-private.h"
|
|
#include "gdkmacospopupsurface-private.h"
|
|
#include "gdkmacostoplevelsurface-private.h"
|
|
#include "gdkmacosutils-private.h"
|
|
|
|
#include "gdkeventsprivate.h"
|
|
#include "gdkmonitorprivate.h"
|
|
#include "gdksurfaceprivate.h"
|
|
|
|
#ifndef AVAILABLE_MAC_OS_X_VERSION_10_15_AND_LATER
|
|
typedef NSString *CALayerContentsGravity;
|
|
#endif
|
|
|
|
@implementation GdkMacosWindow
|
|
|
|
-(BOOL)windowShouldClose:(id)sender
|
|
{
|
|
GdkDisplay *display;
|
|
GdkEvent *event;
|
|
GList *node;
|
|
|
|
display = gdk_surface_get_display (GDK_SURFACE (gdk_surface));
|
|
event = gdk_delete_event_new (GDK_SURFACE (gdk_surface));
|
|
node = _gdk_event_queue_append (display, event);
|
|
_gdk_windowing_got_event (display, node, event,
|
|
_gdk_display_get_next_serial (display));
|
|
|
|
return NO;
|
|
}
|
|
|
|
-(void)windowWillMiniaturize:(NSNotification *)aNotification
|
|
{
|
|
if (GDK_IS_MACOS_TOPLEVEL_SURFACE (gdk_surface))
|
|
_gdk_macos_toplevel_surface_detach_from_parent (GDK_MACOS_TOPLEVEL_SURFACE (gdk_surface));
|
|
else if (GDK_IS_MACOS_POPUP_SURFACE (gdk_surface))
|
|
_gdk_macos_popup_surface_detach_from_parent (GDK_MACOS_POPUP_SURFACE (gdk_surface));
|
|
}
|
|
|
|
-(void)windowDidMiniaturize:(NSNotification *)aNotification
|
|
{
|
|
gdk_synthesize_surface_state (GDK_SURFACE (gdk_surface), 0, GDK_TOPLEVEL_STATE_MINIMIZED);
|
|
}
|
|
|
|
-(void)windowDidDeminiaturize:(NSNotification *)aNotification
|
|
{
|
|
if (GDK_IS_MACOS_TOPLEVEL_SURFACE (gdk_surface))
|
|
_gdk_macos_toplevel_surface_attach_to_parent (GDK_MACOS_TOPLEVEL_SURFACE (gdk_surface));
|
|
else if (GDK_IS_MACOS_POPUP_SURFACE (gdk_surface))
|
|
_gdk_macos_popup_surface_attach_to_parent (GDK_MACOS_POPUP_SURFACE (gdk_surface));
|
|
|
|
gdk_synthesize_surface_state (GDK_SURFACE (gdk_surface), GDK_TOPLEVEL_STATE_MINIMIZED, 0);
|
|
}
|
|
|
|
-(void)windowDidBecomeKey:(NSNotification *)aNotification
|
|
{
|
|
gdk_synthesize_surface_state (GDK_SURFACE (gdk_surface), 0, GDK_TOPLEVEL_STATE_FOCUSED);
|
|
_gdk_macos_display_surface_became_key ([self gdkDisplay], gdk_surface);
|
|
}
|
|
|
|
-(void)windowDidResignKey:(NSNotification *)aNotification
|
|
{
|
|
gdk_synthesize_surface_state (GDK_SURFACE (gdk_surface), GDK_TOPLEVEL_STATE_FOCUSED, 0);
|
|
_gdk_macos_display_surface_resigned_key ([self gdkDisplay], gdk_surface);
|
|
}
|
|
|
|
-(void)windowDidBecomeMain:(NSNotification *)aNotification
|
|
{
|
|
if (![self isVisible])
|
|
{
|
|
/* Note: This is a hack needed because for unknown reasons, hidden
|
|
* windows get shown when clicking the dock icon when the application
|
|
* is not already active.
|
|
*/
|
|
[self orderOut:nil];
|
|
return;
|
|
}
|
|
|
|
_gdk_macos_display_surface_became_main ([self gdkDisplay], gdk_surface);
|
|
}
|
|
|
|
-(void)windowDidResignMain:(NSNotification *)aNotification
|
|
{
|
|
_gdk_macos_display_surface_resigned_main ([self gdkDisplay], gdk_surface);
|
|
}
|
|
|
|
/* Used in combination with NSLeftMouseUp in sendEvent to keep track
|
|
* of when the window is being moved with the mouse.
|
|
*/
|
|
-(void)windowWillMove:(NSNotification *)aNotification
|
|
{
|
|
inMove = YES;
|
|
}
|
|
|
|
-(void)sendEvent:(NSEvent *)event
|
|
{
|
|
NSEventType event_type = [event type];
|
|
|
|
switch ((int)event_type)
|
|
{
|
|
case NSEventTypeLeftMouseUp: {
|
|
GdkDisplay *display = gdk_surface_get_display (GDK_SURFACE (gdk_surface));
|
|
double time = ((double)[event timestamp]) * 1000.0;
|
|
|
|
inManualMove = NO;
|
|
inManualResize = NO;
|
|
inMove = NO;
|
|
|
|
/* We need to deliver the event to the proper drag gestures or we
|
|
* will leave the window in inconsistent state that requires clicking
|
|
* in the window to cancel the gesture.
|
|
*
|
|
* TODO: Can we improve grab breaking to fix this?
|
|
*/
|
|
_gdk_macos_display_send_button_event ([self gdkDisplay], event);
|
|
|
|
_gdk_macos_display_break_all_grabs (GDK_MACOS_DISPLAY (display), time);
|
|
|
|
/* Reset gravity */
|
|
[[[self contentView] layer] setContentsGravity:kCAGravityBottomLeft];
|
|
|
|
break;
|
|
}
|
|
|
|
case NSEventTypeLeftMouseDragged:
|
|
if ([self trackManualMove] || [self trackManualResize])
|
|
return;
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
[super sendEvent:event];
|
|
}
|
|
|
|
-(BOOL)isInMove
|
|
{
|
|
return inMove;
|
|
}
|
|
|
|
-(void)checkSendEnterNotify
|
|
{
|
|
/* When a new window has been created, and the mouse is in the window
|
|
* area, we will not receive an NSEventTypeMouseEntered event.
|
|
* Therefore, we synthesize an enter notify event manually.
|
|
*/
|
|
if (!initialPositionKnown)
|
|
{
|
|
initialPositionKnown = YES;
|
|
|
|
if (NSPointInRect ([NSEvent mouseLocation], [self frame]))
|
|
{
|
|
GdkMacosBaseView *view = (GdkMacosBaseView *)[self contentView];
|
|
NSEvent *event;
|
|
|
|
event = [NSEvent enterExitEventWithType: NSEventTypeMouseEntered
|
|
location: [self mouseLocationOutsideOfEventStream]
|
|
modifierFlags: 0
|
|
timestamp: [[NSApp currentEvent] timestamp]
|
|
windowNumber: [self windowNumber]
|
|
context: NULL
|
|
eventNumber: 0
|
|
trackingNumber: (NSInteger)[view trackingArea]
|
|
userData: nil];
|
|
|
|
[NSApp postEvent:event atStart:NO];
|
|
}
|
|
}
|
|
}
|
|
|
|
-(void)setFrame:(NSRect)frame display:(BOOL)display
|
|
{
|
|
NSRect contentRect = [self contentRectForFrameRect:frame];
|
|
GdkSurface *surface = GDK_SURFACE (gdk_surface);
|
|
gboolean maximized = (surface->state & GDK_TOPLEVEL_STATE_MAXIMIZED) != 0;
|
|
|
|
if (maximized && !inMaximizeTransition && !NSEqualRects (lastMaximizedFrame, frame))
|
|
{
|
|
gdk_synthesize_surface_state (surface, GDK_TOPLEVEL_STATE_MAXIMIZED, 0);
|
|
_gdk_surface_update_size (surface);
|
|
}
|
|
|
|
[super setFrame:frame display:display];
|
|
[[self contentView] setFrame:NSMakeRect (0, 0, contentRect.size.width, contentRect.size.height)];
|
|
}
|
|
|
|
-(id)initWithContentRect:(NSRect)contentRect
|
|
styleMask:(NSWindowStyleMask)styleMask
|
|
backing:(NSBackingStoreType)backingType
|
|
defer:(BOOL)flag
|
|
screen:(NSScreen *)screen
|
|
{
|
|
GdkMacosView *view;
|
|
|
|
self = [super initWithContentRect:contentRect
|
|
styleMask:styleMask
|
|
backing:backingType
|
|
defer:flag
|
|
screen:screen];
|
|
|
|
[self setAcceptsMouseMovedEvents:YES];
|
|
[self setDelegate:(id<NSWindowDelegate>)self];
|
|
[self setReleasedWhenClosed:YES];
|
|
[self setPreservesContentDuringLiveResize:NO];
|
|
|
|
view = [[GdkMacosView alloc] initWithFrame:contentRect];
|
|
[self setContentView:view];
|
|
[view release];
|
|
|
|
/* TODO: We might want to make this more extensible at some point */
|
|
_gdk_macos_clipboard_register_drag_types (self);
|
|
|
|
return self;
|
|
}
|
|
|
|
-(BOOL)canBecomeMainWindow
|
|
{
|
|
return GDK_IS_TOPLEVEL (gdk_surface);
|
|
}
|
|
|
|
-(BOOL)canBecomeKeyWindow
|
|
{
|
|
return GDK_IS_TOPLEVEL (gdk_surface) || GDK_IS_POPUP (gdk_surface);
|
|
}
|
|
|
|
-(void)showAndMakeKey:(BOOL)makeKey
|
|
{
|
|
inShowOrHide = YES;
|
|
|
|
if (makeKey && [self canBecomeKeyWindow])
|
|
[self makeKeyAndOrderFront:nil];
|
|
else
|
|
[self orderFront:nil];
|
|
|
|
inShowOrHide = NO;
|
|
|
|
[self checkSendEnterNotify];
|
|
}
|
|
|
|
-(void)hide
|
|
{
|
|
BOOL wasKey = [self isKeyWindow];
|
|
BOOL wasMain = [self isMainWindow];
|
|
|
|
inShowOrHide = YES;
|
|
[self orderOut:nil];
|
|
inShowOrHide = NO;
|
|
|
|
initialPositionKnown = NO;
|
|
|
|
if (wasMain)
|
|
[self windowDidResignMain:nil];
|
|
|
|
if (wasKey)
|
|
[self windowDidResignKey:nil];
|
|
}
|
|
|
|
-(BOOL)trackManualMove
|
|
{
|
|
NSRect windowFrame;
|
|
NSPoint currentLocation;
|
|
GdkMonitor *monitor;
|
|
GdkRectangle geometry;
|
|
GdkRectangle workarea;
|
|
int shadow_top = 0;
|
|
int shadow_left = 0;
|
|
int shadow_right = 0;
|
|
int shadow_bottom = 0;
|
|
GdkRectangle window_gdk;
|
|
GdkPoint pointer_position;
|
|
GdkPoint new_origin;
|
|
|
|
if (!inManualMove)
|
|
return NO;
|
|
|
|
/* Get our shadow so we can adjust the window position sans-shadow */
|
|
_gdk_macos_surface_get_shadow (gdk_surface,
|
|
&shadow_top,
|
|
&shadow_right,
|
|
&shadow_bottom,
|
|
&shadow_left);
|
|
|
|
windowFrame = [self frame];
|
|
currentLocation = [NSEvent mouseLocation];
|
|
|
|
/* Update the snapping geometry to match the current monitor */
|
|
monitor = _gdk_macos_display_get_monitor_at_display_coords ([self gdkDisplay],
|
|
currentLocation.x,
|
|
currentLocation.y);
|
|
gdk_monitor_get_geometry (monitor, &geometry);
|
|
gdk_macos_monitor_get_workarea (monitor, &workarea);
|
|
_edge_snapping_set_monitor (&self->snapping, &geometry, &workarea);
|
|
|
|
/* Convert origins to GDK coordinates */
|
|
_gdk_macos_display_from_display_coords ([self gdkDisplay],
|
|
currentLocation.x,
|
|
currentLocation.y,
|
|
&pointer_position.x,
|
|
&pointer_position.y);
|
|
_gdk_macos_display_from_display_coords ([self gdkDisplay],
|
|
windowFrame.origin.x,
|
|
windowFrame.origin.y + windowFrame.size.height,
|
|
&window_gdk.x,
|
|
&window_gdk.y);
|
|
window_gdk.width = windowFrame.size.width;
|
|
window_gdk.height = windowFrame.size.height;
|
|
|
|
/* Subtract our shadowin from the window */
|
|
window_gdk.x += shadow_left;
|
|
window_gdk.y += shadow_top;
|
|
window_gdk.width = window_gdk.width - shadow_left - shadow_right;
|
|
window_gdk.height = window_gdk.height - shadow_top - shadow_bottom;
|
|
|
|
/* Now place things on the monitor */
|
|
_edge_snapping_motion (&self->snapping, &pointer_position, &window_gdk);
|
|
|
|
/* And add our shadow back to the frame */
|
|
window_gdk.x -= shadow_left;
|
|
window_gdk.y -= shadow_top;
|
|
window_gdk.width += shadow_left + shadow_right;
|
|
window_gdk.height += shadow_top + shadow_bottom;
|
|
|
|
/* Convert to quartz coordinates */
|
|
_gdk_macos_display_to_display_coords ([self gdkDisplay],
|
|
window_gdk.x,
|
|
window_gdk.y + window_gdk.height,
|
|
&new_origin.x, &new_origin.y);
|
|
windowFrame.origin.x = new_origin.x;
|
|
windowFrame.origin.y = new_origin.y;
|
|
|
|
[self setFrame:NSMakeRect (new_origin.x, new_origin.y,
|
|
window_gdk.width, window_gdk.height)
|
|
display:YES];
|
|
|
|
return YES;
|
|
}
|
|
|
|
-(void)windowDidMove:(NSNotification *)notification
|
|
{
|
|
_gdk_macos_surface_configure ([self gdkSurface]);
|
|
}
|
|
|
|
- (void)windowDidResize:(NSNotification *)notification
|
|
{
|
|
_gdk_macos_surface_configure ([self gdkSurface]);
|
|
}
|
|
|
|
/* Used by gdkmacosdisplay-translate.c to decide if our sendEvent() handler
|
|
* above will see the event or if it will be subjected to standard processing
|
|
* by GDK.
|
|
*/
|
|
-(BOOL)isInManualResizeOrMove
|
|
{
|
|
return inManualResize || inManualMove;
|
|
}
|
|
|
|
-(void)beginManualMove
|
|
{
|
|
gboolean maximized = GDK_SURFACE (gdk_surface)->state & GDK_TOPLEVEL_STATE_MAXIMIZED;
|
|
NSPoint initialMoveLocation;
|
|
GdkPoint point;
|
|
GdkMonitor *monitor;
|
|
GdkRectangle geometry;
|
|
GdkRectangle area;
|
|
GdkRectangle workarea;
|
|
|
|
if (inMove || inManualMove || inManualResize)
|
|
return;
|
|
|
|
inManualMove = YES;
|
|
|
|
monitor = _gdk_macos_surface_get_best_monitor ([self gdkSurface]);
|
|
gdk_monitor_get_geometry (monitor, &geometry);
|
|
gdk_macos_monitor_get_workarea (monitor, &workarea);
|
|
|
|
initialMoveLocation = [NSEvent mouseLocation];
|
|
|
|
if (maximized)
|
|
[self setFrame:NSMakeRect (initialMoveLocation.x - (int)lastUnmaximizedFrame.size.width/2,
|
|
initialMoveLocation.y,
|
|
lastUnmaximizedFrame.size.width,
|
|
lastUnmaximizedFrame.size.height)
|
|
display:YES];
|
|
|
|
_gdk_macos_display_from_display_coords ([self gdkDisplay],
|
|
initialMoveLocation.x,
|
|
initialMoveLocation.y,
|
|
&point.x,
|
|
&point.y);
|
|
|
|
area.x = gdk_surface->root_x;
|
|
area.y = gdk_surface->root_y;
|
|
area.width = GDK_SURFACE (gdk_surface)->width;
|
|
area.height = GDK_SURFACE (gdk_surface)->height;
|
|
|
|
_edge_snapping_init (&self->snapping,
|
|
&geometry,
|
|
&workarea,
|
|
&point,
|
|
&area);
|
|
}
|
|
|
|
-(BOOL)trackManualResize
|
|
{
|
|
NSPoint mouse_location;
|
|
NSRect new_frame;
|
|
float mdx, mdy, dw, dh, dx, dy;
|
|
NSSize min_size;
|
|
|
|
if (!inManualResize || inTrackManualResize)
|
|
return NO;
|
|
|
|
inTrackManualResize = YES;
|
|
|
|
mouse_location = convert_nspoint_to_screen (self, [self mouseLocationOutsideOfEventStream]);
|
|
mdx = initialResizeLocation.x - mouse_location.x;
|
|
mdy = initialResizeLocation.y - mouse_location.y;
|
|
|
|
/* Set how a mouse location delta translates to changes in width,
|
|
* height and position.
|
|
*/
|
|
dw = dh = dx = dy = 0.0;
|
|
if (resizeEdge == GDK_SURFACE_EDGE_EAST ||
|
|
resizeEdge == GDK_SURFACE_EDGE_NORTH_EAST ||
|
|
resizeEdge == GDK_SURFACE_EDGE_SOUTH_EAST)
|
|
{
|
|
dw = -1.0;
|
|
}
|
|
if (resizeEdge == GDK_SURFACE_EDGE_NORTH ||
|
|
resizeEdge == GDK_SURFACE_EDGE_NORTH_WEST ||
|
|
resizeEdge == GDK_SURFACE_EDGE_NORTH_EAST)
|
|
{
|
|
dh = -1.0;
|
|
}
|
|
if (resizeEdge == GDK_SURFACE_EDGE_SOUTH ||
|
|
resizeEdge == GDK_SURFACE_EDGE_SOUTH_WEST ||
|
|
resizeEdge == GDK_SURFACE_EDGE_SOUTH_EAST)
|
|
{
|
|
dh = 1.0;
|
|
dy = -1.0;
|
|
}
|
|
if (resizeEdge == GDK_SURFACE_EDGE_WEST ||
|
|
resizeEdge == GDK_SURFACE_EDGE_NORTH_WEST ||
|
|
resizeEdge == GDK_SURFACE_EDGE_SOUTH_WEST)
|
|
{
|
|
dw = 1.0;
|
|
dx = -1.0;
|
|
}
|
|
|
|
/* Apply changes to the frame captured when we started resizing */
|
|
new_frame = initialResizeFrame;
|
|
new_frame.origin.x += mdx * dx;
|
|
new_frame.origin.y += mdy * dy;
|
|
new_frame.size.width += mdx * dw;
|
|
new_frame.size.height += mdy * dh;
|
|
|
|
/* In case the resulting window would be too small reduce the
|
|
* change to both size and position.
|
|
*/
|
|
min_size = [self contentMinSize];
|
|
|
|
if (new_frame.size.width < min_size.width)
|
|
{
|
|
if (dx)
|
|
new_frame.origin.x -= min_size.width - new_frame.size.width;
|
|
new_frame.size.width = min_size.width;
|
|
}
|
|
|
|
if (new_frame.size.height < min_size.height)
|
|
{
|
|
if (dy)
|
|
new_frame.origin.y -= min_size.height - new_frame.size.height;
|
|
new_frame.size.height = min_size.height;
|
|
}
|
|
|
|
_gdk_macos_surface_user_resize ([self gdkSurface], new_frame);
|
|
|
|
inTrackManualResize = NO;
|
|
|
|
return YES;
|
|
}
|
|
|
|
-(void)beginManualResize:(GdkSurfaceEdge)edge
|
|
{
|
|
CALayerContentsGravity gravity = kCAGravityBottomLeft;
|
|
|
|
if (inMove || inManualMove || inManualResize)
|
|
return;
|
|
|
|
inManualResize = YES;
|
|
resizeEdge = edge;
|
|
|
|
switch (edge)
|
|
{
|
|
default:
|
|
case GDK_SURFACE_EDGE_NORTH:
|
|
gravity = kCAGravityTopLeft;
|
|
break;
|
|
|
|
case GDK_SURFACE_EDGE_NORTH_WEST:
|
|
gravity = kCAGravityTopRight;
|
|
break;
|
|
|
|
case GDK_SURFACE_EDGE_SOUTH_WEST:
|
|
case GDK_SURFACE_EDGE_WEST:
|
|
gravity = kCAGravityBottomRight;
|
|
break;
|
|
|
|
case GDK_SURFACE_EDGE_SOUTH:
|
|
case GDK_SURFACE_EDGE_SOUTH_EAST:
|
|
gravity = kCAGravityBottomLeft;
|
|
break;
|
|
|
|
case GDK_SURFACE_EDGE_EAST:
|
|
gravity = kCAGravityBottomLeft;
|
|
break;
|
|
|
|
case GDK_SURFACE_EDGE_NORTH_EAST:
|
|
gravity = kCAGravityTopLeft;
|
|
break;
|
|
}
|
|
|
|
[[[self contentView] layer] setContentsGravity:gravity];
|
|
|
|
initialResizeFrame = [self frame];
|
|
initialResizeLocation = convert_nspoint_to_screen (self, [self mouseLocationOutsideOfEventStream]);
|
|
}
|
|
|
|
-(NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender
|
|
{
|
|
NSPoint location = [sender draggingLocation];
|
|
NSDragOperation ret;
|
|
GdkMacosDrop *drop;
|
|
|
|
if (!(drop = _gdk_macos_drop_new ([self gdkSurface], sender)))
|
|
return NSDragOperationNone;
|
|
|
|
_gdk_macos_display_set_drop ([self gdkDisplay],
|
|
[sender draggingSequenceNumber],
|
|
GDK_DROP (drop));
|
|
|
|
gdk_drop_emit_enter_event (GDK_DROP (drop),
|
|
TRUE,
|
|
location.x,
|
|
GDK_SURFACE (gdk_surface)->height - location.y,
|
|
GDK_CURRENT_TIME);
|
|
|
|
ret = _gdk_macos_drop_operation (drop);
|
|
|
|
g_object_unref (drop);
|
|
|
|
return ret;
|
|
}
|
|
|
|
-(void)draggingEnded:(id <NSDraggingInfo>)sender
|
|
{
|
|
_gdk_macos_display_set_drop ([self gdkDisplay], [sender draggingSequenceNumber], NULL);
|
|
}
|
|
|
|
-(void)draggingExited:(id <NSDraggingInfo>)sender
|
|
{
|
|
NSInteger sequence_number = [sender draggingSequenceNumber];
|
|
GdkDrop *drop = _gdk_macos_display_find_drop ([self gdkDisplay], sequence_number);
|
|
|
|
if (drop != NULL)
|
|
gdk_drop_emit_leave_event (drop, TRUE, GDK_CURRENT_TIME);
|
|
|
|
_gdk_macos_display_set_drop ([self gdkDisplay], sequence_number, NULL);
|
|
}
|
|
|
|
-(NSDragOperation)draggingUpdated:(id <NSDraggingInfo>)sender
|
|
{
|
|
NSInteger sequence_number = [sender draggingSequenceNumber];
|
|
GdkDisplay *display = gdk_surface_get_display (GDK_SURFACE (gdk_surface));
|
|
GdkDrop *drop = _gdk_macos_display_find_drop (GDK_MACOS_DISPLAY (display), sequence_number);
|
|
NSPoint location = [sender draggingLocation];
|
|
|
|
if (drop == NULL)
|
|
return NSDragOperationNone;
|
|
|
|
_gdk_macos_drop_update_actions (GDK_MACOS_DROP (drop), sender);
|
|
|
|
gdk_drop_emit_motion_event (drop,
|
|
TRUE,
|
|
location.x,
|
|
GDK_SURFACE (gdk_surface)->height - location.y,
|
|
GDK_CURRENT_TIME);
|
|
|
|
return _gdk_macos_drop_operation (GDK_MACOS_DROP (drop));
|
|
}
|
|
|
|
-(BOOL)performDragOperation:(id <NSDraggingInfo>)sender
|
|
{
|
|
NSInteger sequence_number = [sender draggingSequenceNumber];
|
|
GdkDisplay *display = gdk_surface_get_display (GDK_SURFACE (gdk_surface));
|
|
GdkDrop *drop = _gdk_macos_display_find_drop (GDK_MACOS_DISPLAY (display), sequence_number);
|
|
NSPoint location = [sender draggingLocation];
|
|
|
|
if (drop == NULL)
|
|
return NO;
|
|
|
|
gdk_drop_emit_drop_event (drop,
|
|
TRUE,
|
|
location.x,
|
|
GDK_SURFACE (gdk_surface)->height - location.y,
|
|
GDK_CURRENT_TIME);
|
|
|
|
gdk_drop_emit_leave_event (drop, TRUE, GDK_CURRENT_TIME);
|
|
|
|
return GDK_MACOS_DROP (drop)->finish_action != 0;
|
|
}
|
|
|
|
-(BOOL)wantsPeriodicDraggingUpdates
|
|
{
|
|
return NO;
|
|
}
|
|
|
|
-(void)draggedImage:(NSImage *)anImage endedAt:(NSPoint)aPoint operation:(NSDragOperation)operation
|
|
{
|
|
}
|
|
|
|
-(void)setStyleMask:(NSWindowStyleMask)styleMask
|
|
{
|
|
gboolean was_fullscreen;
|
|
gboolean is_fullscreen;
|
|
gboolean was_opaque;
|
|
gboolean is_opaque;
|
|
|
|
was_fullscreen = (([self styleMask] & NSWindowStyleMaskFullScreen) != 0);
|
|
was_opaque = (([self styleMask] & NSWindowStyleMaskTitled) != 0);
|
|
|
|
[super setStyleMask:styleMask];
|
|
|
|
is_fullscreen = (([self styleMask] & NSWindowStyleMaskFullScreen) != 0);
|
|
is_opaque = (([self styleMask] & NSWindowStyleMaskTitled) != 0);
|
|
|
|
if (was_fullscreen != is_fullscreen)
|
|
_gdk_macos_surface_update_fullscreen_state (gdk_surface);
|
|
|
|
if (was_opaque != is_opaque)
|
|
{
|
|
[self setOpaque:is_opaque];
|
|
|
|
if (!is_opaque)
|
|
[self setBackgroundColor:[NSColor clearColor]];
|
|
}
|
|
}
|
|
|
|
-(NSRect)constrainFrameRect:(NSRect)frameRect toScreen:(NSScreen *)screen
|
|
{
|
|
GdkMacosSurface *surface = gdk_surface;
|
|
NSRect rect;
|
|
int shadow_top;
|
|
|
|
/* Allow the window to move up "shadow_top" more than normally allowed
|
|
* by the default impl. This makes it possible to move windows with
|
|
* client side shadow right up to the screen's menu bar. */
|
|
_gdk_macos_surface_get_shadow (surface, &shadow_top, NULL, NULL, NULL);
|
|
rect = [super constrainFrameRect:frameRect toScreen:screen];
|
|
if (frameRect.origin.y > rect.origin.y)
|
|
rect.origin.y = MIN (frameRect.origin.y, rect.origin.y + shadow_top);
|
|
|
|
return rect;
|
|
}
|
|
|
|
-(NSRect)windowWillUseStandardFrame:(NSWindow *)nsWindow
|
|
defaultFrame:(NSRect)newFrame
|
|
{
|
|
NSRect screenFrame = [[self screen] visibleFrame];
|
|
GdkMacosSurface *surface = gdk_surface;
|
|
gboolean maximized = GDK_SURFACE (surface)->state & GDK_TOPLEVEL_STATE_MAXIMIZED;
|
|
|
|
if (!maximized)
|
|
return screenFrame;
|
|
else
|
|
return lastUnmaximizedFrame;
|
|
}
|
|
|
|
-(BOOL)windowShouldZoom:(NSWindow *)nsWindow
|
|
toFrame:(NSRect)newFrame
|
|
{
|
|
GdkMacosSurface *surface = gdk_surface;
|
|
GdkToplevelState state = GDK_SURFACE (surface)->state;
|
|
|
|
if (state & GDK_TOPLEVEL_STATE_MAXIMIZED)
|
|
{
|
|
lastMaximizedFrame = newFrame;
|
|
}
|
|
else
|
|
{
|
|
lastUnmaximizedFrame = [nsWindow frame];
|
|
gdk_synthesize_surface_state (GDK_SURFACE (gdk_surface), 0, GDK_TOPLEVEL_STATE_MAXIMIZED);
|
|
}
|
|
|
|
inMaximizeTransition = YES;
|
|
|
|
return YES;
|
|
}
|
|
|
|
-(void)windowDidEndLiveResize:(NSNotification *)aNotification
|
|
{
|
|
inMaximizeTransition = NO;
|
|
}
|
|
|
|
-(NSSize)window:(NSWindow *)window willUseFullScreenContentSize:(NSSize)proposedSize
|
|
{
|
|
return [[window screen] frame].size;
|
|
}
|
|
|
|
-(void)windowWillEnterFullScreen:(NSNotification *)aNotification
|
|
{
|
|
lastUnfullscreenFrame = [self frame];
|
|
}
|
|
|
|
-(void)windowDidEnterFullScreen:(NSNotification *)aNotification
|
|
{
|
|
initialPositionKnown = NO;
|
|
[self checkSendEnterNotify];
|
|
}
|
|
|
|
-(void)windowWillExitFullScreen:(NSNotification *)aNotification
|
|
{
|
|
[self setFrame:lastUnfullscreenFrame display:NO];
|
|
}
|
|
|
|
-(void)windowDidExitFullScreen:(NSNotification *)aNotification
|
|
{
|
|
initialPositionKnown = NO;
|
|
[self checkSendEnterNotify];
|
|
}
|
|
|
|
-(void)windowDidFailToEnterFullScreen:(NSNotification *)aNotification
|
|
{
|
|
}
|
|
|
|
-(void)windowDidFailToExitFullScreen:(NSNotification *)aNotification
|
|
{
|
|
}
|
|
|
|
-(void)windowDidChangeScreen:(NSNotification *)aNotification
|
|
{
|
|
_gdk_macos_surface_monitor_changed (gdk_surface);
|
|
}
|
|
|
|
-(void)setGdkSurface:(GdkMacosSurface *)surface
|
|
{
|
|
self->gdk_surface = surface;
|
|
}
|
|
|
|
-(void)setDecorated:(BOOL)decorated
|
|
{
|
|
NSWindowStyleMask style_mask = [self styleMask];
|
|
|
|
[self setHasShadow:decorated];
|
|
|
|
if (decorated)
|
|
style_mask |= NSWindowStyleMaskTitled;
|
|
else
|
|
style_mask &= ~NSWindowStyleMaskTitled;
|
|
|
|
[self setStyleMask:style_mask];
|
|
}
|
|
|
|
-(GdkMacosSurface *)gdkSurface
|
|
{
|
|
return self->gdk_surface;
|
|
}
|
|
|
|
-(GdkMacosDisplay *)gdkDisplay
|
|
{
|
|
return GDK_MACOS_DISPLAY (GDK_SURFACE (self->gdk_surface)->display);
|
|
}
|
|
|
|
-(BOOL)movableByWindowBackground
|
|
{
|
|
return NO;
|
|
}
|
|
|
|
-(void)swapBuffer:(GdkMacosBuffer *)buffer withDamage:(const cairo_region_t *)damage
|
|
{
|
|
[(GdkMacosView *)[self contentView] swapBuffer:buffer withDamage:damage];
|
|
}
|
|
|
|
@end
|