NSAttributedTextView // Better area calculation, etc.

This commit is contained in:
ShikiSuen 2022-09-13 14:38:29 +08:00
parent 4f3ab28d6a
commit 36b45c0a2d
2 changed files with 81 additions and 41 deletions

View File

@ -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
}
}

View File

@ -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