Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add controlSize/controlProminence modifiers #431

Merged
merged 10 commits into from
Jul 19, 2021
85 changes: 16 additions & 69 deletions Sources/TokamakCore/Styles/ButtonStyle.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,88 +14,35 @@
//
// Created by Gene Z. Ragan on 07/22/2020.

public struct ButtonStyleConfiguration {
public struct Label: _PrimitiveView {
let content: AnyView
}

public let label: Label
public let isPressed: Bool
}

public struct DefaultButtonStyle: ButtonStyle {
public init() {}
public func makeBody(configuration: ButtonStyleConfiguration) -> some View {
configuration.label
}
}

/// This is a helper type that works around absence of "package private" access control in Swift
public struct _ButtonStyleConfigurationProxy {
public struct Label {
public typealias Subject = ButtonStyleConfiguration.Label
public let subject: Subject

public init(_ subject: Subject) { self.subject = subject }

public var content: AnyView { subject.content }
}

public typealias Subject = ButtonStyleConfiguration
public let subject: Subject

public init(label: AnyView, isPressed: Bool) {
subject = .init(label: .init(content: label), isPressed: isPressed)
}

public var label: ButtonStyleConfiguration.Label { subject.label }
}

public protocol ButtonStyle {
associatedtype Body: View

@ViewBuilder
func makeBody(configuration: Self.Configuration) -> Self.Body

typealias Configuration = ButtonStyleConfiguration
}

public struct _AnyButtonStyle: ButtonStyle {
public typealias Body = AnyView

private let bodyClosure: (ButtonStyleConfiguration) -> AnyView
public let type: Any.Type

public init<S: ButtonStyle>(_ style: S) {
type = S.self
bodyClosure = { configuration in
AnyView(style.makeBody(configuration: configuration))
}
public struct ButtonStyleConfiguration {
public struct Label: View {
public let body: AnyView
}

public func makeBody(configuration: ButtonStyleConfiguration) -> AnyView {
bodyClosure(configuration)
}
public let role: ButtonRole?
public let label: ButtonStyleConfiguration.Label
public let isPressed: Bool
}

public enum _ButtonStyleKey: EnvironmentKey {
public static var defaultValue: _AnyButtonStyle {
_AnyButtonStyle(DefaultButtonStyle())
}
}
struct AnyButtonStyle: ButtonStyle {
let bodyClosure: (ButtonStyleConfiguration) -> AnyView
let type: Any.Type

extension EnvironmentValues {
var buttonStyle: _AnyButtonStyle {
get {
self[_ButtonStyleKey.self]
}
set {
self[_ButtonStyleKey.self] = newValue
init<S: ButtonStyle>(_ style: S) {
type = S.self
bodyClosure = {
AnyView(style.makeBody(configuration: $0))
}
}
}

public extension View {
func buttonStyle<S>(_ style: S) -> some View where S: ButtonStyle {
environment(\.buttonStyle, _AnyButtonStyle(style))
func makeBody(configuration: Configuration) -> some View {
bodyClosure(configuration)
}
}
104 changes: 104 additions & 0 deletions Sources/TokamakCore/Styles/ControlGroupStyle.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
// Copyright 2018-2020 Tokamak contributors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Created by Carson Katri on 7/12/21.
//

import Foundation

public protocol ControlGroupStyle {
associatedtype Body: View
@ViewBuilder
func makeBody(configuration: Self.Configuration) -> Self.Body
typealias Configuration = ControlGroupStyleConfiguration
}

public struct ControlGroupStyleConfiguration {
public struct Content: View {
public let body: AnyView
}

public let content: ControlGroupStyleConfiguration.Content
}

public struct AutomaticControlGroupStyle: ControlGroupStyle {
public init() {}

public func makeBody(configuration: Self.Configuration) -> some View {
Picker(
selection: .constant(AnyHashable?.none),
label: EmptyView()
) {
if let parentView = configuration.content.body.view as? ParentView {
ForEach(Array(parentView.children.enumerated()), id: \.offset) {
$0.element
}
} else {
configuration.content
}
}
.pickerStyle(SegmentedPickerStyle())
}
}

public struct NavigationControlGroupStyle: ControlGroupStyle {
public init() {}

public func makeBody(configuration: Self.Configuration) -> some View {
HStack {
configuration.content
}
}
}

public struct _AnyControlGroupStyle: ControlGroupStyle {
public typealias Body = AnyView

private let bodyClosure: (ControlGroupStyleConfiguration) -> AnyView
public let type: Any.Type

public init<S: ControlGroupStyle>(_ style: S) {
type = S.self
bodyClosure = { configuration in
AnyView(style.makeBody(configuration: configuration))
}
}

public func makeBody(configuration: ControlGroupStyleConfiguration) -> AnyView {
bodyClosure(configuration)
}
}

extension EnvironmentValues {
private enum ControlGroupStyleKey: EnvironmentKey {
static let defaultValue = _AnyControlGroupStyle(AutomaticControlGroupStyle())
}

var controlGroupStyle: _AnyControlGroupStyle {
get {
self[ControlGroupStyleKey.self]
}
set {
self[ControlGroupStyleKey.self] = newValue
}
}
}

public extension View {
func controlGroupStyle<S>(
_ style: S
) -> some View where S: ControlGroupStyle {
environment(\.controlGroupStyle, .init(style))
}
}
133 changes: 133 additions & 0 deletions Sources/TokamakCore/Styles/PrimitiveButtonStyle.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
// Copyright 2020 Tokamak contributors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Created by Carson Katri on 7/12/21.
//

public protocol PrimitiveButtonStyle {
associatedtype Body: View
@ViewBuilder
func makeBody(configuration: Self.Configuration) -> Self.Body
typealias Configuration = PrimitiveButtonStyleConfiguration
}

public struct PrimitiveButtonStyleConfiguration {
public struct Label: View {
public let body: AnyView
}

public let role: ButtonRole?
public let label: PrimitiveButtonStyleConfiguration.Label

let action: () -> ()
public func trigger() { action() }
}

public struct DefaultButtonStyle: PrimitiveButtonStyle {
public init() {}

public func makeBody(configuration: Configuration) -> some View {
BorderedButtonStyle().makeBody(configuration: configuration)
}
}

public struct PlainButtonStyle: ButtonStyle {
public init() {}

public func makeBody(configuration: Configuration) -> some View {
configuration.label
.foregroundColor(configuration.isPressed ? .secondary : .primary)
}
}

public struct BorderedButtonStyle: PrimitiveButtonStyle {
public init() {}

public func makeBody(configuration: Configuration) -> some View {
_PrimitiveButtonStyleBody(style: self, configuration: configuration) {
configuration.label
}
}
}

public struct BorderlessButtonStyle: ButtonStyle {
public init() {}

public func makeBody(configuration: Configuration) -> some View {
configuration.label
.foregroundColor(configuration.isPressed ? .primary : .secondary)
}
}

public struct LinkButtonStyle: ButtonStyle {
public init() {}

public func makeBody(configuration: Configuration) -> some View {
configuration.label.body
.foregroundColor(
configuration
.isPressed ? Color(red: 128 / 255, green: 192 / 255, blue: 240 / 255) : .blue
)
}
}

struct AnyPrimitiveButtonStyle: PrimitiveButtonStyle {
let bodyClosure: (PrimitiveButtonStyleConfiguration) -> AnyView
let type: Any.Type

init<S: PrimitiveButtonStyle>(_ style: S) {
type = S.self
bodyClosure = {
AnyView(style.makeBody(configuration: $0))
}
}

func makeBody(configuration: Self.Configuration) -> AnyView {
bodyClosure(configuration)
}
}

extension EnvironmentValues {
enum ButtonStyleKey: EnvironmentKey {
enum ButtonStyleKeyValue {
case primitiveButtonStyle(AnyPrimitiveButtonStyle)
case buttonStyle(AnyButtonStyle)
}

public static let defaultValue: ButtonStyleKeyValue = .primitiveButtonStyle(
.init(DefaultButtonStyle())
)
}

var buttonStyle: ButtonStyleKey.ButtonStyleKeyValue {
get {
self[ButtonStyleKey.self]
}
set {
self[ButtonStyleKey.self] = newValue
}
}
}

public extension View {
func buttonStyle<S>(
_ style: S
) -> some View where S: PrimitiveButtonStyle {
environment(\.buttonStyle, .primitiveButtonStyle(.init(style)))
}

func buttonStyle<S>(_ style: S) -> some View where S: ButtonStyle {
environment(\.buttonStyle, .buttonStyle(.init(style)))
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2020 Tokamak contributors
// Copyright 2018-2020 Tokamak contributors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
Expand All @@ -12,13 +12,15 @@
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Created by Gene Z. Ragan on 07/22/2020.
// Created by Carson Katri on 7/12/21.
//

import TokamakCore
public struct ButtonRole: Equatable {
public static let destructive = ButtonRole(rawValue: 0)
public static let cancel = ButtonRole(rawValue: 1)

extension ButtonStyleConfiguration.Label: DOMPrimitive {
var renderedBody: AnyView {
_ButtonStyleConfigurationProxy.Label(self).content
private let rawValue: Int
private init(rawValue: Int) {
self.rawValue = rawValue
}
}
Loading