Skip to content
63 changes: 50 additions & 13 deletions ViMac-Swift/HintView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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) {
Expand Down Expand Up @@ -109,7 +145,8 @@ class HintText: NSTextField {

self.isEditable = false
}



func updateTypedText(typed: String) {
let hintText = self.attributedStringValue.string
let attr = NSMutableAttributedString(string: hintText)
Expand Down
26 changes: 13 additions & 13 deletions ViMac-Swift/Modes/HintModeController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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)

Expand Down
23 changes: 8 additions & 15 deletions ViMac-Swift/Modes/HintsViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down