fix: align text annotations in captures
This commit is contained in:
+193
-17
@@ -115,6 +115,12 @@ static id nativeOverlayKeyMonitor = nil;
|
||||
@property(strong) NSView *actionToolbarBg;
|
||||
@property(strong) NSTextField *textEditor;
|
||||
@property NSPoint textEditorLocalPoint;
|
||||
@property NSInteger textEditorAnnotationIndex;
|
||||
@property(strong) NSColor *textEditorColor;
|
||||
@property NSInteger selectedTextAnnotationIndex;
|
||||
@property BOOL draggingTextAnnotation;
|
||||
@property NSPoint textDragStartLocalPoint;
|
||||
@property NSPoint textDragOriginalLocalPoint;
|
||||
@property(strong) NSTextField *sizeLabel;
|
||||
@property(strong) NSTextField *hintLabel;
|
||||
- (void)syncControls;
|
||||
@@ -127,6 +133,10 @@ static id nativeOverlayKeyMonitor = nil;
|
||||
- (void)summarizeSelection;
|
||||
- (void)cancelSelection;
|
||||
- (void)beginTextAnnotationAtPoint:(NSPoint)localPoint;
|
||||
- (void)beginEditingTextAnnotationAtIndex:(NSInteger)index;
|
||||
- (NSInteger)textAnnotationIndexAtPoint:(NSPoint)p;
|
||||
- (NSRect)textAnnotationRectForItem:(NSDictionary *)item;
|
||||
- (NSPoint)clampedTextLocalPoint:(NSPoint)localPoint text:(NSString *)text fontSize:(CGFloat)fontSize;
|
||||
- (BOOL)isEditingText;
|
||||
- (void)commitTextEditor;
|
||||
- (void)cancelTextEditor;
|
||||
@@ -145,6 +155,8 @@ static id nativeOverlayKeyMonitor = nil;
|
||||
_activeTool = nil;
|
||||
_activeColor = [NSColor colorWithCalibratedRed:239.0/255.0 green:68.0/255.0 blue:68.0/255.0 alpha:1.0];
|
||||
_annotations = [NSMutableArray array];
|
||||
_textEditorAnnotationIndex = -1;
|
||||
_selectedTextAnnotationIndex = -1;
|
||||
|
||||
// Use SnipHoverButton for the 5 action buttons so we get hover-color
|
||||
// animation. Annotation buttons stay as plain NSButton because they
|
||||
@@ -364,12 +376,73 @@ static id nativeOverlayKeyMonitor = nil;
|
||||
}
|
||||
}
|
||||
|
||||
- (CGFloat)fontSizeForTextItem:(NSDictionary *)item {
|
||||
NSNumber *fontSize = item[@"fontSize"];
|
||||
if (fontSize != nil && [fontSize doubleValue] > 0) {
|
||||
return [fontSize doubleValue];
|
||||
}
|
||||
return 20.0;
|
||||
}
|
||||
|
||||
- (NSDictionary *)textAttributesWithFontSize:(CGFloat)fontSize color:(NSColor *)color {
|
||||
return @{
|
||||
NSFontAttributeName: [NSFont systemFontOfSize:fontSize weight:NSFontWeightSemibold],
|
||||
NSForegroundColorAttributeName: color ?: [NSColor whiteColor]
|
||||
};
|
||||
}
|
||||
|
||||
- (NSSize)textSizeForString:(NSString *)text fontSize:(CGFloat)fontSize {
|
||||
NSString *measured = text.length > 0 ? text : @" ";
|
||||
NSDictionary *attrs = [self textAttributesWithFontSize:fontSize color:[NSColor whiteColor]];
|
||||
NSRect bounds = [measured boundingRectWithSize:NSMakeSize(CGFLOAT_MAX, CGFLOAT_MAX)
|
||||
options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading
|
||||
attributes:attrs];
|
||||
return NSMakeSize(ceil(MAX(1.0, bounds.size.width)), ceil(MAX(fontSize * 1.2, bounds.size.height)));
|
||||
}
|
||||
|
||||
- (NSRect)textAnnotationRectForItem:(NSDictionary *)item {
|
||||
if (![item[@"tool"] isEqualToString:@"text"]) {
|
||||
return NSZeroRect;
|
||||
}
|
||||
NSArray *points = item[@"points"];
|
||||
NSString *text = item[@"text"];
|
||||
if (points.count == 0 || text.length == 0) {
|
||||
return NSZeroRect;
|
||||
}
|
||||
NSPoint p = [points[0] pointValue];
|
||||
CGFloat fontSize = [self fontSizeForTextItem:item];
|
||||
NSSize size = [self textSizeForString:text fontSize:fontSize];
|
||||
return NSMakeRect(_selection.origin.x + p.x, _selection.origin.y + p.y, size.width, size.height);
|
||||
}
|
||||
|
||||
- (NSPoint)clampedTextLocalPoint:(NSPoint)localPoint text:(NSString *)text fontSize:(CGFloat)fontSize {
|
||||
NSSize size = [self textSizeForString:text fontSize:fontSize];
|
||||
CGFloat maxX = MAX(0, _selection.size.width - size.width);
|
||||
CGFloat maxY = MAX(0, _selection.size.height - size.height);
|
||||
return NSMakePoint(MAX(0, MIN(localPoint.x, maxX)), MAX(0, MIN(localPoint.y, maxY)));
|
||||
}
|
||||
|
||||
- (NSInteger)textAnnotationIndexAtPoint:(NSPoint)p {
|
||||
for (NSInteger i = (NSInteger)_annotations.count - 1; i >= 0; i--) {
|
||||
NSDictionary *item = _annotations[(NSUInteger)i];
|
||||
if (![item[@"tool"] isEqualToString:@"text"]) {
|
||||
continue;
|
||||
}
|
||||
NSRect hitRect = NSInsetRect([self textAnnotationRectForItem:item], -6, -6);
|
||||
if (!NSIsEmptyRect(hitRect) && NSPointInRect(p, hitRect)) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
- (void)drawAnnotations {
|
||||
NSMutableArray *items = [NSMutableArray arrayWithArray:_annotations];
|
||||
if (_draftAnnotation != nil) {
|
||||
[items addObject:_draftAnnotation];
|
||||
}
|
||||
for (NSDictionary *item in items) {
|
||||
for (NSUInteger index = 0; index < items.count; index++) {
|
||||
NSDictionary *item = items[index];
|
||||
NSString *tool = item[@"tool"];
|
||||
NSColor *color = item[@"nsColor"];
|
||||
NSArray *points = item[@"points"];
|
||||
@@ -382,11 +455,18 @@ static id nativeOverlayKeyMonitor = nil;
|
||||
continue;
|
||||
}
|
||||
NSPoint p = [points[0] pointValue];
|
||||
NSDictionary *attrs = @{
|
||||
NSFontAttributeName: [NSFont systemFontOfSize:20 weight:NSFontWeightSemibold],
|
||||
NSForegroundColorAttributeName: color ?: [NSColor whiteColor]
|
||||
};
|
||||
CGFloat fontSize = [self fontSizeForTextItem:item];
|
||||
NSDictionary *attrs = [self textAttributesWithFontSize:fontSize color:color];
|
||||
[text drawAtPoint:NSMakePoint(_selection.origin.x + p.x, _selection.origin.y + p.y) withAttributes:attrs];
|
||||
if ((NSInteger)index == _selectedTextAnnotationIndex && index < _annotations.count) {
|
||||
NSRect rect = NSInsetRect([self textAnnotationRectForItem:item], -4, -3);
|
||||
NSBezierPath *selectionPath = [NSBezierPath bezierPathWithRoundedRect:rect xRadius:4 yRadius:4];
|
||||
[selectionPath setLineWidth:1.5];
|
||||
CGFloat dash[] = {4.0, 3.0};
|
||||
[selectionPath setLineDash:dash count:2 phase:0];
|
||||
[[NSColor colorWithCalibratedRed:59.0/255.0 green:130.0/255.0 blue:246.0/255.0 alpha:0.95] setStroke];
|
||||
[selectionPath stroke];
|
||||
}
|
||||
continue;
|
||||
}
|
||||
[color setStroke];
|
||||
@@ -501,6 +581,7 @@ static id nativeOverlayKeyMonitor = nil;
|
||||
if (_textEditor != nil) {
|
||||
[self commitTextEditor];
|
||||
}
|
||||
_draggingTextAnnotation = NO;
|
||||
NSString *handle = [self resizeHandleAtPoint:p];
|
||||
if (handle != nil) {
|
||||
_resizing = YES;
|
||||
@@ -512,6 +593,31 @@ static id nativeOverlayKeyMonitor = nil;
|
||||
[self syncControls];
|
||||
return;
|
||||
}
|
||||
NSInteger textIndex = _hasSelection ? [self textAnnotationIndexAtPoint:p] : -1;
|
||||
if (textIndex >= 0) {
|
||||
_selectedTextAnnotationIndex = textIndex;
|
||||
_creating = NO;
|
||||
_moving = NO;
|
||||
_resizing = NO;
|
||||
_annotating = NO;
|
||||
if ([event clickCount] >= 2) {
|
||||
[self beginEditingTextAnnotationAtIndex:textIndex];
|
||||
[self syncControls];
|
||||
[self setNeedsDisplay:YES];
|
||||
return;
|
||||
}
|
||||
NSDictionary *item = _annotations[(NSUInteger)textIndex];
|
||||
NSArray *points = item[@"points"];
|
||||
if (points.count > 0) {
|
||||
_draggingTextAnnotation = YES;
|
||||
_textDragStartLocalPoint = [self localPoint:p];
|
||||
_textDragOriginalLocalPoint = [points[0] pointValue];
|
||||
}
|
||||
[self syncControls];
|
||||
[self setNeedsDisplay:YES];
|
||||
return;
|
||||
}
|
||||
_selectedTextAnnotationIndex = -1;
|
||||
if (_hasSelection && NSPointInRect(p, _selection) && [_activeTool isEqualToString:@"text"]) {
|
||||
_creating = NO;
|
||||
_moving = NO;
|
||||
@@ -553,6 +659,7 @@ static id nativeOverlayKeyMonitor = nil;
|
||||
_selection = NSMakeRect(p.x, p.y, 0, 0);
|
||||
[_annotations removeAllObjects];
|
||||
_draftAnnotation = nil;
|
||||
_selectedTextAnnotationIndex = -1;
|
||||
}
|
||||
[self syncControls];
|
||||
}
|
||||
@@ -583,6 +690,17 @@ static id nativeOverlayKeyMonitor = nil;
|
||||
top = MAX(0, MIN(top, self.bounds.size.height));
|
||||
bottom = MAX(0, MIN(bottom, self.bounds.size.height));
|
||||
_selection = NSMakeRect(MIN(left, right), MIN(top, bottom), fabs(right - left), fabs(bottom - top));
|
||||
} else if (_draggingTextAnnotation && _selectedTextAnnotationIndex >= 0 && _selectedTextAnnotationIndex < (NSInteger)_annotations.count) {
|
||||
NSMutableDictionary *item = (NSMutableDictionary *)_annotations[(NSUInteger)_selectedTextAnnotationIndex];
|
||||
NSMutableArray *points = (NSMutableArray *)item[@"points"];
|
||||
if (points.count > 0) {
|
||||
NSPoint local = [self localPoint:p];
|
||||
NSPoint delta = NSMakePoint(local.x - _textDragStartLocalPoint.x, local.y - _textDragStartLocalPoint.y);
|
||||
NSPoint next = NSMakePoint(_textDragOriginalLocalPoint.x + delta.x, _textDragOriginalLocalPoint.y + delta.y);
|
||||
NSString *text = item[@"text"] ?: @"";
|
||||
CGFloat fontSize = [self fontSizeForTextItem:item];
|
||||
points[0] = [NSValue valueWithPoint:[self clampedTextLocalPoint:next text:text fontSize:fontSize]];
|
||||
}
|
||||
} else if (_annotating && _draftAnnotation != nil) {
|
||||
NSMutableArray *points = _draftAnnotation[@"points"];
|
||||
NSPoint local = [self localPoint:p];
|
||||
@@ -604,6 +722,7 @@ static id nativeOverlayKeyMonitor = nil;
|
||||
_creating = NO;
|
||||
_moving = NO;
|
||||
_resizing = NO;
|
||||
_draggingTextAnnotation = NO;
|
||||
if (_annotating && _draftAnnotation != nil) {
|
||||
[_annotations addObject:_draftAnnotation];
|
||||
_draftAnnotation = nil;
|
||||
@@ -731,32 +850,68 @@ static id nativeOverlayKeyMonitor = nil;
|
||||
[self cancelTextEditor];
|
||||
if (_annotations.count > 0) {
|
||||
[_annotations removeLastObject];
|
||||
if (_selectedTextAnnotationIndex >= (NSInteger)_annotations.count) {
|
||||
_selectedTextAnnotationIndex = -1;
|
||||
}
|
||||
[self syncControls];
|
||||
[self setNeedsDisplay:YES];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)beginTextAnnotationAtPoint:(NSPoint)localPoint {
|
||||
[self beginTextEditorAtLocalPoint:localPoint text:@"" color:_activeColor annotationIndex:-1];
|
||||
}
|
||||
|
||||
- (void)beginEditingTextAnnotationAtIndex:(NSInteger)index {
|
||||
if (index < 0 || index >= (NSInteger)_annotations.count) {
|
||||
return;
|
||||
}
|
||||
NSDictionary *item = _annotations[(NSUInteger)index];
|
||||
if (![item[@"tool"] isEqualToString:@"text"]) {
|
||||
return;
|
||||
}
|
||||
NSArray *points = item[@"points"];
|
||||
if (points.count == 0) {
|
||||
return;
|
||||
}
|
||||
NSColor *color = item[@"nsColor"] ?: _activeColor;
|
||||
[self beginTextEditorAtLocalPoint:[points[0] pointValue]
|
||||
text:item[@"text"] ?: @""
|
||||
color:color
|
||||
annotationIndex:index];
|
||||
}
|
||||
|
||||
- (void)beginTextEditorAtLocalPoint:(NSPoint)localPoint text:(NSString *)text color:(NSColor *)color annotationIndex:(NSInteger)index {
|
||||
[self cancelTextEditor];
|
||||
_textEditorLocalPoint = localPoint;
|
||||
NSRect frame = NSMakeRect(_selection.origin.x + localPoint.x, _selection.origin.y + localPoint.y, 180, 30);
|
||||
CGFloat fontSize = 20.0;
|
||||
NSPoint clampedLocal = [self clampedTextLocalPoint:localPoint text:text fontSize:fontSize];
|
||||
_textEditorLocalPoint = clampedLocal;
|
||||
_textEditorAnnotationIndex = index;
|
||||
_textEditorColor = color ?: _activeColor;
|
||||
|
||||
NSSize textSize = [self textSizeForString:text fontSize:fontSize];
|
||||
CGFloat editorWidth = MAX(180, MIN(360, textSize.width + 24));
|
||||
NSRect frame = NSMakeRect(_selection.origin.x + clampedLocal.x, _selection.origin.y + clampedLocal.y, editorWidth, 30);
|
||||
CGFloat maxX = self.bounds.size.width - frame.size.width - 8;
|
||||
CGFloat maxY = self.bounds.size.height - frame.size.height - 8;
|
||||
frame.origin.x = MAX(8, MIN(frame.origin.x, maxX));
|
||||
frame.origin.y = MAX(8, MIN(frame.origin.y, maxY));
|
||||
_textEditorLocalPoint = [self localPoint:frame.origin];
|
||||
|
||||
_textEditor = [[NSTextField alloc] initWithFrame:frame];
|
||||
[_textEditor setBezeled:YES];
|
||||
[_textEditor setBordered:YES];
|
||||
[_textEditor setDrawsBackground:YES];
|
||||
[_textEditor setBackgroundColor:[NSColor colorWithCalibratedWhite:0.08 alpha:0.78]];
|
||||
[_textEditor setTextColor:_activeColor];
|
||||
[_textEditor setFont:[NSFont systemFontOfSize:20 weight:NSFontWeightSemibold]];
|
||||
[_textEditor setTextColor:_textEditorColor];
|
||||
[_textEditor setFont:[NSFont systemFontOfSize:fontSize weight:NSFontWeightSemibold]];
|
||||
[_textEditor setStringValue:text ?: @""];
|
||||
[_textEditor setFocusRingType:NSFocusRingTypeNone];
|
||||
[_textEditor setTarget:self];
|
||||
[_textEditor setAction:@selector(commitTextEditorFromSender:)];
|
||||
[self addSubview:_textEditor];
|
||||
[[self window] makeFirstResponder:_textEditor];
|
||||
[_textEditor selectText:nil];
|
||||
}
|
||||
|
||||
- (BOOL)isEditingText {
|
||||
@@ -772,17 +927,35 @@ static id nativeOverlayKeyMonitor = nil;
|
||||
return;
|
||||
}
|
||||
NSString *text = [[_textEditor stringValue] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
|
||||
if (text.length > 0) {
|
||||
NSColor *textColor = _textEditorColor ?: _activeColor;
|
||||
if (_textEditorAnnotationIndex >= 0 && _textEditorAnnotationIndex < (NSInteger)_annotations.count) {
|
||||
if (text.length > 0) {
|
||||
NSMutableDictionary *item = (NSMutableDictionary *)_annotations[(NSUInteger)_textEditorAnnotationIndex];
|
||||
item[@"color"] = [self hexForColor:textColor];
|
||||
item[@"nsColor"] = textColor;
|
||||
item[@"points"] = [NSMutableArray arrayWithObject:[NSValue valueWithPoint:_textEditorLocalPoint]];
|
||||
item[@"text"] = text;
|
||||
item[@"fontSize"] = @20.0;
|
||||
_selectedTextAnnotationIndex = _textEditorAnnotationIndex;
|
||||
} else {
|
||||
[_annotations removeObjectAtIndex:(NSUInteger)_textEditorAnnotationIndex];
|
||||
_selectedTextAnnotationIndex = -1;
|
||||
}
|
||||
} else if (text.length > 0) {
|
||||
[_annotations addObject:[@{
|
||||
@"tool": @"text",
|
||||
@"color": [self hexForColor:_activeColor],
|
||||
@"nsColor": _activeColor,
|
||||
@"color": [self hexForColor:textColor],
|
||||
@"nsColor": textColor,
|
||||
@"points": [NSMutableArray arrayWithObject:[NSValue valueWithPoint:_textEditorLocalPoint]],
|
||||
@"text": text
|
||||
@"text": text,
|
||||
@"fontSize": @20.0
|
||||
} mutableCopy]];
|
||||
_selectedTextAnnotationIndex = (NSInteger)_annotations.count - 1;
|
||||
}
|
||||
[_textEditor removeFromSuperview];
|
||||
_textEditor = nil;
|
||||
_textEditorAnnotationIndex = -1;
|
||||
_textEditorColor = nil;
|
||||
[[self window] makeFirstResponder:self];
|
||||
[self syncControls];
|
||||
[self setNeedsDisplay:YES];
|
||||
@@ -794,6 +967,8 @@ static id nativeOverlayKeyMonitor = nil;
|
||||
}
|
||||
[_textEditor removeFromSuperview];
|
||||
_textEditor = nil;
|
||||
_textEditorAnnotationIndex = -1;
|
||||
_textEditorColor = nil;
|
||||
[[self window] makeFirstResponder:self];
|
||||
[self setNeedsDisplay:YES];
|
||||
}
|
||||
@@ -848,6 +1023,7 @@ static id nativeOverlayKeyMonitor = nil;
|
||||
NSString *text = item[@"text"];
|
||||
if (text.length > 0) {
|
||||
entry[@"text"] = text;
|
||||
entry[@"fontSize"] = item[@"fontSize"] ?: @20.0;
|
||||
}
|
||||
[payload addObject:entry];
|
||||
}
|
||||
@@ -872,8 +1048,8 @@ static id nativeOverlayKeyMonitor = nil;
|
||||
return;
|
||||
}
|
||||
NSRect r = [self globalRectForSelection:_selection];
|
||||
[self closeOverlayWindow];
|
||||
NSString *json = [self annotationsJSON];
|
||||
[self closeOverlayWindow];
|
||||
nativeOverlayConfirm((int)llround(r.origin.x), (int)llround(r.origin.y), (int)llround(r.size.width), (int)llround(r.size.height), [json UTF8String]);
|
||||
}
|
||||
|
||||
@@ -882,8 +1058,8 @@ static id nativeOverlayKeyMonitor = nil;
|
||||
return;
|
||||
}
|
||||
NSRect r = [self globalRectForSelection:_selection];
|
||||
[self closeOverlayWindow];
|
||||
NSString *json = [self annotationsJSON];
|
||||
[self closeOverlayWindow];
|
||||
nativeOverlayCopy((int)llround(r.origin.x), (int)llround(r.origin.y), (int)llround(r.size.width), (int)llround(r.size.height), [json UTF8String]);
|
||||
}
|
||||
|
||||
@@ -925,8 +1101,8 @@ static id nativeOverlayKeyMonitor = nil;
|
||||
return;
|
||||
}
|
||||
NSRect r = [self globalRectForSelection:_selection];
|
||||
[self closeOverlayWindow];
|
||||
NSString *json = [self annotationsJSON];
|
||||
[self closeOverlayWindow];
|
||||
nativeOverlaySaveRemote((int)llround(r.origin.x), (int)llround(r.origin.y), (int)llround(r.size.width), (int)llround(r.size.height), [json UTF8String]);
|
||||
}
|
||||
|
||||
@@ -937,8 +1113,8 @@ static id nativeOverlayKeyMonitor = nil;
|
||||
return;
|
||||
}
|
||||
NSRect r = [self globalRectForSelection:_selection];
|
||||
[self closeOverlayWindow];
|
||||
NSString *json = [self annotationsJSON];
|
||||
[self closeOverlayWindow];
|
||||
nativeOverlaySummarize((int)llround(r.origin.x), (int)llround(r.origin.y), (int)llround(r.size.width), (int)llround(r.size.height), [json UTF8String]);
|
||||
}
|
||||
@end
|
||||
|
||||
Reference in New Issue
Block a user