diff --git a/ViMac-Swift/HintView.swift b/ViMac-Swift/HintView.swift index 24b1264..bc305d3 100644 --- a/ViMac-Swift/HintView.swift +++ b/ViMac-Swift/HintView.swift @@ -9,6 +9,8 @@ import Cocoa import AXSwift +let scale: CGFloat = 1.0 // adjust to debug at a larger size + class HintView: NSView { static let borderColor = NSColor.darkGray static let backgroundColor = NSColor(red: 255 / 255, green: 224 / 255, blue: 112 / 255, alpha: 1) @@ -18,40 +20,74 @@ class HintView: NSView { let associatedElement: Element var hintTextView: HintText? - let borderWidth: CGFloat = 1.0 - let cornerRadius: CGFloat = 3.0 + let borderWidth: CGFloat = 1.0 * scale + let cornerRadius: CGFloat = 2.0 * scale required init(associatedElement: Element, hintTextSize: CGFloat, hintText: String, typedHintText: String) { self.associatedElement = associatedElement super.init(frame: .zero) - self.hintTextView = HintText(hintTextSize: hintTextSize, hintText: hintText, typedHintText: typedHintText) + self.hintTextView = HintText(hintTextSize: hintTextSize * scale, hintText: hintText, typedHintText: typedHintText) self.subviews.append(hintTextView!) self.wantsLayer = true - - self.layer?.borderWidth = borderWidth - - self.layer?.backgroundColor = HintView.backgroundColor.cgColor - self.layer?.borderColor = HintView.borderColor.cgColor - self.layer?.cornerRadius = cornerRadius self.translatesAutoresizingMaskIntoConstraints = false - self.hintTextView!.centerYAnchor.constraint(equalTo: self.centerYAnchor).isActive = true + self.hintTextView!.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: -2 * borderWidth).isActive = true + self.hintTextView!.centerXAnchor.constraint(equalTo: self.centerXAnchor).isActive = true self.widthAnchor.constraint(equalToConstant: width()).isActive = true self.heightAnchor.constraint(equalToConstant: height()).isActive = true } - + + override func draw(_ dirtyRect: NSRect) { + let textWidth: CGFloat = self.hintTextView!.intrinsicContentSize.width + let textHeight: CGFloat = self.hintTextView!.intrinsicContentSize.height + let pointerLength: CGFloat = textHeight / 2 // as a fraction of the text height + let pointerWidth: CGFloat = textHeight / 2 // as a fraction of the text _height_ + + let border = NSBezierPath.init() + + border.lineWidth = borderWidth + border.lineJoinStyle = .miter + + HintView.backgroundColor.setFill() + HintView.borderColor.setStroke() + + border.move(to:NSPoint(x:borderWidth, y:cornerRadius + borderWidth)) + + border.relativeLine(to:NSPoint(x:0,y:textHeight + borderWidth - 2 * cornerRadius)) + border.appendArc(withCenter: NSPoint(x:border.currentPoint.x + cornerRadius, y:border.currentPoint.y), radius: cornerRadius, startAngle: 180.0, endAngle: 90.0, clockwise: true) + + border.relativeLine(to:NSPoint(x:textWidth / 2.0 - cornerRadius - pointerWidth / 2.0, y: 0)) + + border.relativeLine(to:NSPoint(x:pointerWidth/2.0, y: pointerLength)) + border.relativeLine(to:NSPoint(x:pointerWidth/2.0, y: -pointerLength)) + + border.relativeLine(to:NSPoint(x:textWidth / 2.0 - cornerRadius - pointerWidth / 2.0, y: 0)) + + + border.appendArc(withCenter: NSPoint(x:border.currentPoint.x, y:border.currentPoint.y - cornerRadius), radius: cornerRadius, startAngle: 90.0, endAngle: 0.0, clockwise: true) + + border.relativeLine(to:NSPoint(x:0,y:-(textHeight + borderWidth - 2 * cornerRadius))) + border.appendArc(withCenter: NSPoint(x:border.currentPoint.x - cornerRadius, y:border.currentPoint.y), radius: cornerRadius, startAngle: 0, endAngle: 270, clockwise: true) + + border.relativeLine(to:NSPoint(x:-(textWidth - 2 * cornerRadius),y: 0)) + border.appendArc(withCenter: NSPoint(x:border.currentPoint.x, y:border.currentPoint.y + cornerRadius), radius: cornerRadius, startAngle: 270.0, endAngle: 180.0, clockwise: true) + + border.fill() + border.stroke() + } + private func width() -> CGFloat { return self.hintTextView!.intrinsicContentSize.width + 2 * borderWidth } private func height() -> CGFloat { - self.hintTextView!.intrinsicContentSize.height + 2 * borderWidth + self.hintTextView!.intrinsicContentSize.height * (1.5) + 2 * borderWidth } required init?(coder: NSCoder) { @@ -109,7 +145,8 @@ class HintText: NSTextField { self.isEditable = false } - + + func updateTypedText(typed: String) { let hintText = self.attributedStringValue.string let attr = NSMutableAttributedString(string: hintText) diff --git a/ViMac-Swift/Modes/HintModeController.swift b/ViMac-Swift/Modes/HintModeController.swift index da21de7..5b5361b 100644 --- a/ViMac-Swift/Modes/HintModeController.swift +++ b/ViMac-Swift/Modes/HintModeController.swift @@ -97,6 +97,18 @@ class ContentViewController: NSViewController { struct Hint { let element: Element let text: String + func clickPosition() -> NSPoint { + // hints are shown at the bottom-left for AXLinks (see HintsViewController#renderHint), + // so a click is performed there + if element.role == "AXLink" { + return NSPoint( + // inset from corner by 10 pixels, but no more than halfway + x: element.frame.origin.x + min(element.frame.width / 2, 10), + y: element.frame.origin.y + element.frame.height - min(element.frame.height / 2, 10) + ) + } + return GeometryUtils.center(element.frame) + } } enum HintAction: String { @@ -316,19 +328,7 @@ class HintModeController: ModeController { } private func performHintAction(_ hint: Hint, action: HintAction) { - let element = hint.element - let clickPosition: NSPoint = { - // hints are shown at the bottom-left for AXLinks (see HintsViewController#renderHint), - // so a click is performed there - if element.role == "AXLink" { - return NSPoint( - // tiny offset in case clicking on the edge of the element does nothing - x: element.frame.origin.x + 5, - y: element.frame.origin.y + element.frame.height - 5 - ) - } - return GeometryUtils.center(element.frame) - }() + let clickPosition = hint.clickPosition() Utils.moveMouse(position: clickPosition) diff --git a/ViMac-Swift/Modes/HintsViewController.swift b/ViMac-Swift/Modes/HintsViewController.swift index c739a2a..997e502 100644 --- a/ViMac-Swift/Modes/HintsViewController.swift +++ b/ViMac-Swift/Modes/HintsViewController.swift @@ -70,22 +70,15 @@ class HintsViewController: NSViewController { // are you changing the location where hints are rendered? // make sure to update HintModeController#performHintAction as well func renderHint(_ hint: Hint) -> HintView? { + let clickPosition = GeometryUtils.convertAXFrameToGlobal(NSRect(origin: hint.clickPosition(), size: CGSize(width:0,height:0))).origin + let view = HintView(associatedElement: hint.element, hintTextSize: CGFloat(textSize), hintText: hint.text, typedHintText: "") - guard let elementFrame = self.elementFrame(hint.element) else { return nil } - - let hintOrigin: NSPoint = { - // position hint on bottom-left of AXLinks (see #373) - if hint.element.role == "AXLink" { - return elementFrame.origin - } - - // position hint on center of element - let elementCenter = GeometryUtils.center(elementFrame) - return NSPoint( - x: elementCenter.x - (view.intrinsicContentSize.width / 2), - y: elementCenter.y - (view.intrinsicContentSize.height / 2) - ) - }() + + // position hint just below element + let hintOrigin = NSPoint( + x: clickPosition.x - (view.intrinsicContentSize.width / 2), + y: clickPosition.y - (view.intrinsicContentSize.height) + ) if hintOrigin.x.isNaN || hintOrigin.y.isNaN { return nil