Skip to content

Commit

Permalink
Support font color customization (#55)
Browse files Browse the repository at this point in the history
  • Loading branch information
cp-divyesh-v authored Nov 21, 2024
1 parent c98b319 commit 8573289
Show file tree
Hide file tree
Showing 25 changed files with 1,934 additions and 196 deletions.
45 changes: 45 additions & 0 deletions Sources/RichEditorSwiftUI/Alignment/RichTextAlignment+Picker.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
//
// RichTextAlignment+Picker.swift
// RichEditorSwiftUI
//
// Created by Divyesh Vekariya on 18/11/24.
//

import SwiftUI

public extension RichTextAlignment {

/// This picker can be used to pick a text alignment.
///
/// This view returns a plain SwiftUI `Picker` view that
/// can be styled and configured with a `PickerStyle`.
struct Picker: View {

/// Create a rich text alignment picker.
///
/// - Parameters:
/// - selection: The binding to update with the picker.
/// - values: The pickable alignments, by default `.allCases`.
public init(
selection: Binding<RichTextAlignment>,
values: [RichTextAlignment] = RichTextAlignment.allCases
) {
self._selection = selection
self.values = values
}

let values: [RichTextAlignment]

@Binding
private var selection: RichTextAlignment

public var body: some View {
SwiftUI.Picker(RTEL10n.textAlignment.text, selection: $selection) {
ForEach(values) { value in
value.label
.labelStyle(.iconOnly)
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import SwiftUI
This enum defines supported rich text alignments, like left,
right, center, and justified.
*/
public enum RichTextAlignment: String, CaseIterable, Codable, Equatable, Identifiable {
public enum RichTextAlignment: String, CaseIterable, Codable, Equatable, Identifiable, RichTextLabelValue {

/**
Initialize a rich text alignment with a native alignment.
Expand Down
200 changes: 200 additions & 0 deletions Sources/RichEditorSwiftUI/Colors/RichTextColor+Picker.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
//
// RichTextColor+Picker.swift
// RichEditorSwiftUI
//
// Created by Divyesh Vekariya on 18/11/24.
//

import SwiftUI

public extension RichTextColor {

/**
This picker can be used to select a rich text color.
This picker renders an icon next to the color picker as
well as an optional list of quick colors.
The quick color list is empty by default. You can add a
custom set of colors, for instance `.quickPickerColors`.
*/
struct Picker: View {

/**
Create a rich text color picker that binds to a color.
- Parameters:
- type: The type of color to pick.
- icon: The icon to show, if any, by default the `type` icon.
- value: The value to bind to.
- quickColors: Colors to show in the trailing list, by default no colors.
*/
public init(
type: RichTextColor,
icon: Image? = nil,
value: Binding<Color>,
quickColors: [Color] = []
) {
self.type = type
self.icon = icon ?? type.icon
self._value = value
self.quickColors = quickColors
}

private let type: RichTextColor
private let icon: Image?
private let quickColors: [Color]

@Binding
private var value: Color

private let spacing = 10.0

@Environment(\.colorScheme)
private var colorScheme

public var body: some View {
HStack(spacing: 0) {
iconView
picker
if hasColors {
HStack(spacing: spacing) {
quickPickerDivider
quickPickerButton(for: nil)
quickPickerDivider
}
quickPicker
}
}
.labelsHidden()
}
}
}

private extension RichTextColor.Picker {

var hasColors: Bool {
!quickColors.isEmpty
}
}

public extension Color {

/// Get a curated list of quick color picker colors.
static var quickPickerColors: [Self] {
[
.black, .gray, .white,
.red, .pink, .orange, .yellow,
.indigo, .purple, .blue, .cyan, .teal, .mint,
.green, .brown
]
}
}

public extension Collection where Element == Color {

/// Get a curated list of quick color picker colors.
static var quickPickerColors: [Element] {
Element.quickPickerColors
}
}

private extension RichTextColor.Picker {

@ViewBuilder
var iconView: some View {
if let icon {
icon.frame(minWidth: 30)
}
}

@ViewBuilder
var picker: some View {
#if iOS || macOS || os(visionOS)
ColorPicker("", selection: $value)
.fixedSize()
.padding(.horizontal, spacing)
#endif
}

var quickPicker: some View {
ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: spacing) {
ForEach(Array(quickColors.enumerated()), id: \.offset) {
quickPickerButton(for: $0.element)
}
}
.padding(.horizontal, spacing)
.padding(.vertical, 2)
}.frame(maxWidth: .infinity)
}

func quickPickerButton(for color: Color?) -> some View {
Button {
value = type.adjust(color, for: colorScheme)
} label: {
if let color {
color
} else {
Image.richTextColorReset
}
}
.buttonStyle(ColorButtonStyle())
}

var quickPickerDivider: some View {
Divider()
.padding(0)
.frame(maxHeight: 30)
}
}

private struct ColorButtonStyle: ButtonStyle {

func makeBody(configuration: Configuration) -> some View {
configuration.label
.frame(width: 20, height: 20)
.clipShape(Circle())
.shadow(radius: 1, x: 0, y: 1)
}
}

#Preview {

struct Preview: View {
@State
private var foregroundColor = Color.black

@State
private var backgroundColor = Color.white

var body: some View {
VStack(alignment: .leading, spacing: 10) {
Text("Preview")
.foregroundStyle(foregroundColor)
.padding()
.background(backgroundColor)
.frame(maxWidth: .infinity)
.border(Color.black)
.background(Color.red)
.padding()

RichTextColor.Picker(
type: .foreground,
value: $foregroundColor,
quickColors: [.white, .black, .red, .green, .blue]
)
.padding(.leading)

RichTextColor.Picker(
type: .background,
value: $backgroundColor,
quickColors: [.white, .black, .red, .green, .blue]
)
.padding(.leading)
}
}
}

return Preview()
}
14 changes: 13 additions & 1 deletion Sources/RichEditorSwiftUI/Data/Models/RichAttributes.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
// Created by Divyesh Vekariya on 04/04/24.
//

import Foundation
import SwiftUI

// MARK: - RichAttributes
public struct RichAttributes: Codable {
Expand Down Expand Up @@ -224,6 +224,18 @@ extension RichAttributes {
if let list = list {
styles.insert(list.getTextSpanStyle())
}
if let size = size {
styles.insert(.size(size))
}
if let font = font {
styles.insert(.font(font))
}
if let color = color {
styles.insert(.color(Color(hex: color)))
}
if let background = background {
styles.insert(.background(Color(hex: background)))
}
return styles
}
}
Expand Down
74 changes: 74 additions & 0 deletions Sources/RichEditorSwiftUI/Fonts/RichTextFont+ListPicker.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
//
// RichTextFont+ListPicker.swift
// RichEditorSwiftUI
//
// Created by Divyesh Vekariya on 18/11/24.
//

import SwiftUI

public extension RichTextFont {

/**
This view uses a `List` to list a set of fonts of which
one can be selected.
Unlike ``RichTextFont/Picker`` this picker presents all
pickers with proper previews on all platforms. You must
therefore add it ina way that gives it space.
You can configure this picker by applying a config view
modifier to your view hierarchy:
```swift
VStack {
RichTextFont.ListPicker(...)
...
}
.richTextFontPickerConfig(...)
```
*/
struct ListPicker: View {

/**
Create a font list picker.
- Parameters:
- selection: The selected font name.
*/
public init(
selection: Binding<FontName>
) {
self._selection = selection
}

public typealias Config = RichTextFont.PickerConfig
public typealias Font = Config.Font
public typealias FontName = Config.FontName

@Binding
private var selection: FontName

@Environment(\.richTextFontPickerConfig)
private var config

public var body: some View {
let font = Binding(
get: { Font(fontName: selection) },
set: { selection = $0.fontName }
)

RichEditorSwiftUI.ListPicker(
items: config.fontsToList(for: selection),
selection: font,
dismissAfterPick: config.dismissAfterPick
) { font, isSelected in
RichTextFont.PickerItem(
font: font,
fontSize: config.fontSize,
isSelected: isSelected
)
}
}
}
}
Loading

0 comments on commit 8573289

Please sign in to comment.