forked from ImageOptim/ImageOptim
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathRevealButtonCell.m
More file actions
195 lines (159 loc) · 8.09 KB
/
Copy pathRevealButtonCell.m
File metadata and controls
195 lines (159 loc) · 8.09 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
#import "RevealButtonCell.h"
// These defines should be on, and are simply for demo purposes
#define HIT_TEST 1
#define EDIT_FRAME 1
#define TRACKING 1
#define TRACKING_AREA 1
@implementation RevealButtonCell
- (instancetype)init {
if ((self = [super init])) {
[self setLineBreakMode:NSLineBreakByTruncatingTail];
}
return self;
}
- (SEL)infoButtonAction {
return iInfoButtonAction;
}
- (void)setInfoButtonAction:(SEL)action {
iInfoButtonAction = action;
}
- (NSImage *)infoButtonImage {
static NSImage *image;
if (!image) image = [NSImage imageNamed:@"NSRevealFreestandingTemplate"];
return image;
}
#define PADDING_BEFORE_IMAGE 2.0f
#define PADDING_BETWEEN_TITLE_AND_IMAGE 4.0f
#define VERTICAL_PADDING_FOR_IMAGE 0.0f
#define INFO_IMAGE_SIZE 14.0f
#define PADDING_AROUND_INFO_IMAGE 2.0f
#define IMAGE_SIZE 0.0f
- (NSRect)rectForInfoButtonBasedOnTitleRect:(NSRect)titleRect inBounds:(NSRect)bounds {
NSRect buttonRect = titleRect;
//display at column right
buttonRect.origin.x = NSMaxX(bounds) - INFO_IMAGE_SIZE-PADDING_BETWEEN_TITLE_AND_IMAGE;
//display directly after text
//buttonRect.origin.x = NSMaxX(titleRect) + PADDING_BETWEEN_TITLE_AND_IMAGE;
buttonRect.origin.y += 2.0f;
buttonRect.size.height = INFO_IMAGE_SIZE;
buttonRect.size.width = INFO_IMAGE_SIZE;
// Make sure it doesn't go past the bounds -- if so, we don't want to draw it.
if (NSMaxX(buttonRect) - NSMaxX(bounds) > 0) {
buttonRect = NSZeroRect;
}
//buttonRect.origin.x = round(buttonRect.origin.x);
return buttonRect;
}
- (NSRect)infoButtonRectForBounds:(NSRect)bounds {
NSRect titleRect = [self titleRectForBounds:bounds];
return [self rectForInfoButtonBasedOnTitleRect:titleRect inBounds:bounds];
}
- (NSRect)titleRectForBounds:(NSRect)bounds {
NSAttributedString *title = [self attributedStringValue];
NSRect result = bounds;
// The x origin is easy
result.origin.x += PADDING_BEFORE_IMAGE + IMAGE_SIZE + PADDING_BETWEEN_TITLE_AND_IMAGE;
// The y origin should be inline with the image
result.origin.y += VERTICAL_PADDING_FOR_IMAGE;
// Set the width and the height based on the texts real size. Notice the nil check! Otherwise, the resulting NSSize could be undefined if we messaged a nil object.
if (title != nil) {
result.size = [title size];
} else {
result.size = NSZeroSize;
}
// Now, we have to constrain us to the bounds. The max x we can go to has to be the same as the bounds, but minus the info image location
CGFloat maxX = NSMaxX(bounds) - (PADDING_AROUND_INFO_IMAGE + INFO_IMAGE_SIZE + PADDING_AROUND_INFO_IMAGE);
CGFloat maxWidth = maxX - NSMinX(result);
if (maxWidth < 0) maxWidth = 0;
// Constrain us to these bounds
result.size.width = MIN(NSWidth(result), maxWidth);
return result;
}
- (NSSize)cellSizeForBounds:(NSRect)bounds {
NSSize result;
// Figure out the natural cell size and confine it to the bounds given
NSRect titleRect = [self titleRectForBounds:bounds];
result.width = PADDING_BEFORE_IMAGE + IMAGE_SIZE + PADDING_BETWEEN_TITLE_AND_IMAGE + titleRect.size.width;
// Add in spacing for the info image
result.width += PADDING_AROUND_INFO_IMAGE + INFO_IMAGE_SIZE + PADDING_AROUND_INFO_IMAGE;
result.height = VERTICAL_PADDING_FOR_IMAGE + IMAGE_SIZE + VERTICAL_PADDING_FOR_IMAGE;
// Constrain it to the bounds passed in
result.width = MIN(result.width, NSWidth(bounds));
result.height = MIN(result.height, NSHeight(bounds));
return result;
}
- (void)drawInteriorWithFrame:(NSRect)bounds inView:(NSView *)controlView {
NSRect titleRect = [self titleRectForBounds:bounds];
NSAttributedString *title = [self attributedStringValue];
if ([title length] > 0) {
[title drawInRect:titleRect];
}
NSRect infoButtonRect = [self infoButtonRectForBounds:bounds];
NSImage *image = [self infoButtonImage];
float opacity = iMouseHoveredInInfoButton ? 1.0f : ([self isHighlighted] ? 0.5f : 0.3f);
[image drawInRect:infoButtonRect fromRect:NSZeroRect operation:NSCompositeSourceOver fraction:opacity respectFlipped:YES hints:nil];
}
- (NSCellHitResult)hitTestForEvent:(NSEvent *)event inRect:(NSRect)cellFrame ofView:(NSView *)controlView {
NSPoint point = [controlView convertPoint:[event locationInWindow] fromView:nil];
NSRect titleRect = [self titleRectForBounds:cellFrame];
if (NSMouseInRect(point, titleRect, [controlView isFlipped])) {
return NSCellHitContentArea | NSCellHitEditableTextArea;
}
// How about the info button?
NSRect infoButtonRect = [self infoButtonRectForBounds:cellFrame];
if (NSMouseInRect(point, infoButtonRect, [controlView isFlipped])) {
return NSCellHitContentArea | NSCellHitTrackableArea;
}
return NSCellHitNone;
}
+ (BOOL)prefersTrackingUntilMouseUp {
// NSCell returns NO for this by default. If you want to have trackMouse:inRect:ofView:untilMouseUp: always track until the mouse is up, then you MUST return YES. Otherwise, strange things will happen.
return YES;
}
// Mouse tracking -- the only part we want to track is the "info" button
- (BOOL)trackMouse:(NSEvent *)theEvent inRect:(NSRect)cellFrame ofView:(NSView *)controlView untilMouseUp:(BOOL)flag {
NSRect infoButtonRect = [self infoButtonRectForBounds:cellFrame];
while ([theEvent type] != NSLeftMouseUp) {
// This is VERY simple event tracking. We simply check to see if the mouse is in the "i" button or not and dispatch entered/exited mouse events
NSPoint point = [controlView convertPoint:[theEvent locationInWindow] fromView:nil];
BOOL mouseInButton = NSMouseInRect(point, infoButtonRect, [controlView isFlipped]);
if (iMouseDownInInfoButton != mouseInButton) {
iMouseDownInInfoButton = mouseInButton;
[controlView setNeedsDisplayInRect:cellFrame];
}
if ([theEvent type] == NSMouseEntered || [theEvent type] == NSMouseExited) {
[NSApp sendEvent:theEvent];
}
// Note that we process mouse entered and exited events and dispatch them to properly handle updates
theEvent = [[controlView window] nextEventMatchingMask:(NSLeftMouseUpMask | NSLeftMouseDraggedMask | NSMouseEnteredMask | NSMouseExitedMask)];
}
// Another way of implementing the above code would be to keep an NSButtonCell as an ivar, and simply call trackMouse:inRect:ofView:untilMouseUp: on it, if the tracking area was inside of it.
if (iMouseDownInInfoButton) {
// Send the action, and redisplay
iMouseDownInInfoButton = NO;
[controlView setNeedsDisplayInRect:cellFrame];
if (iInfoButtonAction) {
[NSApp sendAction:iInfoButtonAction to:[self target] from:controlView];
}
}
// We return YES since the mouse was released while we were tracking. Not returning YES when you processed the mouse up is an easy way to introduce bugs!
return YES;
}
// Mouse movement tracking -- we have a custom NSOutlineView subclass that automatically lets us add mouseEntered:/mouseExited: support to any cell!
- (void)addTrackingAreasForView:(NSView *)controlView inRect:(NSRect)cellFrame withUserInfo:(NSDictionary *)userInfo mouseLocation:(NSPoint)mouseLocation {
NSRect infoButtonRect = [self infoButtonRectForBounds:cellFrame];
NSTrackingAreaOptions options = NSTrackingEnabledDuringMouseDrag | NSTrackingMouseEnteredAndExited | NSTrackingActiveAlways;
BOOL mouseIsInside = NSMouseInRect(mouseLocation, infoButtonRect, [controlView isFlipped]);
if (mouseIsInside) options |= NSTrackingAssumeInside;
if (iMouseHoveredInInfoButton != mouseIsInside) {
iMouseHoveredInInfoButton = mouseIsInside;
[controlView setNeedsDisplayInRect:cellFrame];
}
// We make the view the owner, and it delegates the calls back to the cell after it is properly setup for the corresponding row/column in the outlineview
NSTrackingArea *area = [[NSTrackingArea alloc] initWithRect:infoButtonRect options:options owner:controlView userInfo:userInfo];
[controlView addTrackingArea:area];
}
- (void)setMouseEntered:(BOOL)y {
iMouseHoveredInInfoButton = y;
}
@end