NSAttributedTextView // Better area calculation, etc.
This commit is contained in:
parent
4f3ab28d6a
commit
36b45c0a2d
|
@ -60,7 +60,7 @@ public class NSAttributedTextView: NSView {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public var attributedStringValue: NSAttributedString {
|
public func attributedStringValue(areaCalculation: Bool = false) -> NSAttributedString {
|
||||||
var newAttributes = attributes
|
var newAttributes = attributes
|
||||||
let isVertical: Bool = !(direction == .horizontal)
|
let isVertical: Bool = !(direction == .horizontal)
|
||||||
newAttributes[.verticalGlyphForm] = isVertical
|
newAttributes[.verticalGlyphForm] = isVertical
|
||||||
|
@ -71,23 +71,14 @@ public class NSAttributedTextView: NSView {
|
||||||
newStyle.minimumLineHeight = fontSize * 1.1
|
newStyle.minimumLineHeight = fontSize * 1.1
|
||||||
}
|
}
|
||||||
newAttributes[.paragraphStyle] = newStyle
|
newAttributes[.paragraphStyle] = newStyle
|
||||||
var text: String = text ?? ""
|
var text: String = text ?? text ?? ""
|
||||||
if !(direction == .horizontal) {
|
if areaCalculation {
|
||||||
text = text.replacingOccurrences(of: "˙", with: "・")
|
text = text.replacingOccurrences(
|
||||||
text = text.replacingOccurrences(of: "\u{A0}", with: " ")
|
of: "[^\n]",
|
||||||
text = text.replacingOccurrences(of: "+", with: "")
|
with: "國",
|
||||||
text = text.replacingOccurrences(of: "Shift", with: "⇧")
|
options: .regularExpression,
|
||||||
text = text.replacingOccurrences(of: "Control", with: "⌃")
|
range: text.range(of: text)
|
||||||
text = text.replacingOccurrences(of: "Enter", with: "⏎")
|
)
|
||||||
text = text.replacingOccurrences(of: "Command", with: "⌘")
|
|
||||||
text = text.replacingOccurrences(of: "Delete", with: "⌦")
|
|
||||||
text = text.replacingOccurrences(of: "BackSpace", with: "⌫")
|
|
||||||
text = text.replacingOccurrences(of: "SHIFT", with: "⇧")
|
|
||||||
text = text.replacingOccurrences(of: "CONTROL", with: "⌃")
|
|
||||||
text = text.replacingOccurrences(of: "ENTER", with: "⏎")
|
|
||||||
text = text.replacingOccurrences(of: "COMMAND", with: "⌘")
|
|
||||||
text = text.replacingOccurrences(of: "DELETE", with: "⌦")
|
|
||||||
text = text.replacingOccurrences(of: "BACKSPACE", with: "⌫")
|
|
||||||
}
|
}
|
||||||
let attributedText = NSMutableAttributedString(string: text, attributes: newAttributes)
|
let attributedText = NSMutableAttributedString(string: text, attributes: newAttributes)
|
||||||
return attributedText
|
return attributedText
|
||||||
|
@ -109,10 +100,33 @@ public class NSAttributedTextView: NSView {
|
||||||
private var ctFrame: CTFrame?
|
private var ctFrame: CTFrame?
|
||||||
private(set) var currentRect: NSRect?
|
private(set) var currentRect: NSRect?
|
||||||
|
|
||||||
|
@discardableResult public func shrinkFrame() -> NSRect {
|
||||||
|
let attrString: NSAttributedString = {
|
||||||
|
switch direction {
|
||||||
|
case .horizontal: return attributedStringValue()
|
||||||
|
default: return attributedStringValue(areaCalculation: true)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
var rect = attrString.boundingRect(
|
||||||
|
with: NSSize(width: 1600.0, height: 1600.0),
|
||||||
|
options: [.usesLineFragmentOrigin, .usesFontLeading, .usesDeviceMetrics]
|
||||||
|
)
|
||||||
|
rect.size.height *= 1.03
|
||||||
|
rect.size.height = max(rect.size.height, NSFont.systemFontSize * 1.1)
|
||||||
|
rect.size.height = ceil(rect.size.height)
|
||||||
|
rect.size.width *= 1.03
|
||||||
|
rect.size.width = max(rect.size.width, NSFont.systemFontSize * 1.05)
|
||||||
|
rect.size.width = ceil(rect.size.width)
|
||||||
|
if direction != .horizontal {
|
||||||
|
rect = .init(x: rect.minX, y: rect.minY, width: rect.height, height: rect.width)
|
||||||
|
}
|
||||||
|
return rect
|
||||||
|
}
|
||||||
|
|
||||||
override public func draw(_ rect: CGRect) {
|
override public func draw(_ rect: CGRect) {
|
||||||
let context = NSGraphicsContext.current?.cgContext
|
let context = NSGraphicsContext.current?.cgContext
|
||||||
guard let context = context else { return }
|
guard let context = context else { return }
|
||||||
let setter = CTFramesetterCreateWithAttributedString(attributedStringValue)
|
let setter = CTFramesetterCreateWithAttributedString(attributedStringValue())
|
||||||
let path = CGPath(rect: rect, transform: nil)
|
let path = CGPath(rect: rect, transform: nil)
|
||||||
let theCTFrameProgression: CTFrameProgression = {
|
let theCTFrameProgression: CTFrameProgression = {
|
||||||
switch direction {
|
switch direction {
|
||||||
|
@ -134,3 +148,46 @@ public class NSAttributedTextView: NSView {
|
||||||
CTFrameDraw(newFrame, context)
|
CTFrameDraw(newFrame, context)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class NSAttributedTooltipTextView: NSAttributedTextView {
|
||||||
|
override public func attributedStringValue(areaCalculation: Bool = false) -> NSAttributedString {
|
||||||
|
var newAttributes = attributes
|
||||||
|
let isVertical: Bool = !(direction == .horizontal)
|
||||||
|
newAttributes[.verticalGlyphForm] = isVertical
|
||||||
|
let newStyle: NSMutableParagraphStyle = newAttributes[.paragraphStyle] as! NSMutableParagraphStyle
|
||||||
|
if #available(macOS 10.13, *) {
|
||||||
|
newStyle.lineSpacing = isVertical ? (fontSize / -2) : fontSize * 0.1
|
||||||
|
newStyle.maximumLineHeight = fontSize * 1.1
|
||||||
|
newStyle.minimumLineHeight = fontSize * 1.1
|
||||||
|
}
|
||||||
|
newAttributes[.paragraphStyle] = newStyle
|
||||||
|
var text: String = text ?? text ?? ""
|
||||||
|
if !(direction == .horizontal) {
|
||||||
|
text = text.replacingOccurrences(of: "˙", with: "・")
|
||||||
|
text = text.replacingOccurrences(of: "\u{A0}", with: " ")
|
||||||
|
text = text.replacingOccurrences(of: "+", with: "")
|
||||||
|
text = text.replacingOccurrences(of: "Shift", with: "⇧")
|
||||||
|
text = text.replacingOccurrences(of: "Control", with: "⌃")
|
||||||
|
text = text.replacingOccurrences(of: "Enter", with: "⏎")
|
||||||
|
text = text.replacingOccurrences(of: "Command", with: "⌘")
|
||||||
|
text = text.replacingOccurrences(of: "Delete", with: "⌦")
|
||||||
|
text = text.replacingOccurrences(of: "BackSpace", with: "⌫")
|
||||||
|
text = text.replacingOccurrences(of: "SHIFT", with: "⇧")
|
||||||
|
text = text.replacingOccurrences(of: "CONTROL", with: "⌃")
|
||||||
|
text = text.replacingOccurrences(of: "ENTER", with: "⏎")
|
||||||
|
text = text.replacingOccurrences(of: "COMMAND", with: "⌘")
|
||||||
|
text = text.replacingOccurrences(of: "DELETE", with: "⌦")
|
||||||
|
text = text.replacingOccurrences(of: "BACKSPACE", with: "⌫")
|
||||||
|
}
|
||||||
|
if areaCalculation {
|
||||||
|
text = text.replacingOccurrences(
|
||||||
|
of: "[^\n]",
|
||||||
|
with: "國",
|
||||||
|
options: .regularExpression,
|
||||||
|
range: text.range(of: text)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
let attributedText = NSMutableAttributedString(string: text, attributes: newAttributes)
|
||||||
|
return attributedText
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -18,7 +18,7 @@ public class ctlTooltip: NSWindowController {
|
||||||
case prompt
|
case prompt
|
||||||
}
|
}
|
||||||
|
|
||||||
private var messageText: NSAttributedTextView
|
private var messageText: NSAttributedTooltipTextView
|
||||||
private var tooltip: String = "" {
|
private var tooltip: String = "" {
|
||||||
didSet {
|
didSet {
|
||||||
messageText.text = tooltip.isEmpty ? nil : tooltip
|
messageText.text = tooltip.isEmpty ? nil : tooltip
|
||||||
|
@ -26,7 +26,7 @@ public class ctlTooltip: NSWindowController {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public var direction: NSAttributedTextView.writingDirection = .horizontal {
|
public var direction: NSAttributedTooltipTextView.writingDirection = .horizontal {
|
||||||
didSet {
|
didSet {
|
||||||
if #unavailable(macOS 10.13) { direction = .horizontal }
|
if #unavailable(macOS 10.13) { direction = .horizontal }
|
||||||
if Bundle.main.preferredLocalizations[0] == "en" { direction = .horizontal }
|
if Bundle.main.preferredLocalizations[0] == "en" { direction = .horizontal }
|
||||||
|
@ -43,7 +43,7 @@ public class ctlTooltip: NSWindowController {
|
||||||
panel.level = NSWindow.Level(Int(kCGPopUpMenuWindowLevel) + 2)
|
panel.level = NSWindow.Level(Int(kCGPopUpMenuWindowLevel) + 2)
|
||||||
panel.hasShadow = true
|
panel.hasShadow = true
|
||||||
panel.backgroundColor = NSColor.controlBackgroundColor
|
panel.backgroundColor = NSColor.controlBackgroundColor
|
||||||
messageText = NSAttributedTextView()
|
messageText = NSAttributedTooltipTextView()
|
||||||
messageText.backgroundColor = NSColor.controlBackgroundColor
|
messageText.backgroundColor = NSColor.controlBackgroundColor
|
||||||
messageText.textColor = NSColor.textColor
|
messageText.textColor = NSColor.textColor
|
||||||
panel.contentView?.addSubview(messageText)
|
panel.contentView?.addSubview(messageText)
|
||||||
|
@ -58,7 +58,7 @@ public class ctlTooltip: NSWindowController {
|
||||||
public func show(
|
public func show(
|
||||||
tooltip: String = "", at point: NSPoint,
|
tooltip: String = "", at point: NSPoint,
|
||||||
bottomOutOfScreenAdjustmentHeight heightDelta: CGFloat,
|
bottomOutOfScreenAdjustmentHeight heightDelta: CGFloat,
|
||||||
direction: NSAttributedTextView.writingDirection = .horizontal
|
direction: NSAttributedTooltipTextView.writingDirection = .horizontal
|
||||||
) {
|
) {
|
||||||
self.direction = direction
|
self.direction = direction
|
||||||
self.tooltip = tooltip
|
self.tooltip = tooltip
|
||||||
|
@ -147,24 +147,7 @@ public class ctlTooltip: NSWindowController {
|
||||||
}
|
}
|
||||||
|
|
||||||
private func adjustSize() {
|
private func adjustSize() {
|
||||||
let attrString = messageText.attributedStringValue
|
var rect = messageText.shrinkFrame()
|
||||||
var rect = attrString.boundingRect(
|
|
||||||
with: NSSize(width: 1600.0, height: 1600.0),
|
|
||||||
options: [.usesLineFragmentOrigin, .usesFontLeading, .usesDeviceMetrics]
|
|
||||||
)
|
|
||||||
if direction != .horizontal {
|
|
||||||
rect = .init(x: rect.minX, y: rect.minY, width: rect.height, height: rect.width)
|
|
||||||
rect.size.height += NSFont.systemFontSize
|
|
||||||
rect.size.width *= 1.03
|
|
||||||
rect.size.width = max(rect.size.width, NSFont.systemFontSize * 1.05)
|
|
||||||
rect.size.width = ceil(rect.size.width)
|
|
||||||
} else {
|
|
||||||
rect = .init(x: rect.minX, y: rect.minY, width: rect.width, height: rect.height)
|
|
||||||
rect.size.width += NSFont.systemFontSize
|
|
||||||
rect.size.height *= 1.03
|
|
||||||
rect.size.height = max(rect.size.height, NSFont.systemFontSize * 1.05)
|
|
||||||
rect.size.height = ceil(rect.size.height)
|
|
||||||
}
|
|
||||||
var bigRect = rect
|
var bigRect = rect
|
||||||
bigRect.size.width += NSFont.systemFontSize
|
bigRect.size.width += NSFont.systemFontSize
|
||||||
bigRect.size.height += NSFont.systemFontSize
|
bigRect.size.height += NSFont.systemFontSize
|
||||||
|
|
Loading…
Reference in New Issue