Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
108 changes: 105 additions & 3 deletions Sources/OpenSwiftUI/View/Control/Button/Button.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,113 @@
//
// Audited for 3.5.2
// Status: Empty
import Foundation

public struct Button<Label>: View where Label: View {
public init(action: @escaping () -> Void, @ViewBuilder label: () -> Label) {
/// Mark: - Button

/// A control that initiates an action.
///
/// ## Overview
///
/// You can create a button by providing an action and a label. The aciton is either a method or closure that does something when a user
/// clicks or taps the button. The label is a view that describes the button's action -- for example, by showing text, an icon or both.
///
/// The label of a button can be any kind of view, such as Text view for text-only tables
nonisolated
public struct Button<ButtonLabel> where ButtonLabel: View {
private init(action: @escaping () -> Void, label: ButtonLabel) {
self.action = action;
self.label = label;
}

public var body: some View { EmptyView() }
public init(action: @escaping () -> Void, @ViewBuilder label: () -> ButtonLabel) {
self.init(action: action, label: label())
}

public init(role: ButtonRole?, action: @escaping () -> Void, @ViewBuilder label: () -> ButtonLabel) {
self.init(action: action, label: label())
// TODO : update style.configuration role
}

public var body: some View {
HStack(
alignment: .center,
){
self.label
}.onTapGesture {
if !isEnabled {
self.action()
}
}
}

/// The descriptor for the ``action`` triggered by the Button.
var label: ButtonLabel;

/// The ``action`` take whenever the button is interacted with.
var action: () -> Void;

/// The enabled or disabled state.
@Environment(\.isEnabled)
var isEnabled: Bool;

/// The active ``ButtonStyle``
@Environment(\.buttonStyle)
var style: AnyButtonStyle;
}

extension Button where ButtonLabel == Image {
init(_ image: Image, action: @escaping () -> Void) {
self.init(action: action, label: image,);
}
}

extension Button where ButtonLabel == Text {
@preconcurrency
nonisolated
init(_ label: LocalizedStringResource, action: @escaping () -> Void) {
self.init(action: action, label: Text(label));
}

@preconcurrency
nonisolated
init(_ label: LocalizedStringKey, action: @escaping () -> Void) {
self.init(action: action, label: Text(label));
}

@preconcurrency
nonisolated
init<S>(_ label: S, action: @escaping () -> Void) where S:StringProtocol{
self.init(action: action, label: Text(label));
}
}

extension Button where ButtonLabel == Label<Text, Image> {
@preconcurrency
nonisolated
init(_ title: LocalizedStringResource, image: ImageResource, action: @escaping () -> Void) {
self.init(action: action, label: Label(title, image: image));
}

@preconcurrency
nonisolated
init(_ title: LocalizedStringKey, image: ImageResource, action: @escaping () -> Void) {
self.init(action: action, label: Label(title, image: image))
}

@preconcurrency
nonisolated
init<S>(_ title: S, image: ImageResource, action: @escaping () -> Void) where S:StringProtocol {
self.init(action: action, label: Label(title, image: image));
}

@preconcurrency
nonisolated
init<S>(_ title: S, systemImage: String, action: @escaping () -> Void) where S: StringProtocol {
self.init(action: action, label: Label(title, image: systemImage));
}
}

#Preview("Creating a button: text", body: {Button("Example Text Button"){}.body})
#Preview("Creating a button: icon", body: {Button(Image("add")){}.body})
#Preview("Creating a button: Label<Text,Image>", body: { Button("Text", systemImage: "add"){}.body })
41 changes: 41 additions & 0 deletions Sources/OpenSwiftUI/View/Control/Button/ButtonSizing.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/// Mark: - ButtonSizing

/// The sizing behaviour of Buttons and other button-like controls.
struct ButtonSizing {
static var automatic: ButtonSizing { get {.init()}}
static var fitted: ButtonSizing { get {.init()} }
static var flexible: ButtonSizing { get {.init()} }
}

extension ButtonSizing: Hashable {}
extension ButtonSizing: Equatable {}
extension ButtonSizing: Sendable {}

nonisolated struct ButtonSizingModifier<Style>: StyleModifier where Style: ButtonStyle {
init(style: Style) {
self.style = style
}

var style: Style;

func styleBody(configuration: Style.Configuration) -> some View {
style.makeBody(configuration: configuration)
}
}

extension View {
nonisolated public func ButtonSizing<S>(_ style: S) -> some View where S: ButtonStyle {
modifier(ButtonSizingModifier(style: style))
}
}

struct ButtonSizingKey: EnvironmentKey {
static let defaultValue = ButtonSizing.automatic
}

extension EnvironmentValues {
var buttonSizing: ButtonSizing {
get { self[ButtonSizingKey.self] }
set { self[ButtonSizingKey.self] = newValue }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
// Audited for 3.5.2
// Status: Complete

/// MARK: - ButtonStyle

@MainActor @preconcurrency
public protocol ButtonStyle {
associatedtype Body: View

Expand All @@ -13,3 +16,80 @@ public protocol ButtonStyle {

typealias Configuration = ButtonStyleConfiguration
}

extension View {
nonisolated public func buttonStyle<S>(_ style: S) -> some View where S: ButtonStyle {
modifier(ButtonStyleModifier(style: style))
}
}

struct ButtonStyleModifier<Style>: StyleModifier where Style: ButtonStyle {
init(style: Style) {
self.style = style
}

var style: Style;

func styleBody(configuration: Style.Configuration) -> some View {
style.makeBody(configuration: configuration)
}
}

private struct ButtonStyleKey: EnvironmentKey {
static let defaultValue = AnyButtonStyle.default
}

private class AnyStyleBox {
func body(configuration _: ButtonStyleConfiguration) -> AnyView {
_openSwiftUIBaseClassAbstractMethod()
}
}

private final class StyleBox<Base: ButtonStyle>: AnyStyleBox {
let base: Base

init(base: Base) {
self.base = base
}

override func body(configuration: ButtonStyleConfiguration) -> AnyView {
AnyView(base.makeBody(configuration: configuration))
}
}

struct AnyButtonStyle: ButtonStyle {
private let box: AnyStyleBox;

static let `default` = AnyButtonStyle.init()

private init() {
self.box = AnyStyleBox()
}

@Environment(\.isEnabled)
nonisolated
var isEnabled: Bool;


@ViewBuilder
func makeBody(configuration: Configuration) -> some View {
box.body(configuration: configuration)
}

private init(box: AnyStyleBox) {
self.box = box
}

init(style: some ButtonStyle) {
self.box = StyleBox(base: style)
}
}


extension EnvironmentValues {
@inline(__always)
var buttonStyle: AnyButtonStyle {
get { self[ButtonStyleKey.self] }
set { self[ButtonStyleKey.self] = newValue }
}
}
54 changes: 54 additions & 0 deletions Sources/OpenSwiftUI/View/Label/Label.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import Foundation

nonisolated struct Label<Title,Icon>: View where Title: View, Icon: View {
private init(title: Title, icon: Icon) {
self.title = title;
self.icon = icon
}
init(@ViewBuilder title: () -> Title, @ViewBuilder icon: () -> Icon) {
self.title = title();
self.icon = icon();
}

var title: Title;
var icon: Icon;

var body: some View {
HStack(alignment: .center, spacing: 8) {
title
icon
}
}
}

extension Label where Title == Text, Icon == Image {
nonisolated
init(_ title: LocalizedStringResource, image name: String) {
self.init(title: Text(title), icon: Image(name))
}

nonisolated
init(_ title: LocalizedStringResource, image resource: ImageResource) {
self.init(title: Text(title), icon: Image(resource))
}
nonisolated
init(_ title: LocalizedStringKey, image name: String) {
self.init(title: Text(title), icon: Image(name))
}

nonisolated
init(_ title: LocalizedStringKey, image resource: ImageResource) {
self.init(title: Text(title), icon: Image(resource))
}
nonisolated
init<S>(_ title: S, image name: String) where S: StringProtocol {
self.init(title: Text(title), icon: Image(name))
}

nonisolated
init<S>(_ titleResource: S, image resource: ImageResource) where S: StringProtocol {
self.init(title: Text(titleResource), icon: Image(resource))
}
}

#Preview("Creating a label", body: {Label("label text", image: "add").body})
34 changes: 34 additions & 0 deletions Tests/OpenSwiftUITests/View/Control/Button/ButtonTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
//
// ButtonTests.swift
// OpenSwiftUITests

@testable import OpenSwiftUI
import Testing

@MainActor
@Suite(.snapshots(record: .never, diffTool: diffTool))
struct ButtonTests {
@Test
func enabledButtonInvokesAction() {
var clicked = false;
var cb = {
clicked = true;
}

var button = Button ("Run", cb)
// TODO: Trigger attempt to press button.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't see any examples for testing gestures. Some guidance would be appreciated 😄

expect(clicked, true);
}
@Test
func disabledButtonDoesntInvokeAction() {
var clicked = false;
var cb = {
clicked = true;
};

var button = Button ("Run", cb)

// TODO: Attempt to trigger the button.
expect(clicked, false);
}
}
16 changes: 16 additions & 0 deletions Tests/OpenSwiftUITests/View/Label/LabelTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
@testable import OpenSwiftUI
import Testing

@MainActor
@Suite(.snapshots(record: .never, diffTool: diffTool))
struct LabelTests {
@Test
func hasTheExpectedChildren() {
final icon = Image("add")
final text = Text("test")
final label = Label("test", "add")
// TODO Actualy validate contents
expect(label.title == text, true)
expect(label.icon == icon, true)
}
}