Skip to content

Commit

Permalink
Add support for line spacing
Browse files Browse the repository at this point in the history
  • Loading branch information
danielsaidi committed Feb 16, 2024
1 parent 21ba8c3 commit 5a868ee
Show file tree
Hide file tree
Showing 36 changed files with 537 additions and 146 deletions.
2 changes: 1 addition & 1 deletion Demo/macOS/EditorScreen.swift
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ private extension EditorScreen {
var toolbar: some View {
RichTextFormatSidebar(
context: context,
colorPickers: .allCases
config: .init(colorPickers: [.foreground])
)
.frame(width: 250)
}
Expand Down
1 change: 1 addition & 0 deletions RELEASE_NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ By deprecating these functions, we can simplify the library in 1.0, and focus mo
* `RichTextFormatSidebar` has new configuration and style types.
* `RichTextFormatToolbar` is a new rich text formatting toolbar.
* `RichTextKeyboardToolbar` has a new config to always be shown.
* `RichTextLine` is a new namespace with support for linespacing.
* `RichTextStyle.Button` now supports using custom button styles.
* `RichTextView` has a new theme that lets you define its style.
* `RichTextViewComponent` has a new `hasRichTextStyle` function.
Expand Down
37 changes: 21 additions & 16 deletions Sources/RichTextKit/Actions/RichTextAction.swift
Original file line number Diff line number Diff line change
Expand Up @@ -59,12 +59,15 @@ public enum RichTextAction: Identifiable, Equatable, RichTextLabelValue {

/// Set a certain ``RichTextStyle``.
case setStyle(RichTextStyle, Bool)

/// Step the font size.
case stepFontSize(points: Int)

/// Step the indent level.
case stepIndent(points: CGFloat)

/// Step the line spacing.
case stepLineSpacing(points: CGFloat)

/// Step the superscript level.
case stepSuperscript(steps: Int)
Expand Down Expand Up @@ -102,6 +105,7 @@ public extension RichTextAction {
case .setStyle(let style, _): style.icon
case .stepFontSize(let val): .richTextStepFontSize(val)
case .stepIndent(let val): .richTextStepIndent(val)
case .stepLineSpacing: .richTextLineSpacing
case .stepSuperscript(let val): .richTextStepSuperscript(val)
case .toggleStyle(let val): val.icon
case .undoLatestChange: .richTextActionUndo
Expand Down Expand Up @@ -136,23 +140,24 @@ public extension RichTextAction {
switch self {
case .copy: .actionCopy
case .dismissKeyboard: .actionDismissKeyboard
case .pasteImage: .pasteImage
case .pasteImages: .pasteImages
case .pasteText: .pasteText
case .print: .actionPrint
case .redoLatestChange: .actionRedoLatestChange
case .selectRange: .selectRange
case .setAlignment(let alignment): alignment.titleKey
case .setAttributedString: .setAttributedString
case .setColor(let color, _): color.titleKey
case .setHighlightedRange: .highlightedRange
case .setHighlightingStyle: .highlightingStyle
case .setStyle(let style, _): style.titleKey
case .stepFontSize(let points): .actionStepFontSize(points)
case .stepIndent(let points): .actionStepIndent(points)
case .stepLineSpacing(let points): .actionStepLineSpacing(points)
case .stepSuperscript(let steps): .actionStepSuperscript(steps)
case .toggleStyle(let style): style.titleKey
case .undoLatestChange: .actionUndoLatestChange
case .setColor(let color, _): color.titleKey
case .setHighlightedRange: .highlightedRange
case .setHighlightingStyle: .highlightingStyle
case .pasteImage: .pasteImage
case .pasteImages: .pasteImages
case .pasteText: .pasteText
case .selectRange: .selectRange
case .setAttributedString: .setAttributedString
case .setStyle(let style, _): style.titleKey
}
}
}
Expand All @@ -161,42 +166,42 @@ public extension RichTextAction {

public extension RichTextAction {

/// A name alias for `.stepFontSize`.
@available(*, deprecated, message: "Use stepFontSize directly")
static func increaseFontSize(
points: UInt = 1
) -> RichTextAction {
stepFontSize(points: Int(points))
}

/// A name alias for `.stepFontSize(points: -1)`.
@available(*, deprecated, message: "Use stepFontSize directly")
static func decreaseFontSize(
points: UInt = 1
) -> RichTextAction {
stepFontSize(points: -Int(points))
}

/// A name alias for `.stepIndent`.
@available(*, deprecated, message: "Use stepIndent directly")
static func increaseIndent(
points: UInt = .defaultRichTextIntentStepSize
) -> RichTextAction {
stepIndent(points: CGFloat(points))
}

/// A name alias for `.stepIndent(points: -1)`.
@available(*, deprecated, message: "Use stepIndent directly")
static func decreaseIndent(
points: UInt = .defaultRichTextIntentStepSize
) -> RichTextAction {
stepIndent(points: -CGFloat(points))
}

/// A name alias for `.stepSuperscript`.
@available(*, deprecated, message: "Use stepSuperscript directly")
static func increaseSuperscript(
steps: UInt = 1
) -> RichTextAction {
stepSuperscript(steps: Int(steps))
}

/// A name alias for `.stepSuperscript(steps: -1)`.
@available(*, deprecated, message: "Use stepSuperscript directly")
static func decreaseSuperscript(
steps: UInt = 1
) -> RichTextAction {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ public extension RichTextCommand {
}

private let actions: [RichTextAction]

private let step = 1

public var body: some View {
ForEach(actions) {
Expand Down Expand Up @@ -59,8 +61,8 @@ public extension RichTextCommand.ActionButtonGroup {
additionalActions: [RichTextAction] = []
) {
self.actions = [
.increaseFontSize(),
.decreaseFontSize()
.stepFontSize(points: 1),
.stepFontSize(points: -1)
] + additionalActions
}

Expand All @@ -70,8 +72,19 @@ public extension RichTextCommand.ActionButtonGroup {
additionalActions: [RichTextAction] = []
) {
self.actions = [
.increaseIndent(),
.decreaseIndent()
.stepIndent(points: 1),
.stepIndent(points: -1)
] + additionalActions
}

/// Create a button group with line spacing steppers.
init(
lineSpacing: Bool,
additionalActions: [RichTextAction] = []
) {
self.actions = [
.stepLineSpacing(points: 1),
.stepLineSpacing(points: -1)
] + additionalActions
}

Expand All @@ -91,8 +104,8 @@ public extension RichTextCommand.ActionButtonGroup {
additionalActions: [RichTextAction] = []
) {
self.actions = [
.increaseSuperscript(),
.decreaseSuperscript()
.stepSuperscript(steps: -1),
.stepSuperscript(steps: 1)
] + additionalActions
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ public extension RichTextCommand.FormatMenu {

/// This enum defines various format sub-menus
enum SubMenu: String, CaseIterable, Identifiable, View {
case font, text, indent, superscript
case font, text, indent, lineSpacing, superscript

public var id: String { rawValue }

Expand All @@ -82,6 +82,10 @@ public extension RichTextCommand.FormatMenu {
Menu(RTKL10n.menuIndent.text) {
Group(indent: true)
}
case .lineSpacing:
Menu(RTKL10n.menuLineSpacing.text) {
Group(lineSpacing: true)
}
case .superscript:
Menu(RTKL10n.menuSuperscript.text) {
Group(superscript: true)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,33 +16,23 @@ import AppKit

public extension RichTextViewComponent {

/// Get the text alignment at the current range.
/// Get the text alignment.
var richTextAlignment: RichTextAlignment? {
guard let style = richTextParagraphStyle else { return nil }
return RichTextAlignment(style.alignment)
}

/// Set the text alignment at the current range.
/// Set the text alignment.
///
/// > Important: This function will affect the next line
/// if it changes the `richTextParagraphStyle` value, so
/// it instead creates a brand new paragraph style.
func setRichTextAlignment(
_ alignment: RichTextAlignment
) {
func setRichTextAlignment(_ alignment: RichTextAlignment) {
if richTextAlignment == alignment { return }
guard let storage = textStorageWrapper else { return }
let range = lineRange(for: selectedRange)
guard range.length > 0 else { return }
let style = NSMutableParagraphStyle(alignment)
let style = NSMutableParagraphStyle(alignment: alignment)
storage.addAttribute(.paragraphStyle, value: style, range: range)
}
}

private extension NSMutableParagraphStyle {

convenience init(_ alignment: RichTextAlignment) {
self.init()
self.alignment = alignment.nativeAlignment
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import Foundation

public extension RichTextViewComponent {

/// Get all attributes at the current range.
/// Get all attributes.
var richTextAttributes: RichTextAttributes {
if hasSelectedRange {
return richTextAttributes(at: selectedRange)
Expand All @@ -25,14 +25,14 @@ public extension RichTextViewComponent {
#endif
}

/// Get a certain attribute at the current range.
/// Get a certain attribute.
func richTextAttribute<Value>(
_ attribute: RichTextAttribute
) -> Value? {
richTextAttributes[attribute] as? Value
}

/// Set a certain attribute at the current range.
/// Set a certain attribute.
func setRichTextAttribute(
_ attribute: RichTextAttribute,
to value: Any
Expand All @@ -44,7 +44,7 @@ public extension RichTextViewComponent {
}
}

/// Set certain attributes at the current range.
/// Set certain attributes.
func setRichTextAttributes(
_ attributes: RichTextAttributes
) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import Foundation

public extension RichTextViewComponent {

/// Get a certain color at the current range.
/// Get a certain color.
func richTextColor(
_ color: RichTextColor
) -> ColorRepresentable? {
Expand All @@ -27,7 +27,7 @@ public extension RichTextViewComponent {
return richTextAttribute(attribute, at: range)
}

/// Set a certain color at the current range.
/// Set a certain color.
func setRichTextColor(
_ color: RichTextColor,
to val: ColorRepresentable
Expand Down
43 changes: 43 additions & 0 deletions Sources/RichTextKit/Component/RichTextViewComponent+Line.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
//
// RichTextViewComponent+Line.swift
// RichTextKit
//
// Created by Daniel Saidi on 2024-02-16.
// Copyright © 2024 Daniel Saidi. All rights reserved.
//

import Foundation

#if canImport(UIKit)
import UIKit
#elseif canImport(AppKit) && !targetEnvironment(macCatalyst)
import AppKit
#endif

public extension RichTextViewComponent {

/// Get the line spacing.
var richTextLineSpacing: CGFloat? {
richTextParagraphStyle?.lineSpacing
}

/// Set the line spacing.
func setRichTextLineSpacing(_ spacing: CGFloat) {
if richTextLineSpacing == spacing { return }
guard let storage = textStorageWrapper else { return }
let range = lineRange(for: selectedRange)
let style = NSMutableParagraphStyle(
from: richTextParagraphStyle,
lineSpacing: spacing
)
style.lineSpacing = spacing
storage.addAttribute(.paragraphStyle, value: style, range: range)
}

/// Step the line spacing.
func stepRichTextLineSpacing(points: CGFloat) {
let currentSize = richTextLineSpacing ?? 0
let newSize = currentSize + points
setRichTextLineSpacing(newSize)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import AppKit

public extension RichTextViewComponent {

/// Get the paragraph style at the current range.
/// Get the paragraph style.
var richTextParagraphStyle: NSMutableParagraphStyle? {
richTextAttribute(.paragraphStyle)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import Foundation

public extension RichTextViewComponent {

/// Get all styles at the current range.
/// Get all styles.
var richTextStyles: [RichTextStyle] {
let attributes = richTextAttributes
let traits = richTextFont?.fontDescriptor.symbolicTraits
Expand All @@ -25,7 +25,7 @@ public extension RichTextViewComponent {
richTextStyles.contains(style)
}

/// Set a certain style at the current range.
/// Set a certain style.
func setRichTextStyle(
_ style: RichTextStyle,
to newValue: Bool
Expand All @@ -45,7 +45,7 @@ public extension RichTextViewComponent {
}
}

/// Toggle a certain style at the current range.
/// Toggle a certain style.
func toggleRichTextStyle(
_ style: RichTextStyle
) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import Foundation

public extension RichTextViewComponent {

/// Get the superscript level at the current range.
/// Get the superscript level.
var richTextSuperscriptLevel: Int? {
#if macOS
richTextAttribute(.superscript)
Expand All @@ -19,7 +19,7 @@ public extension RichTextViewComponent {
#endif
}

/// Set the superscript level at the current range.
/// Set the superscript level.
func setRichTextSuperscriptLevel(to val: Int) {
#if macOS
setRichTextAttribute(.superscript, to: val)
Expand All @@ -28,7 +28,7 @@ public extension RichTextViewComponent {
#endif
}

/// Step the superscript level at the current range.
/// Step the superscript level.
func stepRichTextSuperscriptLevel(points: Int) {
let currentSize = richTextSuperscriptLevel ?? 0
let newSize = currentSize + points
Expand Down
Loading

0 comments on commit 5a868ee

Please sign in to comment.