blob: d440db7c0d250bf69d79df2b8c5b2ec09e0635b0 [file] [log] [blame]
Kateryna Kostiuk6891d4f2019-09-19 17:44:33 -04001/*
2 * Copyright (C) 2019 Savoir-faire Linux Inc.
3 * Author: Kateryna Kostiuk <kateryna.kostiuk@savoirfairelinux.com>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 */
19
20#import "MovableView.h"
21#import <QuartzCore/QuartzCore.h>
22
23@implementation MovableView
24
25NSPoint firstMouseDownPoint = NSZeroPoint;
26NSInteger const MARGIN = 20;
27BOOL movingFromCorner;
28BOOL movingToCorner;
29
30@synthesize movable;
31
32-(void)mouseDown:(NSEvent *)event
33{
34 if (!movable) {
35 return;
36 }
37 firstMouseDownPoint = [self.hostingView convertPoint:event.locationInWindow toView:self];
38}
39
40-(void)mouseDragged:(NSEvent *)event
41{
42 if (!movable) {
43 return;
44 }
45 NSPoint newPoint = [self.hostingView convertPoint:event.locationInWindow toView:self];
46 NSPoint offset = CGPointMake(newPoint.x - firstMouseDownPoint.x,newPoint.y - firstMouseDownPoint.y);
47 NSPoint origin = self.frame.origin;
48 NSSize size = self.frame.size;
49 NSPoint newOrigin= CGPointMake(origin.x + offset.x, origin.y + offset.y);
50 NSPoint newMax= CGPointMake(newOrigin.x + self.frame.size.width, newOrigin.y + self.frame.size.height);
51 if(!CGRectContainsPoint(CGRectInset([self.hostingView frame], MARGIN, MARGIN), newOrigin)
52 || !CGRectContainsPoint(CGRectInset([self.hostingView frame], MARGIN, MARGIN), newMax)) {
53 if (newOrigin.x < self.minX) {
54 newOrigin.x = self.minX;
55 }
56 if (newOrigin.x > self.maxX) {
57 newOrigin.x = self.maxX;
58 }
59 if (newOrigin.y < self.minY) {
60 newOrigin.y = self.minY;
61 }
62 if (newOrigin.y > self.maxY) {
63 newOrigin.y = self.maxY;
64 }
65 }
66 self.frame = CGRectMake(newOrigin.x, newOrigin.y, size.width, size.height);
67}
68
69- (void)viewDidMoveToWindow {
70 [self.window setAcceptsMouseMovedEvents:YES];
71
72 NSTrackingAreaOptions options = (NSTrackingActiveAlways | NSTrackingInVisibleRect | NSTrackingMouseEnteredAndExited | NSTrackingMouseMoved);
73
74 NSTrackingArea *area = [[NSTrackingArea alloc] initWithRect:self.frame
75 options:options
76 owner:self
77 userInfo:nil];
78
79 [self addTrackingArea:area];
80}
81
82- (void)mouseEntered:(NSEvent *)event {
83 if (!movable || movingFromCorner) {
84 return;
85 }
86 CGPoint currentOrigin = self.frame.origin;
87 CGPoint cornerPoint = [self pointForCorner: self.closestCorner];
88 if (currentOrigin.x != cornerPoint.x || currentOrigin.y != cornerPoint.y) {
89 return;
90 }
91 movingFromCorner = true;
92 auto currentSize = self.frame.size;
93 CGPoint newOrigin = [self pointToMoveFromCorner: self.closestCorner];
94 auto currentFrame = self.frame;
95 auto newFrame = currentFrame;
96 newFrame.origin = newOrigin;
97 [NSAnimationContext runAnimationGroup:^(NSAnimationContext * _Nonnull context) {
98 context.duration = 0.1f;
99 context.timingFunction = [CAMediaTimingFunction functionWithName: kCAMediaTimingFunctionEaseOut];
100 self.animator.frame = newFrame;
101 } completionHandler:^{
102 movingFromCorner = false;
103 }];
104}
105
106- (void)mouseExited:(NSEvent *)event {
107 if(movingToCorner) {
108 return;
109 }
110 CGPoint currentOrigin = self.frame.origin;
111 CGPoint cornerPoint = [self pointToMoveFromCorner: self.closestCorner];
112 if ((currentOrigin.x != cornerPoint.x) || (currentOrigin.y != cornerPoint.y)) {
113 return;
114 }
115 movingToCorner = true;
116 [self moveToCornerWithDuration:0.1];
117}
118
119- (void)mouseUp:(NSEvent *)event {
120 [self moveToCornerWithDuration:0.3];
121}
122
123-(void) moveToCornerWithDuration:(CGFloat) duration {
124 if (!movable) {
125 return;
126 }
127 NSRect frame = self.frame;
128 CGPoint currentOrigin = frame.origin;
129 auto closestCorner = self.closestCorner;
130 frame.origin = [self pointForCorner: self.closestCorner];
131 [NSAnimationContext runAnimationGroup:^(NSAnimationContext * _Nonnull context) {
132 context.duration = duration;
133 context.timingFunction = [CAMediaTimingFunction functionWithName: kCAMediaTimingFunctionEaseOut];
134 self.animator.frame = frame;
135 } completionHandler: ^{
136 movingToCorner = false;
137 }];
138}
139
140- (CGFloat) maxY {
141 return self.hostingView.frame.size.height - self.frame.size.height - MARGIN;
142}
143
144- (CGFloat) maxX {
145 return self.hostingView.frame.size.width - self.frame.size.width - MARGIN;
146}
147
148- (CGFloat) minX {
149 return MARGIN;
150}
151
152- (CGFloat) minY {
153 return MARGIN;
154}
155
156- (ViewCorner) closestCorner {
157 NSPoint origin = self.frame.origin;
158 BOOL isLeft = origin.x < self.maxX * 0.5;
159 BOOL isTop = origin.y > self.maxY * 0.5;
160 if (isLeft) {
161 if (isTop) {
162 return TOP_LEFT;
163 }
164 return BOTTOM_LEFT;
165 }
166 if (isTop) {
167 return TOP_RIGHT;
168 }
169 return BOTTOM_RIGHT;
170}
171
172- (CGPoint) pointForCorner:(NSInteger) corner {
173 switch (corner) {
174 case TOP_LEFT:
175 return CGPointMake(self.minX, self.maxY);
176 case BOTTOM_LEFT:
177 return CGPointMake(self.minX, self.minY);
178 case TOP_RIGHT: {
179 auto max = self.maxY;
180 return CGPointMake(self.maxX, self.maxY);
181 }
182 case BOTTOM_RIGHT:
183 return CGPointMake(self.maxX, self.minY);
184 }
185}
186
187- (CGPoint) pointToMoveFromCorner:(NSInteger) corner {
188 CGFloat margin = 4;
189 CGPoint currentOrigin = [self pointForCorner: corner];
190 switch (corner) {
191 case TOP_LEFT:
192 return CGPointMake(currentOrigin.x + margin, currentOrigin.y - margin);
193 case BOTTOM_LEFT:
194 return CGPointMake(currentOrigin.x + margin, currentOrigin.y + margin);
195 case TOP_RIGHT:
196 return CGPointMake(currentOrigin.x - margin, currentOrigin.y - margin);
197 case BOTTOM_RIGHT:
198 return CGPointMake(currentOrigin.x - margin, currentOrigin.y + margin);
199 }
200}
201
202@end