Skip to content

Commit

Permalink
Make format toolbar and sidebar share styles, configurations and views
Browse files Browse the repository at this point in the history
  • Loading branch information
danielsaidi committed Feb 16, 2024
1 parent 91ea919 commit 210ccdf
Show file tree
Hide file tree
Showing 11 changed files with 267 additions and 150 deletions.
3 changes: 2 additions & 1 deletion RELEASE_NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ By deprecating these functions, we can simplify the library in 1.0, and focus mo
* `RichTextCommand.ActionButtonGroup` has new inits.
* `RichTextCommand.FormatMenu` is a lot more configurable.
* `RichTextFormatToolbar` is now available on all platforms.
* `RichTextFormatToolbar` has new configuration and style type.
* `RichTextFormatToolbar` has new configuration and style types.
* `RichTextFormatSidebar` has new configuration and style types.
* `RichTextKeyboardToolbar` has a new config to always be shown.
* `RichTextView` has a new theme that lets you define its style.
* `RichTextViewComponent` has a new `hasRichTextStyle` function.
Expand Down
101 changes: 46 additions & 55 deletions Sources/RichTextKit/Format/RichTextFormatSidebar.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,103 +17,82 @@ import SwiftUI
should also be made to look good on iPadOS in landscape, to
let us use it instead of the ``RichTextFormatSheet``.
*/
public struct RichTextFormatSidebar: View {
public struct RichTextFormatSidebar: RichTextFormatToolbarBase {

/**
Create a rich text format sheet.
- Parameters:
- context: The context to apply changes to.
- colorPickers: The color pickers to use, by default `.foreground` and `.background`.
- config: The configuration to use, by default `.standard`.
*/
public init(
context: RichTextContext,
colorPickers: [RichTextColor] = [.foreground, .background]
config: Configuration = .standard
) {
self._context = ObservedObject(wrappedValue: context)
self.colorPickers = colorPickers
self.config = config
}

public typealias Configuration = RichTextFormatToolbar.Configuration

@ObservedObject
private var context: RichTextContext

let config: Configuration

/// The sidebar spacing.
public var spacing = 10.0

/// The color pickers to use.
public var colorPickers: [RichTextColor]
@Environment(\.richTextFormatToolbarStyle)
var style

public var body: some View {
VStack(alignment: .leading, spacing: spacing) {
VStack(alignment: .leading, spacing: style.spacing) {
SidebarSection {
RichTextFont.Picker(selection: $context.fontName, fontSize: 12)
fontPicker(value: $context.fontName)
HStack {
RichTextStyle.ToggleGroup(context: context)
RichTextFont.SizePickerStack(context: context)
styleToggleGroup(for: context)
Spacer()
fontSizePicker(for: context)
}
}

Divider()

SidebarSection {
RichTextAlignment.Picker(selection: $context.textAlignment)
.pickerStyle(.segmented)
alignmentPicker(value: $context.textAlignment)
HStack {
RichTextAction.ButtonGroup(
context: context,
actions: [.decreaseIndent(), .increaseIndent()]
)
if hasSuperscriptSupport {
RichTextAction.ButtonGroup(
context: context,
actions: [
.increaseSuperscript(),
.decreaseSuperscript()
]
)
}
superscriptButtons(for: context, greedy: true)
indentButtons(for: context, greedy: true)
}
}

Divider()

SidebarSection {
VStack(spacing: 4) {
ForEach(colorPickers) {
RichTextColor.Picker(
type: $0,
value: context.binding(for: $0),
quickColors: .quickPickerColors
)
}
if hasColorPickers {
SidebarSection {
colorPickers(for: context)
}
.padding(.trailing, -8)
Divider()
}
.font(.callout)
.padding(.trailing, -8)

Spacer()
}
.padding(8)
.padding(style.padding - 2)
.background(Color.white.opacity(0.05))
}
}

private extension RichTextFormatSidebar {

var hasSuperscriptSupport: Bool {
#if macOS
return true
#else
return false
#endif
}
}

private struct SidebarSection<Content: View>: View {

@ViewBuilder
let content: () -> Content

@Environment(\.richTextFormatToolbarStyle)
var style

var body: some View {
VStack(alignment: .leading, spacing: 10) {
VStack(alignment: .leading, spacing: style.spacing) {
content()
Divider()
}
}
}
Expand All @@ -126,13 +105,25 @@ struct RichTextFormatSidebar_Previews: PreviewProvider {
private var context = RichTextContext()

var body: some View {
RichTextFormatSidebar(context: context)
RichTextFormatSidebar(
context: context,
config: .init(
alignments: [.left, .right],
colorPickers: [.foreground],
colorPickersDisclosed: [],
fontPicker: true,
fontSizePicker: true,
indentButtons: true,
styles: .all,
superscriptButtons: true
)
)
}
}

static var previews: some View {
Preview()
.frame(width: 350)
.frame(minWidth: 350)
}
}
#endif
121 changes: 28 additions & 93 deletions Sources/RichTextKit/Format/RichTextFormatToolbar.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,19 @@
import SwiftUI

/**
This toolbar provides different text format options, and is
meant to be used on iOS, where space is limited.
This horizontal toolbar provides text format controls.
Consider presenting this view from the bottom in a way that
doesn't cause the underlying text view to dim.
This toolbar adapts the layout based on the horizontal size
class. The control row will be split in two in compact size,
while macOS and regular sizes get a single row.
You can provide a custom configuration to adjust the format
options that are presented. When presented, the font picker
will take up the available vertical height.
options. The font picker will take up all available height.
You can style this view by applying a style anywhere in the
view hierarchy, using `.richTextFormatToolbarStyle`.
*/
public struct RichTextFormatToolbar: View {
public struct RichTextFormatToolbar: RichTextFormatToolbarBase {

/**
Create a rich text format sheet.
Expand All @@ -42,18 +41,17 @@ public struct RichTextFormatToolbar: View {
@ObservedObject
private var context: RichTextContext

let config: Configuration

@Environment(\.richTextFormatToolbarStyle)
private var style
var style

@Environment(\.horizontalSizeClass)
private var horizontalSizeClass

/// The configuration to use.
private let config: Configuration

public var body: some View {
VStack(spacing: 0) {
fontPicker
fontListPicker(value: $context.fontName)
toolbar
}
}
Expand All @@ -70,6 +68,7 @@ public extension RichTextFormatToolbar {
public init(
alignments: [RichTextAlignment] = .all,
colorPickers: [RichTextColor] = [.foreground],
colorPickersDisclosed: [RichTextColor] = [],
fontPicker: Bool = true,
fontSizePicker: Bool = true,
indentButtons: Bool = true,
Expand All @@ -78,6 +77,7 @@ public extension RichTextFormatToolbar {
) {
self.alignments = alignments
self.colorPickers = colorPickers
self.colorPickersDisclosed = colorPickersDisclosed
self.fontPicker = fontPicker
self.fontSizePicker = fontSizePicker
self.indentButtons = indentButtons
Expand All @@ -91,6 +91,7 @@ public extension RichTextFormatToolbar {

public var alignments: [RichTextAlignment]
public var colorPickers: [RichTextColor]
public var colorPickersDisclosed: [RichTextColor]
public var fontPicker: Bool
public var fontSizePicker: Bool
public var indentButtons: Bool
Expand Down Expand Up @@ -189,19 +190,13 @@ public extension RichTextFormatToolbar {

private extension RichTextFormatToolbar {

@ViewBuilder
var fontPicker: some View {
if config.fontPicker {
RichTextFont.ListPicker(selection: $context.fontName)
.frame(height: style.fontPickerHeight)
Divider()
}
}

var toolbar: some View {
VStack(spacing: style.spacing) {
controls
colorPickers
if hasColorPickers {
Divider()
colorPickers(for: context)
}
}
.padding(.vertical, style.padding)
.environment(\.sizeCategory, .medium)
Expand All @@ -219,17 +214,6 @@ private extension RichTextFormatToolbar {

private extension RichTextFormatToolbar {

@ViewBuilder
var alignmentPicker: some View {
if !config.alignments.isEmpty {
RichTextAlignment.Picker(
selection: $context.textAlignment,
values: config.alignments
)
.pickerStyle(.segmented)
}
}

var background: some View {
Color.clear
.overlay(Color.primary.opacity(0.1))
Expand All @@ -255,76 +239,26 @@ private extension RichTextFormatToolbar {
@ViewBuilder
var controlsContent: some View {
HStack {
styleButtons
styleToggleGroup(for: context)
if !useSingleLine {
Spacer()
}
fontSizePicker
if horizontalSizeClass == .regular {
Spacer()
}
}
HStack {
alignmentPicker
superscriptButtons
indentButtons
}
}

@ViewBuilder
var colorPickers: some View {
if !config.colorPickers.isEmpty {
VStack(spacing: style.spacing) {
Divider()
ForEach(config.colorPickers) {
RichTextColor.Picker(
type: $0,
value: context.binding(for: $0),
quickColors: .quickPickerColors
)
}
}
.padding(.leading, style.padding)
alignmentPicker(value: $context.textAlignment)
superscriptButtons(for: context, greedy: false)
indentButtons(for: context, greedy: false)
}
}

@ViewBuilder
var fontSizePicker: some View {
if config.fontSizePicker {
RichTextFont.SizePickerStack(context: context)
.buttonStyle(.bordered)
}
}

@ViewBuilder
var indentButtons: some View {
if config.indentButtons {
RichTextAction.ButtonGroup(
context: context,
actions: [.decreaseIndent(), .increaseIndent()],
greedy: false
)
}
}

@ViewBuilder
var styleButtons: some View {
if !config.styles.isEmpty {
RichTextStyle.ToggleGroup(
context: context,
styles: config.styles
)
if !useSingleLine {
Spacer()
}
}
}

@ViewBuilder
var superscriptButtons: some View {
if config.superscriptButtons {
RichTextAction.ButtonGroup(
context: context,
actions: [.decreaseSuperscript(), .increaseSuperscript()],
greedy: false
)
fontSizePicker(for: context)
}
}
}
Expand All @@ -344,8 +278,9 @@ struct RichTextFormatToolbar_Previews: PreviewProvider {
context: context,
config: .init(
alignments: .all,
// colorPickers: [.foreground],
fontPicker: false,
colorPickers: [.foreground, .background],
colorPickersDisclosed: [.stroke],
fontPicker: true,
fontSizePicker: true,
indentButtons: true,
styles: .all
Expand Down
Loading

0 comments on commit 210ccdf

Please sign in to comment.