From 48fd61f429df07f6368e9283431db480e13dc6cb Mon Sep 17 00:00:00 2001 From: Alexandr Romanov Date: Sun, 6 Oct 2024 11:46:38 +0400 Subject: [PATCH] Add RoundedRectangleCorner for macOS, update filed position and etc --- .../OversizeUI/Controls/Avatar/Avatar.swift | 31 ++++++- .../Controls/Avatar/AvatarModifiers.swift | 3 +- .../Controls/Button/FieldButtonStyle.swift | 23 ++--- .../Controls/IconPicker/IconPicker.swift | 5 +- .../Controls/PageControl/PageIndexView.swift | 4 + .../OversizeUI/Controls/PageView/Page.swift | 2 +- .../Controls/PriceField/PriceField.swift | 2 +- .../Controls/Select/MultiSelect.swift | 5 ++ .../OversizeUI/Controls/Select/Select.swift | 5 ++ .../OversizeUI/Controls/TextBox/TextBox.swift | 45 ++++++---- .../TextEditorPlaceholderViewModifier.swift | 74 +++++++++++++--- .../TextField/FieldHelperViewModifier.swift | 4 +- .../TextField/LabeledTextFieldStyle.swift | 88 +++++++++++-------- .../Core/Appearance/ThemeSettings.swift | 6 ++ .../FieldPositionEnvironment.swift | 10 +-- .../Core/EnvironmentKeys/Platform.swift | 14 +-- Sources/OversizeUI/Core/Typography.swift | 42 ++++++++- Sources/OversizeUI/Deprecated/PageView.swift | 16 ++++ .../Extensions/View/View+CornerRadius.swift | 14 ++- .../Shapes/RoundedRectangleCorner.swift | 84 ++++++++++++++++-- 20 files changed, 365 insertions(+), 112 deletions(-) diff --git a/Sources/OversizeUI/Controls/Avatar/Avatar.swift b/Sources/OversizeUI/Controls/Avatar/Avatar.swift index 9c1d2fb..80449be 100644 --- a/Sources/OversizeUI/Controls/Avatar/Avatar.swift +++ b/Sources/OversizeUI/Controls/Avatar/Avatar.swift @@ -41,6 +41,9 @@ public struct Avatar: View { /// Sets a stroke color for the Avatar. var strokeColor: Color = .clear + /// Sets a stroke width for the Avatar. + var strokeLineWidth: CGFloat = 2 + /// Sets a custom background color for the Avatar. var background: AvatarBackgroundType = .color(.surfaceSecondary) @@ -82,13 +85,23 @@ public struct Avatar: View { .scaledToFill() .frame(width: avatarSize, height: avatarSize) .clipShape(Circle()) - .overlay(Circle().stroke(strokeColor, lineWidth: 2)) + .overlay( + Circle().stroke( + strokeColor, + lineWidth: strokeLineWidth + ) + ) } else { ZStack { avatarSurface .frame(width: avatarSize, height: avatarSize) - .overlay(Circle().stroke(strokeColor, lineWidth: 2)) + .overlay( + Circle().stroke( + strokeColor, + lineWidth: strokeLineWidth + ) + ) avatarLabel } @@ -106,13 +119,23 @@ public struct Avatar: View { .scaledToFill() .frame(width: Space.xxxLarge.rawValue, height: Space.xxxLarge.rawValue) .clipShape(Circle()) - .overlay(Circle().stroke(strokeColor, lineWidth: 2)) + .overlay( + Circle().stroke( + strokeColor, + lineWidth: strokeLineWidth + ) + ) } else { ZStack { avatarSurface .frame(width: Space.xxxLarge.rawValue, height: Space.xxxLarge.rawValue) - .overlay(Circle().stroke(strokeColor, lineWidth: 2)) + .overlay( + Circle().stroke( + strokeColor, + lineWidth: strokeLineWidth + ) + ) } } } diff --git a/Sources/OversizeUI/Controls/Avatar/AvatarModifiers.swift b/Sources/OversizeUI/Controls/Avatar/AvatarModifiers.swift index 0e1db62..da840d0 100644 --- a/Sources/OversizeUI/Controls/Avatar/AvatarModifiers.swift +++ b/Sources/OversizeUI/Controls/Avatar/AvatarModifiers.swift @@ -27,9 +27,10 @@ public extension Avatar { /// Sets a stroke for the Avatar /// - Parameter strokeColor: Color for Avatar stroke /// - Returns: The modified view with stroke. - func avatarStroke(_ strokeColor: Color = .surfacePrimary) -> Avatar { + func avatarStroke(_ strokeColor: Color = .surfacePrimary, lineWidth: CGFloat = 2) -> Avatar { var control = self control.strokeColor = strokeColor + control.strokeLineWidth = lineWidth return control } } diff --git a/Sources/OversizeUI/Controls/Button/FieldButtonStyle.swift b/Sources/OversizeUI/Controls/Button/FieldButtonStyle.swift index 5121cbc..1a79818 100644 --- a/Sources/OversizeUI/Controls/Button/FieldButtonStyle.swift +++ b/Sources/OversizeUI/Controls/Button/FieldButtonStyle.swift @@ -7,7 +7,7 @@ import SwiftUI public struct FieldButtonStyle: ButtonStyle { @Environment(\.theme) private var theme: ThemeSettings - @Environment(\.fieldPosition) private var fieldPosition: FieldPosition + @Environment(\.fieldPosition) private var fieldPosition: VerticalAlignment? public init() {} @@ -24,16 +24,6 @@ public struct FieldButtonStyle: ButtonStyle { @ViewBuilder private func fieldBackground(isPressed: Bool) -> some View { switch fieldPosition { - case .default: - RoundedRectangle(cornerRadius: Radius.medium, style: .continuous) - .fill(isPressed ? Color.surfaceTertiary : Color.surfaceSecondary) - .overlay( - RoundedRectangle(cornerRadius: Radius.medium, - style: .continuous) - .stroke(theme.borderTextFields - ? Color.border - : Color.surfaceSecondary, lineWidth: CGFloat(theme.borderSize)) - ) case .top, .bottom, .center: #if os(iOS) RoundedRectangleCorner(radius: Radius.medium, corners: backgroundShapeCorners) @@ -55,6 +45,17 @@ public struct FieldButtonStyle: ButtonStyle { : Color.surfaceSecondary, lineWidth: CGFloat(theme.borderSize)) ) #endif + + default: + RoundedRectangle(cornerRadius: Radius.medium, style: .continuous) + .fill(isPressed ? Color.surfaceTertiary : Color.surfaceSecondary) + .overlay( + RoundedRectangle(cornerRadius: Radius.medium, + style: .continuous) + .stroke(theme.borderTextFields + ? Color.border + : Color.surfaceSecondary, lineWidth: CGFloat(theme.borderSize)) + ) } } diff --git a/Sources/OversizeUI/Controls/IconPicker/IconPicker.swift b/Sources/OversizeUI/Controls/IconPicker/IconPicker.swift index dd1ca6d..c5314e9 100644 --- a/Sources/OversizeUI/Controls/IconPicker/IconPicker.swift +++ b/Sources/OversizeUI/Controls/IconPicker/IconPicker.swift @@ -5,9 +5,7 @@ import SwiftUI -@available(macOS, unavailable) -@available(watchOS, unavailable) -@available(tvOS, unavailable) +#if os(iOS) public struct IconPicker: View { @Environment(\.theme) private var theme: ThemeSettings @Environment(\.horizontalSizeClass) var horizontalSizeClass @@ -109,3 +107,4 @@ public struct IconPicker: View { } } } +#endif diff --git a/Sources/OversizeUI/Controls/PageControl/PageIndexView.swift b/Sources/OversizeUI/Controls/PageControl/PageIndexView.swift index 72d48c3..5fc08ef 100644 --- a/Sources/OversizeUI/Controls/PageControl/PageIndexView.swift +++ b/Sources/OversizeUI/Controls/PageControl/PageIndexView.swift @@ -19,7 +19,11 @@ public struct PageIndexView: View { ForEach(0 ..< maxIndex, id: \.self) { index in Capsule() .fill(index == self.index ? Color.accent : Color.surfaceTertiary) + #if os(iOS) .frame(width: index == self.index ? 28 : 8, height: 8) + #else + .frame(width: index == self.index ? 24 : 6, height: 6) + #endif .animation(.default, value: index) } } diff --git a/Sources/OversizeUI/Controls/PageView/Page.swift b/Sources/OversizeUI/Controls/PageView/Page.swift index 9709c53..b176af8 100644 --- a/Sources/OversizeUI/Controls/PageView/Page.swift +++ b/Sources/OversizeUI/Controls/PageView/Page.swift @@ -1109,7 +1109,7 @@ struct SeartchTextFieldButtonStyle: ButtonStyle { private extension View { @ViewBuilder func prefersNavigationBarHidden() -> some View { - #if os(watchOS) + #if os(watchOS) || os(macOS) self #else if #available(iOS 16.0, macOS 13.0, tvOS 16.0, *) { diff --git a/Sources/OversizeUI/Controls/PriceField/PriceField.swift b/Sources/OversizeUI/Controls/PriceField/PriceField.swift index 8495391..4f19577 100644 --- a/Sources/OversizeUI/Controls/PriceField/PriceField.swift +++ b/Sources/OversizeUI/Controls/PriceField/PriceField.swift @@ -9,7 +9,7 @@ import SwiftUI import UIKit #endif -@available(macOS 13, iOS 16, tvOS 16, watchOS 9, *) +@available(macOS 14, iOS 16, tvOS 16, watchOS 9, *) public struct PriceField: View { private var formatter: NumberFormatter { let formatter = NumberFormatter() diff --git a/Sources/OversizeUI/Controls/Select/MultiSelect.swift b/Sources/OversizeUI/Controls/Select/MultiSelect.swift index e9ae58f..8588495 100644 --- a/Sources/OversizeUI/Controls/Select/MultiSelect.swift +++ b/Sources/OversizeUI/Controls/Select/MultiSelect.swift @@ -6,6 +6,7 @@ import SwiftUI // swiftlint:disable all +@available(iOS 15.0, macOS 14, tvOS 15.0, watchOS 9.0, *) public struct MultiSelect: View where Content: View, @@ -143,6 +144,7 @@ public struct MultiSelect: View where Content: View, @@ -138,6 +139,7 @@ public struct Select TextBox { diff --git a/Sources/OversizeUI/Controls/TextEditor/TextEditorPlaceholderViewModifier.swift b/Sources/OversizeUI/Controls/TextEditor/TextEditorPlaceholderViewModifier.swift index 8a6f9f0..92bbc53 100644 --- a/Sources/OversizeUI/Controls/TextEditor/TextEditorPlaceholderViewModifier.swift +++ b/Sources/OversizeUI/Controls/TextEditor/TextEditorPlaceholderViewModifier.swift @@ -31,14 +31,12 @@ public struct TextEditorPlaceholderViewModifier: ViewModifier { } content - .padding(.horizontal, .xSmall) - .padding(.top, topInputPadding) - .padding(.bottom, 10) + .padding(padding) .headline(.medium) .onSurfaceHighEmphasisForegroundColor() .background { ZStack { - RoundedRectangle(cornerRadius: .medium, style: .continuous) + RoundedRectangle(cornerRadius: fieldRadius, style: .continuous) .fill(isFocused ? Color.surfacePrimary : Color.surfaceSecondary) overlay } @@ -53,12 +51,66 @@ public struct TextEditorPlaceholderViewModifier: ViewModifier { } } - var topInputPadding: CGFloat { + private var fieldRadius: Radius { + #if os(macOS) + return .xSmall + #else + return .medium + #endif + } + + var padding: EdgeInsets { + switch fieldPlaceholderPosition { + case .default, .adjacent: + #if os(macOS) + return .init( + top: 10, + leading: Space.xSmall.rawValue, + bottom: 10, + trailing: Space.xSmall.rawValue + ) + #else + return .init( + top: 10, + leading: Space.xSmall.rawValue, + bottom: 10, + trailing: Space.xSmall.rawValue + ) + #endif + case .overInput: + #if os(macOS) + return .init( + top: text.isEmpty ? 13 : 22, + leading: Space.xxSmall.rawValue, + bottom: 10, + trailing: Space.xxSmall.rawValue + ) + #else + return .init( + top: text.isEmpty ? 8 : 22, + leading: Space.xSmall.rawValue, + bottom: 10, + trailing: Space.xSmall.rawValue + ) + + #endif + } + } + + var labelPadding: EdgeInsets { switch fieldPlaceholderPosition { case .default, .adjacent: - 10 + #if os(macOS) + return .init(Space.xSmall) + #else + return .init(Space.xSmall) + #endif case .overInput: - text.isEmpty ? 8 : 22 + #if os(macOS) + return .init(horizontal: Space.xSmall, vertical: Space.xSmall) + #else + return .init(Space.xxSmall) + #endif } } @@ -71,7 +123,7 @@ public struct TextEditorPlaceholderViewModifier: ViewModifier { .subheadline() .onSurfaceDisabledForegroundColor() .opacity(0.7) - .padding(.small) + .padding(labelPadding) } case .adjacent: EmptyView() @@ -81,14 +133,14 @@ public struct TextEditorPlaceholderViewModifier: ViewModifier { .fontWeight(text.isEmpty ? .medium : .semibold) .onSurfaceDisabledForegroundColor() .opacity(0.7) - .padding(.small) + .padding(labelPadding) .offset(y: text.isEmpty ? 0 : -6) } } @ViewBuilder var overlay: some View { - RoundedRectangle(cornerRadius: Radius.medium, style: .continuous) + RoundedRectangle(cornerRadius: fieldRadius, style: .continuous) .stroke(overlayBorderColor, lineWidth: isFocused ? 2 : CGFloat(theme.borderSize)) } @@ -123,10 +175,12 @@ struct TextEditor_preview: PreviewProvider { TextEditor(text: .constant("")) .textEditorPlaceholder("Complaint", text: .constant("")) .fieldLabelPosition(.overInput) + .fieldPosition(.top) TextEditor(text: .constant("Text")) .textEditorPlaceholder("Complaint", text: .constant("Text")) .fieldLabelPosition(.overInput) + .fieldPosition(.bottom) Spacer() } diff --git a/Sources/OversizeUI/Controls/TextField/FieldHelperViewModifier.swift b/Sources/OversizeUI/Controls/TextField/FieldHelperViewModifier.swift index fa1edce..ce2e97e 100644 --- a/Sources/OversizeUI/Controls/TextField/FieldHelperViewModifier.swift +++ b/Sources/OversizeUI/Controls/TextField/FieldHelperViewModifier.swift @@ -23,13 +23,13 @@ public struct FieldHelperViewModifier: ViewModifier { } public func body(content: Content) -> some View { - VStack(alignment: .leading, spacing: platform == .mac ? .xxxSmall : .xSmall) { + VStack(alignment: .leading, spacing: platform == .macOS ? .xxxSmall : .xSmall) { content if helperText.isEmpty == false, helperStyle != .none { Text(helperText) .subheadline(.medium) .foregroundColor(helperForegroundColor) - .offset(x: platform == .mac ? 4 : 0) + .offset(x: platform == .macOS ? 4 : 0) } } .animation(.easeIn(duration: 0.15), value: helperStyle) diff --git a/Sources/OversizeUI/Controls/TextField/LabeledTextFieldStyle.swift b/Sources/OversizeUI/Controls/TextField/LabeledTextFieldStyle.swift index a88d016..3407d6e 100644 --- a/Sources/OversizeUI/Controls/TextField/LabeledTextFieldStyle.swift +++ b/Sources/OversizeUI/Controls/TextField/LabeledTextFieldStyle.swift @@ -6,10 +6,11 @@ import SwiftUI // swiftlint:disable identifier_name +@available(iOS 15.0, macOS 14, tvOS 15.0, watchOS 9.0, *) public struct LabeledTextFieldStyle: TextFieldStyle { @Environment(\.theme) private var theme: ThemeSettings @Environment(\.fieldLabelPosition) private var fieldPlaceholderPosition: FieldLabelPosition - @Environment(\.fieldPosition) private var fieldPosition: FieldPosition + @Environment(\.fieldPosition) private var fieldPosition: VerticalAlignment? @Environment(\.platform) private var platform: Platform @FocusState private var isFocused: Bool @Binding private var text: String @@ -21,13 +22,13 @@ public struct LabeledTextFieldStyle: TextFieldStyle { } public func _body(configuration: TextField) -> some View { - VStack(alignment: .leading, spacing: platform == .mac ? .xxxSmall : .xSmall) { + VStack(alignment: .leading, spacing: platform == .macOS ? .xxxSmall : .xSmall) { if fieldPlaceholderPosition == .adjacent { Text(placeholder) .subheadline(.medium) - .foregroundColor(platform == .mac ? .onSurfaceMediumEmphasis : .onSurfaceHighEmphasis) + .foregroundColor(platform == .macOS ? .onSurfaceMediumEmphasis : .onSurfaceHighEmphasis) .frame(maxWidth: .infinity, alignment: .leading) - .offset(x: platform == .mac ? 4 : 0) + .offset(x: platform == .macOS ? 4 : 0) } ZStack(alignment: .leading) { labelTextView @@ -62,7 +63,7 @@ public struct LabeledTextFieldStyle: TextFieldStyle { #endif case .overInput: #if os(macOS) - return .init(horizontal: .xxSmall, vertical: .xSmall) + return .init(.xSmall) #else return .init( top: Space.xxxSmall.rawValue + Space.small.rawValue, @@ -83,27 +84,28 @@ public struct LabeledTextFieldStyle: TextFieldStyle { @ViewBuilder private var fieldBackground: some View { switch fieldPosition { - case .default: + case .top, .bottom, .center: #if canImport(UIKit) - RoundedRectangle( - cornerRadius: fieldRadius, - style: .continuous + RoundedRectangleCorner( + radius: fieldRadius, + corners: backgroundShapeCorners ) .fill(isFocused ? Color.surfacePrimary : Color.surfaceSecondary) #else if fieldPlaceholderPosition != .adjacent { - RoundedRectangle( - cornerRadius: fieldRadius, - style: .continuous + RoundedRectangleCorner( + radius: fieldRadius, + corners: backgroundShapeCorners ) .fill(isFocused ? Color.surfacePrimary : Color.surfaceSecondary) } #endif - case .top, .bottom, .center: + + default: #if canImport(UIKit) - RoundedRectangleCorner( - radius: fieldRadius, - corners: backgroundShapeCorners + RoundedRectangle( + cornerRadius: fieldRadius, + style: .continuous ) .fill(isFocused ? Color.surfacePrimary : Color.surfaceSecondary) #else @@ -118,7 +120,7 @@ public struct LabeledTextFieldStyle: TextFieldStyle { } } - var fieldRadius: Radius { + private var fieldRadius: Radius { #if os(macOS) return .xSmall #else @@ -141,13 +143,28 @@ public struct LabeledTextFieldStyle: TextFieldStyle { } #endif + #if canImport(AppKit) + private var backgroundShapeCorners: RectCorner { + switch fieldPosition { + case .top: + [.topLeft, .topRight] + case .bottom: + [.bottomLeft, .bottomRight] + case .center: + [] + default: + [.allCorners] + } + } + #endif + private var fieldOffset: CGFloat { switch fieldPlaceholderPosition { case .default, .adjacent: .zero case .overInput: #if os(macOS) - text.isEmpty ? 0 : 8 + text.isEmpty && !isFocused ? -2 : text.isEmpty ? 0 : isFocused ? 9 : 7 #else text.isEmpty ? 0 : 10 #endif @@ -157,23 +174,6 @@ public struct LabeledTextFieldStyle: TextFieldStyle { @ViewBuilder private var overlay: some View { switch fieldPosition { - case .default: - #if canImport(UIKit) - RoundedRectangle( - cornerRadius: fieldRadius, - style: .continuous - ) - .stroke(overlayBorderColor, lineWidth: isFocused ? 2 : CGFloat(theme.borderSize)) - #else - if fieldPlaceholderPosition != .adjacent { - RoundedRectangle( - cornerRadius: fieldRadius, - style: .continuous - ) - .stroke(overlayBorderColor, lineWidth: isFocused ? 2 : CGFloat(theme.borderSize)) - } - #endif - case .top, .bottom, .center: #if canImport(UIKit) RoundedRectangleCorner(radius: fieldRadius, corners: backgroundShapeCorners) @@ -181,6 +181,22 @@ public struct LabeledTextFieldStyle: TextFieldStyle { overlayBorderColor, lineWidth: isFocused ? 2 : CGFloat(theme.borderSize) ) + #elseif canImport(AppKit) + if fieldPlaceholderPosition != .adjacent { + RoundedRectangleCorner(radius: fieldRadius, corners: backgroundShapeCorners) + .stroke( + overlayBorderColor, + lineWidth: isFocused ? 2 : CGFloat(theme.borderSize) + ) + } + #endif + default: + #if canImport(UIKit) + RoundedRectangle( + cornerRadius: fieldRadius, + style: .continuous + ) + .stroke(overlayBorderColor, lineWidth: isFocused ? 2 : CGFloat(theme.borderSize)) #else if fieldPlaceholderPosition != .adjacent { RoundedRectangle( @@ -237,6 +253,7 @@ public struct LabeledTextFieldStyle: TextFieldStyle { } } +@available(iOS 15.0, macOS 14, tvOS 15.0, watchOS 9.0, *) public extension TextFieldStyle where Self == LabeledTextFieldStyle { static var `default`: LabeledTextFieldStyle { LabeledTextFieldStyle(placeholder: "", text: .constant("")) @@ -247,6 +264,7 @@ public extension TextFieldStyle where Self == LabeledTextFieldStyle { } } +@available(iOS 15.0, macOS 14, tvOS 15.0, watchOS 9.0, *) struct LabeledTextFieldStyle_Previews: PreviewProvider { static var previews: some View { VStack(spacing: 32) { diff --git a/Sources/OversizeUI/Core/Appearance/ThemeSettings.swift b/Sources/OversizeUI/Core/Appearance/ThemeSettings.swift index 4d12550..f7031be 100644 --- a/Sources/OversizeUI/Core/Appearance/ThemeSettings.swift +++ b/Sources/OversizeUI/Core/Appearance/ThemeSettings.swift @@ -38,8 +38,14 @@ public class ThemeSettings: ObservableObject { @AppStorage(ThemeSettingsNames.borderApp) public var borderApp: Bool = false @AppStorage(ThemeSettingsNames.borderButtons) public var borderButtons: Bool = false + #if os(macOS) + @AppStorage(ThemeSettingsNames.borderTextFields) public var borderTextFields: Bool = true + @AppStorage(ThemeSettingsNames.borderSurface) public var borderSurface: Bool = true + #else @AppStorage(ThemeSettingsNames.borderSurface) public var borderSurface: Bool = false @AppStorage(ThemeSettingsNames.borderTextFields) public var borderTextFields: Bool = false + #endif + @AppStorage(ThemeSettingsNames.borderControls) public var borderControls: Bool = false @AppStorage(ThemeSettingsNames.borderSize) public var borderSize: Double = 0.5 diff --git a/Sources/OversizeUI/Core/EnvironmentKeys/FieldPositionEnvironment.swift b/Sources/OversizeUI/Core/EnvironmentKeys/FieldPositionEnvironment.swift index 5ea8649..9011d73 100644 --- a/Sources/OversizeUI/Core/EnvironmentKeys/FieldPositionEnvironment.swift +++ b/Sources/OversizeUI/Core/EnvironmentKeys/FieldPositionEnvironment.swift @@ -5,23 +5,19 @@ import SwiftUI -public enum FieldPosition { - case `default`, top, bottom, center -} - private struct FieldPositionKey: EnvironmentKey { - public static var defaultValue: FieldPosition = .default + public static var defaultValue: VerticalAlignment? = nil } public extension EnvironmentValues { - var fieldPosition: FieldPosition { + var fieldPosition: VerticalAlignment? { get { self[FieldPositionKey.self] } set { self[FieldPositionKey.self] = newValue } } } public extension View { - func fieldPosition(_ position: FieldPosition) -> some View { + func fieldPosition(_ position: VerticalAlignment?) -> some View { environment(\.fieldPosition, position) } } diff --git a/Sources/OversizeUI/Core/EnvironmentKeys/Platform.swift b/Sources/OversizeUI/Core/EnvironmentKeys/Platform.swift index 853b23b..2122915 100644 --- a/Sources/OversizeUI/Core/EnvironmentKeys/Platform.swift +++ b/Sources/OversizeUI/Core/EnvironmentKeys/Platform.swift @@ -9,15 +9,15 @@ import UIKit #endif public enum Platform { - case iPhone, iPad, mac, tv, watch, vision, carPlay, other + case iPhone, iPadOS, macOS, tvOS, watchOS, visionOS, carPlay, other } private struct PlatformKey: EnvironmentKey { static let defaultValue: Platform = { #if os(macOS) || targetEnvironment(macCatalyst) - return .mac + return .macOS #elseif os(watchOS) - return .watch + return .watchOS #elseif os(visionOS) return .vision #elseif canImport(UIKit) @@ -25,15 +25,15 @@ private struct PlatformKey: EnvironmentKey { case .phone: return .iPhone case .pad: - return .iPad + return .iPadOS case .tv: - return .tv + return .tvOS case .carPlay: return .carPlay case .mac: - return .mac + return .macOS case .vision: - return .vision + return .visionOS case .unspecified: return .other @unknown default: diff --git a/Sources/OversizeUI/Core/Typography.swift b/Sources/OversizeUI/Core/Typography.swift index 12a9631..b779820 100644 --- a/Sources/OversizeUI/Core/Typography.swift +++ b/Sources/OversizeUI/Core/Typography.swift @@ -38,35 +38,71 @@ public struct Typography: ViewModifier { public func body(content: Content) -> some View { content - .font(.system(fontStyle, design: fontDesign).weight(fontWeight).leading(.tight)) - .frame(minHeight: lineHeight) + .font( + .system(fontStyle, design: fontDesign) + .weight(fontWeight) + .leading(.tight) + ) .lineSpacing(lineHeight * 0.2) + .frame(minHeight: lineHeight) } private var lineHeight: CGFloat { switch fontStyle { case .largeTitle: + #if os(macOS) + return 40 + #else return 44 + #endif case .title: + #if os(macOS) + return 32 + #else return 36 + #endif case .title2: + #if os(macOS) + return 24 + #else return 28 + #endif case .title3: + #if os(macOS) + return 20 + #else return 24 + #endif case .headline: + #if os(macOS) + return 20 + #else return 24 + #endif case .subheadline: + #if os(macOS) + return 16 + #else return 20 + #endif case .body: + #if os(macOS) + return 20 + #else return 24 + #endif case .callout: + #if os(macOS) + return 16 + #else return 20 + #endif case .footnote: return 16 case .caption: return 16 case .caption2: - return 12 + return 16 @unknown default: return 16 } diff --git a/Sources/OversizeUI/Deprecated/PageView.swift b/Sources/OversizeUI/Deprecated/PageView.swift index 2004381..d3ccc6a 100644 --- a/Sources/OversizeUI/Deprecated/PageView.swift +++ b/Sources/OversizeUI/Deprecated/PageView.swift @@ -5,6 +5,7 @@ import SwiftUI +@available(iOS 15.0, macOS 14, tvOS 15.0, watchOS 9.0, *) public struct PageView: View where Content: View, LeadingBar: View, TrailingBar: View, TopToolbar: View, TitleLabel: View { @Environment(\.screenSize) var screenSize @@ -271,6 +272,7 @@ public struct PageView } } +@available(iOS 15.0, macOS 14, tvOS 15.0, watchOS 9.0, *) public extension PageView { enum PageViewBottomType { case shadow, gradient, none @@ -281,6 +283,7 @@ public extension PageView { } } +@available(iOS 15.0, macOS 14, tvOS 15.0, watchOS 9.0, *) public extension PageView where LeadingBar == EmptyView, TitleLabel == EmptyView { init(_ title: String? = nil, onOffsetChanged: @escaping (CGFloat) -> Void = { _ in }, @@ -296,6 +299,7 @@ public extension PageView where LeadingBar == EmptyView, TitleLabel == EmptyView } } +@available(iOS 15.0, macOS 14, tvOS 15.0, watchOS 9.0, *) public extension PageView where TrailingBar == EmptyView, TitleLabel == EmptyView { init(_ title: String? = nil, onOffsetChanged: @escaping (CGFloat) -> Void = { _ in }, @@ -311,6 +315,7 @@ public extension PageView where TrailingBar == EmptyView, TitleLabel == EmptyVie } } +@available(iOS 15.0, macOS 14, tvOS 15.0, watchOS 9.0, *) public extension PageView where TrailingBar == EmptyView, LeadingBar == EmptyView, TitleLabel == EmptyView { init(_ title: String? = nil, onOffsetChanged: @escaping (CGFloat) -> Void = { _ in }, @@ -327,6 +332,7 @@ public extension PageView where TrailingBar == EmptyView, LeadingBar == EmptyVie } } +@available(iOS 15.0, macOS 14, tvOS 15.0, watchOS 9.0, *) public extension PageView where TrailingBar == EmptyView, LeadingBar == EmptyView, TopToolbar == EmptyView, TitleLabel == EmptyView { init(_ title: String? = nil, onOffsetChanged: @escaping (CGFloat) -> Void = { _ in }, @@ -344,6 +350,7 @@ public extension PageView where TrailingBar == EmptyView, LeadingBar == EmptyVie } } +@available(iOS 15.0, macOS 14, tvOS 15.0, watchOS 9.0, *) public extension PageView where LeadingBar == EmptyView, TopToolbar == EmptyView, TitleLabel == EmptyView { init(_ title: String? = nil, onOffsetChanged: @escaping (CGFloat) -> Void = { _ in }, @@ -360,6 +367,7 @@ public extension PageView where LeadingBar == EmptyView, TopToolbar == EmptyView } } +@available(iOS 15.0, macOS 14, tvOS 15.0, watchOS 9.0, *) public extension PageView where TrailingBar == EmptyView, TopToolbar == EmptyView, TitleLabel == EmptyView { init(_ title: String? = nil, onOffsetChanged: @escaping (CGFloat) -> Void = { _ in }, @@ -376,6 +384,7 @@ public extension PageView where TrailingBar == EmptyView, TopToolbar == EmptyVie } } +@available(iOS 15.0, macOS 14, tvOS 15.0, watchOS 9.0, *) public extension PageView where TopToolbar == EmptyView, TitleLabel == EmptyView { init(_ title: String? = nil, onOffsetChanged: @escaping (CGFloat) -> Void = { _ in }, @@ -391,6 +400,7 @@ public extension PageView where TopToolbar == EmptyView, TitleLabel == EmptyView } } +@available(iOS 15.0, macOS 14, tvOS 15.0, watchOS 9.0, *) public extension PageView where TrailingBar == EmptyView, LeadingBar == EmptyView, TopToolbar == EmptyView { init(_ title: String? = nil, onOffsetChanged: @escaping (CGFloat) -> Void = { _ in }, @@ -407,6 +417,7 @@ public extension PageView where TrailingBar == EmptyView, LeadingBar == EmptyVie } } +@available(iOS 15.0, macOS 14, tvOS 15.0, watchOS 9.0, *) public extension PageView where TrailingBar == EmptyView, TopToolbar == EmptyView { init(_ title: String? = nil, onOffsetChanged: @escaping (CGFloat) -> Void = { _ in }, @@ -422,6 +433,7 @@ public extension PageView where TrailingBar == EmptyView, TopToolbar == EmptyVie } } +@available(iOS 15.0, macOS 14, tvOS 15.0, watchOS 9.0, *) public extension PageView where LeadingBar == EmptyView, TopToolbar == EmptyView { init(_ title: String? = nil, onOffsetChanged: @escaping (CGFloat) -> Void = { _ in }, @@ -437,6 +449,7 @@ public extension PageView where LeadingBar == EmptyView, TopToolbar == EmptyView } } +@available(iOS 15.0, macOS 14, tvOS 15.0, watchOS 9.0, *) public extension PageView where TrailingBar == EmptyView { init(_ title: String? = nil, onOffsetChanged: @escaping (CGFloat) -> Void = { _ in }, @@ -451,6 +464,7 @@ public extension PageView where TrailingBar == EmptyView { } } +@available(iOS 15.0, macOS 14, tvOS 15.0, watchOS 9.0, *) public extension PageView where LeadingBar == EmptyView { init(_ title: String? = nil, onOffsetChanged: @escaping (CGFloat) -> Void = { _ in }, @@ -465,6 +479,7 @@ public extension PageView where LeadingBar == EmptyView { } } +@available(iOS 15.0, macOS 14, tvOS 15.0, watchOS 9.0, *) public extension PageView where TopToolbar == EmptyView { init(_ title: String? = nil, onOffsetChanged: @escaping (CGFloat) -> Void = { _ in }, @@ -479,6 +494,7 @@ public extension PageView where TopToolbar == EmptyView { } } +@available(iOS 15.0, macOS 14, tvOS 15.0, watchOS 9.0, *) public extension PageView where TitleLabel == EmptyView { init(_ title: String? = nil, onOffsetChanged: @escaping (CGFloat) -> Void = { _ in }, diff --git a/Sources/OversizeUI/Extensions/View/View+CornerRadius.swift b/Sources/OversizeUI/Extensions/View/View+CornerRadius.swift index c3a79c7..366b083 100644 --- a/Sources/OversizeUI/Extensions/View/View+CornerRadius.swift +++ b/Sources/OversizeUI/Extensions/View/View+CornerRadius.swift @@ -3,14 +3,22 @@ // View+CornerRadius.swift, created on 11.09.2021 // -#if os(iOS) import SwiftUI +#if canImport(UIKit) +public extension View { + func cornerRadius(_ radius: CGFloat, corners: UIRectCorner) -> some View { + clipShape(RoundedRectangleCorner(radius: radius, corners: corners)) + } +} +#endif + +#if canImport(AppKit) public extension View { - @available(macOS, unavailable) @available(watchOS, unavailable) @available(tvOS, unavailable) - func cornerRadius(_ radius: CGFloat, corners: UIRectCorner) -> some View { + @available(macOS 14, *) + func cornerRadius(_ radius: CGFloat, corners: RectCorner) -> some View { clipShape(RoundedRectangleCorner(radius: radius, corners: corners)) } } diff --git a/Sources/OversizeUI/Shapes/RoundedRectangleCorner.swift b/Sources/OversizeUI/Shapes/RoundedRectangleCorner.swift index 4de13c5..c41437c 100644 --- a/Sources/OversizeUI/Shapes/RoundedRectangleCorner.swift +++ b/Sources/OversizeUI/Shapes/RoundedRectangleCorner.swift @@ -1,16 +1,22 @@ -// -// Copyright © 2021 Alexander Romanov -// RoundedRectangleCorner.swift, created on 11.09.2021 -// +import SwiftUI #if canImport(UIKit) -import SwiftUI +import UIKit +#elseif canImport(AppKit) +import AppKit +#endif +@available(iOS 15.0, macOS 14, tvOS 15.0, watchOS 9.0, *) public struct RoundedRectangleCorner: Shape { private var radius: CGFloat = .infinity + #if canImport(UIKit) private var corners: UIRectCorner = .allCorners + #else + private var corners: RectCorner = .allCorners + #endif + #if canImport(UIKit) public init(radius: CGFloat, corners: UIRectCorner) { self.radius = radius self.corners = corners @@ -20,11 +26,79 @@ public struct RoundedRectangleCorner: Shape { self.radius = radius.rawValue self.corners = corners } + #else + public init(radius: CGFloat, corners: RectCorner) { + self.radius = radius + self.corners = corners + } + + public init(radius: Radius, corners: RectCorner) { + self.radius = radius.rawValue + self.corners = corners + } + #endif public func path(in rect: CGRect) -> Path { + #if canImport(UIKit) let path = UIBezierPath(roundedRect: rect, byRoundingCorners: corners, cornerRadii: CGSize(width: radius, height: radius)) return Path(path.cgPath) + #else + let path = NSBezierPath() + + let topLeftRadius = corners.contains(.topLeft) ? radius : 0 + let topRightRadius = corners.contains(.topRight) ? radius : 0 + let bottomLeftRadius = corners.contains(.bottomLeft) ? radius : 0 + let bottomRightRadius = corners.contains(.bottomRight) ? radius : 0 + + path.move(to: CGPoint(x: rect.minX + topLeftRadius, y: rect.minY)) + + path.line(to: CGPoint(x: rect.maxX - topRightRadius, y: rect.minY)) + if topRightRadius > 0 { + path.curve(to: CGPoint(x: rect.maxX, y: rect.minY + topRightRadius), + controlPoint1: CGPoint(x: rect.maxX - topRightRadius / 2, y: rect.minY), + controlPoint2: CGPoint(x: rect.maxX, y: rect.minY + topRightRadius / 2)) + } + + path.line(to: CGPoint(x: rect.maxX, y: rect.maxY - bottomRightRadius)) + if bottomRightRadius > 0 { + path.curve(to: CGPoint(x: rect.maxX - bottomRightRadius, y: rect.maxY), + controlPoint1: CGPoint(x: rect.maxX, y: rect.maxY - bottomRightRadius / 2), + controlPoint2: CGPoint(x: rect.maxX - bottomRightRadius / 2, y: rect.maxY)) + } + + path.line(to: CGPoint(x: rect.minX + bottomLeftRadius, y: rect.maxY)) + if bottomLeftRadius > 0 { + path.curve(to: CGPoint(x: rect.minX, y: rect.maxY - bottomLeftRadius), + controlPoint1: CGPoint(x: rect.minX + bottomLeftRadius / 2, y: rect.maxY), + controlPoint2: CGPoint(x: rect.minX, y: rect.maxY - bottomLeftRadius / 2)) + } + + path.line(to: CGPoint(x: rect.minX, y: rect.minY + topLeftRadius)) + if topLeftRadius > 0 { + path.curve(to: CGPoint(x: rect.minX + topLeftRadius, y: rect.minY), + controlPoint1: CGPoint(x: rect.minX, y: rect.minY + topLeftRadius / 2), + controlPoint2: CGPoint(x: rect.minX + topLeftRadius / 2, y: rect.minY)) + } + + path.close() + return Path(path.cgPath) + #endif + } +} + +#if !canImport(UIKit) +public struct RectCorner: OptionSet, Sendable { + public let rawValue: Int + + public static let topLeft = RectCorner(rawValue: 1 << 0) + public static let topRight = RectCorner(rawValue: 1 << 1) + public static let bottomLeft = RectCorner(rawValue: 1 << 2) + public static let bottomRight = RectCorner(rawValue: 1 << 3) + public static let allCorners: RectCorner = [.topLeft, .topRight, .bottomLeft, .bottomRight] + + public init(rawValue: Int) { + self.rawValue = rawValue } } #endif