diff --git a/Sources/Luminare/Components/Auxiliary/LuminareButtonStyles.swift b/Sources/Luminare/Components/Auxiliary/LuminareButtonStyles.swift index 75101aa..6d3de73 100644 --- a/Sources/Luminare/Components/Auxiliary/LuminareButtonStyles.swift +++ b/Sources/Luminare/Components/Auxiliary/LuminareButtonStyles.swift @@ -10,11 +10,13 @@ import SwiftUI struct AspectRatioModifier: ViewModifier { @Environment(\.luminareMinHeight) private var minHeight @Environment(\.luminareAspectRatio) private var aspectRatio + @Environment(\.luminareAspectRatioContentMode) private var contentMode + @Environment(\.luminareAspectRatioHasFixedHeight) private var hasFixedHeight @ViewBuilder func body(content: Content) -> some View { if let aspectRatio { Group { - if isConstrained { + if isConstrained, let contentMode { content .frame( minWidth: minWidth, maxWidth: .infinity, @@ -22,8 +24,8 @@ struct AspectRatioModifier: ViewModifier { maxHeight: hasFixedHeight ? nil : .infinity ) .aspectRatio( - aspectRatio.aspectRatio, - contentMode: aspectRatio.contentMode + aspectRatio, + contentMode: contentMode ) } else { content @@ -34,7 +36,7 @@ struct AspectRatioModifier: ViewModifier { } } .fixedSize( - horizontal: aspectRatio.contentMode == .fit, + horizontal: contentMode == .fit, vertical: hasFixedHeight ) } else { @@ -42,19 +44,13 @@ struct AspectRatioModifier: ViewModifier { } } - private var hasFixedHeight: Bool { - guard let aspectRatio else { return false } - return aspectRatio.hasFixedHeight - } - private var isConstrained: Bool { - guard let aspectRatio else { return false } - return aspectRatio.contentMode == .fit || hasFixedHeight + guard let contentMode else { return false } + return contentMode == .fit || hasFixedHeight } private var minWidth: CGFloat? { - if hasFixedHeight, - let aspectRatio = aspectRatio?.aspectRatio { + if hasFixedHeight, let aspectRatio { minHeight * aspectRatio } else { nil diff --git a/Sources/Luminare/Components/Auxiliary/Scroll Views/AutoScrollView.swift b/Sources/Luminare/Components/Auxiliary/Scroll Views/AutoScrollView.swift index a2c29da..02d6e26 100644 --- a/Sources/Luminare/Components/Auxiliary/Scroll Views/AutoScrollView.swift +++ b/Sources/Luminare/Components/Auxiliary/Scroll Views/AutoScrollView.swift @@ -9,6 +9,11 @@ import SwiftUI /// A simple scroll view that enables scrolling only if the content is large enough to scroll. public struct AutoScrollView: View where Content: View { + @Environment(\.luminareContentMarginsTop) private var contentMarginsTop + @Environment(\.luminareContentMarginsLeading) private var contentMarginsLeading + @Environment(\.luminareContentMarginsBottom) private var contentMarginsBottom + @Environment(\.luminareContentMarginsTrailing) private var contentMarginsTrailing + private let axes: Axis.Set private let showsIndicators: Bool @ViewBuilder private var content: () -> Content @@ -33,29 +38,68 @@ public struct AutoScrollView: View where Content: View { } public var body: some View { - ScrollView(axes, showsIndicators: showsIndicators) { - content() - .onGeometryChange(for: CGSize.self) { proxy in - proxy.size - } action: { size in - contentSize = size + ScrollView(allowedAxes, showsIndicators: showsIndicators) { + VStack(spacing: 0) { + if contentMarginsTop > 0 { + Spacer() + .frame(height: contentMarginsTop) } + + content() + .padding(.leading, contentMarginsLeading) + .padding(.trailing, contentMarginsTrailing) + + if contentMarginsBottom > 0 { + Spacer() + .frame(height: contentMarginsBottom) + } + } + .onGeometryChange(for: CGSize.self) { proxy in + proxy.size + } action: { size in + contentSize = size + } } .onGeometryChange(for: CGSize.self) { proxy in proxy.size } action: { size in containerSize = size } - .scrollDisabled(isHorizontalScrollDisabled && isVerticalScrollDisabled) + .scrollDisabled(isHorizontalScrollingDisabled && isVerticalScrollingDisabled) + } + + private var allowedAxes: Axis.Set { + if isHorizontalScrollingDisabled && isVerticalScrollingDisabled { + axes + } else if isHorizontalScrollingDisabled { + axes.intersection(.vertical) + } else if isVerticalScrollingDisabled { + axes.intersection(.horizontal) + } else { + axes + } } - private var isHorizontalScrollDisabled: Bool { + private var isHorizontalScrollingDisabled: Bool { guard axes.contains(.horizontal) else { return true } return contentSize.width <= containerSize.width } - private var isVerticalScrollDisabled: Bool { + private var isVerticalScrollingDisabled: Bool { guard axes.contains(.vertical) else { return true } return contentSize.height <= containerSize.height } } + +@available(macOS 15.0, *) +#Preview( + "AutoScrollView", + traits: .sizeThatFitsLayout +) { + AutoScrollView { + Color.red + .frame(height: 300) + } + .luminareContentMargins(.vertical, 50) + .frame(width: 100, height: 300) +} diff --git a/Sources/Luminare/Components/LuminareList.swift b/Sources/Luminare/Components/LuminareList.swift index 80691f5..56f1803 100644 --- a/Sources/Luminare/Components/LuminareList.swift +++ b/Sources/Luminare/Components/LuminareList.swift @@ -54,13 +54,14 @@ public struct LuminareList: View @Environment(\.luminareClickedOutside) private var luminareClickedOutside @Environment(\.luminareTint) private var tint @Environment(\.luminareAnimation) private var animation - @Environment(\.luminareListContentMarginsTop) private var marginsTop - @Environment(\.luminareListContentMarginsBottom) private var marginsBottom + @Environment(\.luminareContentMarginsTop) private var contentMarginsTop + @Environment(\.luminareContentMarginsLeading) private var contentMarginsLeading + @Environment(\.luminareContentMarginsBottom) private var contentMarginsBottom + @Environment(\.luminareContentMarginsTrailing) private var contentMarginsTrailing @Environment(\.luminareListItemHeight) private var itemHeight @Environment(\.luminareListFixedHeightUntil) private var fixedHeight @Environment(\.luminareListRoundedTopCornerBehavior) private var topCorner - @Environment(\.luminareListRoundedBottomCornerBehavior) private - var bottomCorner + @Environment(\.luminareListRoundedBottomCornerBehavior) private var bottomCorner // MARK: Fields @@ -128,9 +129,9 @@ public struct LuminareList: View emptyView() } else { List(selection: $selection) { - if marginsTop > 0 { + if contentMarginsTop > 0 { Spacer() - .frame(height: marginsTop) + .frame(height: contentMarginsTop) } ForEach($items, id: keyPath) { item in @@ -184,11 +185,13 @@ public struct LuminareList: View .listRowSeparator(.hidden) .listRowInsets(.init()) .padding(.horizontal, -10) + .padding(.leading, contentMarginsLeading) + .padding(.trailing, contentMarginsTrailing) .transition(.slide) - if marginsBottom > 0 { + if contentMarginsBottom > 0 { Spacer() - .frame(height: marginsBottom) + .frame(height: contentMarginsBottom) } } .listStyle(.plain) @@ -237,7 +240,8 @@ public struct LuminareList: View } private var totalHeight: CGFloat { - CGFloat(max(1, items.count)) * itemHeight + marginsTop + marginsBottom + let margins = contentMarginsTop + contentMarginsBottom + return CGFloat(max(1, items.count)) * itemHeight + margins } private var hasFixedHeight: Bool { diff --git a/Sources/Luminare/Main Window/LuminareView.swift b/Sources/Luminare/Main Window/LuminareView.swift index 4b24816..3a124f7 100644 --- a/Sources/Luminare/Main Window/LuminareView.swift +++ b/Sources/Luminare/Main Window/LuminareView.swift @@ -17,8 +17,10 @@ public struct LuminareView: View where Content: View { @Environment(\.luminareTint) private var tint @Environment(\.luminareWindow) private var window - @Environment(\.luminareWindowMinFrame) private var minFrame - @Environment(\.luminareWindowMaxFrame) private var maxFrame + @Environment(\.luminareWindowMinWidth) private var minWidth + @Environment(\.luminareWindowMaxWidth) private var maxWidth + @Environment(\.luminareWindowMinHeight) private var minHeight + @Environment(\.luminareWindowMaxHeight) private var maxHeight // MARK: Fields @@ -42,8 +44,8 @@ public struct LuminareView: View where Content: View { } } .frame( - minWidth: minFrame.width, maxWidth: maxFrame.width, - minHeight: minFrame.height, maxHeight: maxFrame.height, + minWidth: minWidth, maxWidth: maxWidth, + minHeight: minHeight, maxHeight: maxHeight, alignment: .leading ) .focusable(false) diff --git a/Sources/Luminare/Main Window/LuminareWindow.swift b/Sources/Luminare/Main Window/LuminareWindow.swift index c90f08e..41154b8 100644 --- a/Sources/Luminare/Main Window/LuminareWindow.swift +++ b/Sources/Luminare/Main Window/LuminareWindow.swift @@ -34,8 +34,7 @@ public class LuminareWindow: NSWindow { let view = NSHostingView( rootView: LuminareView(content: content) .environment(\.luminareWindow, self) - .environment(\.luminareWindowMinFrame, minFrame) - .environment(\.luminareWindowMaxFrame, maxFrame) + .luminareWindowFrame(min: minFrame, max: maxFrame) ) contentView = view diff --git a/Sources/Luminare/Main Window/Sidebar/LuminareSidebar.swift b/Sources/Luminare/Main Window/Sidebar/LuminareSidebar.swift index b9f6c6d..2ecc31b 100644 --- a/Sources/Luminare/Main Window/Sidebar/LuminareSidebar.swift +++ b/Sources/Luminare/Main Window/Sidebar/LuminareSidebar.swift @@ -9,6 +9,10 @@ import SwiftUI /// A stylized sidebar for ``LuminareWindow``. public struct LuminareSidebar: View where Content: View { + @Environment(\.luminareContentMarginsTop) private var contentMarginsTop + @Environment(\.luminareContentMarginsBottom) private var contentMarginsBottom + @Environment(\.luminareSidebarOverflow) private var overflow + @ViewBuilder private var content: () -> Content /// Initializes a ``LuminareSidebar``. @@ -21,48 +25,40 @@ public struct LuminareSidebar: View where Content: View { } public var body: some View { - if #available(macOS 14.0, *) { - let overflow: CGFloat = 50 - - AutoScrollView(.vertical) { - VStack(spacing: 24) { - content() - } - .padding(.bottom, 12) - } - .scrollIndicators(.never) - .scrollContentBackground(.hidden) - .padding(.horizontal, 12) - .frame(maxHeight: .infinity, alignment: .top) - .padding(.top, -overflow) - .contentMargins(.top, overflow) - .mask { - VStack(spacing: 0) { - LinearGradient( - colors: [.clear, .white], - startPoint: .top, - endPoint: .bottom - ) - .frame(height: overflow) - - Color.white - } - .padding(.top, -overflow) + AutoScrollView(.vertical) { + VStack(spacing: 24) { + content() } - - .luminareBackground() - } else { - AutoScrollView(.vertical) { - VStack(spacing: 24) { - content() - } + .padding(.bottom, 12) + } + .luminareContentMargins(.top, overflow + contentMarginsTop) + .luminareContentMargins(.bottom, overflow + contentMarginsBottom) + .scrollIndicators(.never) + .scrollContentBackground(.hidden) + .padding(.horizontal, 12) + .frame(maxHeight: .infinity, alignment: .top) + .padding(.top, -overflow) + .mask { + VStack(spacing: 0) { + LinearGradient( + colors: [.clear, .white], + startPoint: .top, + endPoint: .bottom + ) + .frame(height: overflow) + + Color.white + + LinearGradient( + colors: [.clear, .white], + startPoint: .top, + endPoint: .bottom + ) + .frame(height: overflow) } - .scrollIndicators(.never) - .scrollContentBackground(.hidden) - .padding(.horizontal, 12) - .frame(maxHeight: .infinity, alignment: .top) - .luminareBackground() + .padding(.vertical, -overflow) } + .luminareBackground() } } diff --git a/Sources/Luminare/Utilities/EnvironmentValues.swift b/Sources/Luminare/Utilities/EnvironmentValues.swift index 19a3fcf..7f66540 100644 --- a/Sources/Luminare/Utilities/EnvironmentValues.swift +++ b/Sources/Luminare/Utilities/EnvironmentValues.swift @@ -27,9 +27,13 @@ public extension EnvironmentValues { // MARK: Window @Entry var luminareWindow: NSWindow? - @Entry var luminareWindowMinFrame: CGSize = .init(width: 100, height: 100) - @Entry var luminareWindowMaxFrame: CGSize = .init(width: CGFloat.infinity, height: CGFloat.infinity) + @Entry var luminareSidebarOverflow: CGFloat = 50 @Entry var luminareClickedOutside: Bool = false + + @Entry var luminareWindowMinWidth: CGFloat = 100 + @Entry var luminareWindowMaxWidth: CGFloat = .infinity + @Entry var luminareWindowMinHeight: CGFloat = 100 + @Entry var luminareWindowMaxHeight: CGFloat = .infinity } // MARK: - Modals @@ -43,19 +47,16 @@ public extension EnvironmentValues { // MARK: Sheet @Entry var luminareSheetCornerRadii: RectangleCornerRadii = .init(12) + @Entry var luminareSheetPresentation: LuminareSheetPresentation = .windowCenter @Entry var luminareSheetIsMovableByWindowBackground: Bool = false @Entry var luminareSheetClosesOnDefocus: Bool = false // MARK: Popup + @Entry var luminarePopupCornerRadii: RectangleCornerRadii = .init(12) + @Entry var luminarePopupPadding: CGFloat = 12 - @Entry var luminarePopupCornerRadii: RectangleCornerRadii = .init( - topLeading: 12, - bottomLeading: 12, - bottomTrailing: 12, - topTrailing: 12 - ) // MARK: Color Picker @@ -69,25 +70,29 @@ public extension EnvironmentValues { // MARK: General @Entry var luminareCornerRadii: RectangleCornerRadii = .init(12) + @Entry var luminareMinHeight: CGFloat = 34 @Entry var luminareHorizontalPadding: CGFloat = 8 @Entry var luminareIsBordered: Bool = true @Entry var luminareHasBackground: Bool = true @Entry var luminareHasDividers: Bool = true + + @Entry var luminareAspectRatio: CGFloat? + @Entry var luminareAspectRatioContentMode: ContentMode? = .fit + @Entry var luminareAspectRatioHasFixedHeight: Bool = true + + @Entry var luminareContentMarginsTop: CGFloat = 0 + @Entry var luminareContentMarginsLeading: CGFloat = 0 + @Entry var luminareContentMarginsBottom: CGFloat = 0 + @Entry var luminareContentMarginsTrailing: CGFloat = 0 // MARK: Button Styles - @Entry var luminareAspectRatio: ( - aspectRatio: CGFloat?, - contentMode: ContentMode, - hasFixedHeight: Bool - )? = (nil, .fit, true) - - @Entry var luminareButtonMaterial: Material? = nil @Entry var luminareButtonCornerRadii: RectangleCornerRadii = .init(2) - @Entry var luminareButtonHighlightOnHover: Bool = true - @Entry var luminareCompactButtonCornerRadii: RectangleCornerRadii = .init(8) + + @Entry var luminareButtonMaterial: Material? = nil + @Entry var luminareButtonHighlightOnHover: Bool = true // MARK: Form Style @@ -130,12 +135,12 @@ public extension EnvironmentValues { // MARK: List - @Entry var luminareListContentMarginsTop: CGFloat = 0 - @Entry var luminareListContentMarginsBottom: CGFloat = 0 @Entry var luminareListItemCornerRadii: RectangleCornerRadii = .init(2) + @Entry var luminareListItemHeight: CGFloat = 50 @Entry var luminareListItemHighlightOnHover: Bool = true @Entry var luminareListFixedHeightUntil: CGFloat? = nil + @Entry var luminareListRoundedTopCornerBehavior: LuminareListRoundedCornerBehavior = .never @Entry var luminareListRoundedBottomCornerBehavior: LuminareListRoundedCornerBehavior = .never diff --git a/Sources/Luminare/Utilities/Extensions/EdgeInsets+Extensions.swift b/Sources/Luminare/Utilities/Extensions/EdgeInsets+Extensions.swift new file mode 100644 index 0000000..0576e74 --- /dev/null +++ b/Sources/Luminare/Utilities/Extensions/EdgeInsets+Extensions.swift @@ -0,0 +1,42 @@ +// +// EdgeInsets+Extensions.swift +// Luminare +// +// Created by KrLite on 2024/12/19. +// + + +import SwiftUI + +public extension EdgeInsets { + static var zero: Self { .init(0) } + + init(_ length: CGFloat) { + self.init( + top: length, + leading: length, + bottom: length, + trailing: length + ) + } + + init(_ edges: Edge.Set, _ length: CGFloat) { + self.init( + top: edges.contains(.top) ? length : 0, + leading: edges.contains(.leading) ? length : 0, + bottom: edges.contains(.bottom) ? length : 0, + trailing: edges.contains(.trailing) ? length : 0 + ) + } +} + +extension EdgeInsets { + func map(_ transform: @escaping (CGFloat) -> CGFloat) -> Self { + .init( + top: transform(top), + leading: transform(leading), + bottom: transform(bottom), + trailing: transform(trailing) + ) + } +} diff --git a/Sources/Luminare/Utilities/Extensions/View+Extensions.swift b/Sources/Luminare/Utilities/Extensions/View+Extensions.swift index 8a3268d..51195cf 100644 --- a/Sources/Luminare/Utilities/Extensions/View+Extensions.swift +++ b/Sources/Luminare/Utilities/Extensions/View+Extensions.swift @@ -127,21 +127,29 @@ public extension View { } @ViewBuilder func luminareWindowFrame( - minFrame: CGSize = .init(width: 100, height: 100), - maxFrame: CGSize = .init(width: CGFloat.infinity, height: CGFloat.infinity) + min: CGSize? = nil, + max: CGSize? = nil ) -> some View { - environment(\.luminareWindowMinFrame, minFrame) - .environment(\.luminareWindowMaxFrame, maxFrame) + let view = self + if let min { view.luminareWindowFrame(minWidth: min.width, minHeight: min.height) } + if let max { view.luminareWindowFrame(maxWidth: max.width, maxHeight: max.height) } + view } @ViewBuilder func luminareWindowFrame( - minWidth: CGFloat = 100, maxWidth: CGFloat = .infinity, - minHeight: CGFloat = 100, maxHeight: CGFloat = .infinity + minWidth: CGFloat? = nil, maxWidth: CGFloat? = nil, + minHeight: CGFloat? = nil, maxHeight: CGFloat? = nil ) -> some View { - luminareWindowFrame( - minFrame: .init(width: minWidth, height: minHeight), - maxFrame: .init(width: maxWidth, height: maxHeight) - ) + let view = self + if let minWidth { view.environment(\.luminareWindowMinWidth, minWidth) } + if let maxWidth { view.environment(\.luminareWindowMaxWidth, maxWidth) } + if let minHeight { view.environment(\.luminareWindowMinHeight, minHeight) } + if let maxHeight { view.environment(\.luminareWindowMaxHeight, maxHeight) } + view + } + + @ViewBuilder func luminareSizebarOverflow(_ overflow: CGFloat) -> some View { + environment(\.luminareSidebarOverflow, overflow) } } @@ -160,11 +168,11 @@ public extension View { // MARK: Sheet - @ViewBuilder func luminareSheetCornerRadii(_ radii: RectangleCornerRadii = .init(12)) -> some View { + @ViewBuilder func luminareSheetCornerRadii(_ radii: RectangleCornerRadii) -> some View { environment(\.luminareSheetCornerRadii, radii) } - @ViewBuilder func luminareSheetCornerRadius(_ radius: CGFloat = 12) -> some View { + @ViewBuilder func luminareSheetCornerRadius(_ radius: CGFloat) -> some View { luminareSheetCornerRadii(.init(radius)) } @@ -186,222 +194,251 @@ public extension View { environment(\.luminarePopupPadding, padding) } - @ViewBuilder func luminarePopupCornerRadii(_ radii: RectangleCornerRadii = .init(topLeading: 12, bottomLeading: 12, bottomTrailing: 12, topTrailing: 12)) -> some View { + @ViewBuilder func luminarePopupCornerRadii(_ radii: RectangleCornerRadii) -> some View { environment(\.luminarePopupCornerRadii, radii) } // MARK: Color Picker - @ViewBuilder func luminareColorPickerControls(hasCancel: Bool = false, hasDone: Bool = false) -> some View { - environment(\.luminareColorPickerHasCancel, hasCancel) - .environment(\.luminareColorPickerHasDone, hasDone) + @ViewBuilder func luminareColorPickerControls(hasCancel: Bool? = nil, hasDone: Bool? = nil) -> some View { + let view = self + if let hasCancel { view.environment(\.luminareColorPickerHasCancel, hasCancel) } + if let hasDone { view.environment(\.luminareColorPickerHasDone, hasDone) } + view } } public extension View { // MARK: General - @ViewBuilder func luminareCornerRadii(_ radii: RectangleCornerRadii = .init(12)) -> some View { + @ViewBuilder func luminareCornerRadii(_ radii: RectangleCornerRadii) -> some View { environment(\.luminareCornerRadii, radii) } - @ViewBuilder func luminareCornerRadius(_ radius: CGFloat = 12) -> some View { + @ViewBuilder func luminareCornerRadius(_ radius: CGFloat) -> some View { luminareCornerRadii(.init(radius)) } - @ViewBuilder func luminareMinHeight(_ height: CGFloat = 34) -> some View { + @ViewBuilder func luminareMinHeight(_ height: CGFloat) -> some View { environment(\.luminareMinHeight, height) } - @ViewBuilder func luminareHorizontalPadding(_ padding: CGFloat = 8) -> some View { + @ViewBuilder func luminareHorizontalPadding(_ padding: CGFloat) -> some View { environment(\.luminareHorizontalPadding, padding) } - @ViewBuilder func luminareBordered(_ bordered: Bool = true) -> some View { + @ViewBuilder func luminareBordered(_ bordered: Bool) -> some View { environment(\.luminareIsBordered, bordered) } - @ViewBuilder func luminareHasBackground(_ hasBackground: Bool = true) -> some View { + @ViewBuilder func luminareHasBackground(_ hasBackground: Bool) -> some View { environment(\.luminareHasBackground, hasBackground) } - @ViewBuilder func luminareHasDividers(_ hasDividers: Bool = true) -> some View { + @ViewBuilder func luminareHasDividers(_ hasDividers: Bool) -> some View { environment(\.luminareHasDividers, hasDividers) } + + @ViewBuilder func luminareAspectRatio(unapplying: Bool) -> some View { + if unapplying { + environment(\.luminareAspectRatioContentMode, nil) + } else { + self + } + } + + @ViewBuilder func luminareAspectRatio( + _ aspectRatio: CGFloat? = nil, contentMode: ContentMode, hasFixedHeight: Bool? = nil + ) -> some View { + let view = self + view.environment(\.luminareAspectRatio, aspectRatio) + view.environment(\.luminareAspectRatioContentMode, contentMode) + if let hasFixedHeight { view.environment(\.luminareAspectRatioHasFixedHeight, hasFixedHeight) } + view + } + + @ViewBuilder func luminareAspectRatio( + _ aspectRatio: CGSize, contentMode: ContentMode, hasFixedHeight: Bool? = nil + ) -> some View { + luminareAspectRatio( + aspectRatio.width / aspectRatio.height, + contentMode: contentMode, + hasFixedHeight: hasFixedHeight + ) + } + + @ViewBuilder func luminareContentMargins(_ insets: EdgeInsets) -> some View { + environment(\.luminareContentMarginsTop, insets.top) + .environment(\.luminareContentMarginsLeading, insets.leading) + .environment(\.luminareContentMarginsBottom, insets.bottom) + .environment(\.luminareContentMarginsTrailing, insets.trailing) + } + + @ViewBuilder func luminareContentMargins(_ edges: Edge.Set, _ length: CGFloat) -> some View { + let view = self + if edges.contains(.top) { view.environment(\.luminareContentMarginsTop, length) } + if edges.contains(.leading) { view.environment(\.luminareContentMarginsLeading, length) } + if edges.contains(.bottom) { view.environment(\.luminareContentMarginsBottom, length) } + if edges.contains(.trailing) { view.environment(\.luminareContentMarginsTrailing, length) } + view + } + + @ViewBuilder func luminareContentMargins(_ length: CGFloat) -> some View { + luminareContentMargins(.all, length) + } // MARK: Form @available(macOS 15.0, *) - @ViewBuilder func luminareFormSpacing(_ spacing: CGFloat = 15) -> some View { + @ViewBuilder func luminareFormSpacing(_ spacing: CGFloat) -> some View { environment(\.luminareFormSpacing, spacing) } // MARK: Pane - @ViewBuilder func luminarePaneLayout(_ layout: LuminarePaneLayout = .stacked) -> some View { + @ViewBuilder func luminarePaneLayout(_ layout: LuminarePaneLayout) -> some View { environment(\.luminarePaneLayout, layout) } - @ViewBuilder func luminarePaneTitlebarHeight(_ height: CGFloat = 50) -> some View { + @ViewBuilder func luminarePaneTitlebarHeight(_ height: CGFloat) -> some View { environment(\.luminarePaneTitlebarHeight, height) } // MARK: Button Styles - @ViewBuilder func luminareAspectRatio(unapplying: Bool) -> some View { - if unapplying { - environment(\.luminareAspectRatio, nil) - } else { - luminareAspectRatio() - } - } - - @ViewBuilder func luminareAspectRatio( - _ aspectRatio: CGFloat? = nil, contentMode: ContentMode = .fit, hasFixedHeight: Bool = true - ) -> some View { - environment(\.luminareAspectRatio, (aspectRatio, contentMode, hasFixedHeight)) - } - - @ViewBuilder func luminareAspectRatio( - _ aspectRatio: CGSize, contentMode: ContentMode = .fit, hasFixedHeight: Bool = true - ) -> some View { - luminareAspectRatio( - aspectRatio.width / aspectRatio.height, contentMode: contentMode, hasFixedHeight: hasFixedHeight - ) - } - - @ViewBuilder func luminareButtonMaterial(_ material: Material? = nil) -> some View { + @ViewBuilder func luminareButtonMaterial(_ material: Material?) -> some View { environment(\.luminareButtonMaterial, material) } - @ViewBuilder func luminareButtonCornerRadii(_ radii: RectangleCornerRadii = .init(2)) -> some View { + @ViewBuilder func luminareButtonCornerRadii(_ radii: RectangleCornerRadii) -> some View { environment(\.luminareButtonCornerRadii, radii) } - @ViewBuilder func luminareButtonCornerRadius(_ radius: CGFloat = 2) -> some View { + @ViewBuilder func luminareButtonCornerRadius(_ radius: CGFloat) -> some View { luminareButtonCornerRadii(.init(radius)) } - @ViewBuilder func luminareButtonHighlightOnHover(_ highlight: Bool = true) -> some View { + @ViewBuilder func luminareButtonHighlightOnHover(_ highlight: Bool) -> some View { environment(\.luminareButtonHighlightOnHover, highlight) } - @ViewBuilder func luminareCompactButtonCornerRadii(_ radii: RectangleCornerRadii = .init(8)) -> some View { + @ViewBuilder func luminareCompactButtonCornerRadii(_ radii: RectangleCornerRadii) -> some View { environment(\.luminareCompactButtonCornerRadii, radii) } - @ViewBuilder func luminareCompactButtonCornerRadius(_ radius: CGFloat = 8) -> some View { + @ViewBuilder func luminareCompactButtonCornerRadius(_ radius: CGFloat) -> some View { luminareCompactButtonCornerRadii(.init(radius)) } // MARK: Section - @ViewBuilder func luminareSectionLayout(_ layout: LuminareSectionLayout = .stacked) -> some View { + @ViewBuilder func luminareSectionLayout(_ layout: LuminareSectionLayout) -> some View { environment(\.luminareSectionLayout, layout) } - @ViewBuilder func luminareSectionMaterial(_ material: Material? = nil) -> some View { + @ViewBuilder func luminareSectionMaterial(_ material: Material?) -> some View { environment(\.luminareSectionMaterial, material) } - @ViewBuilder func luminareSectionMaxWidth(_ maxWidth: CGFloat? = .infinity) -> some View { + @ViewBuilder func luminareSectionMaxWidth(_ maxWidth: CGFloat?) -> some View { environment(\.luminareSectionMaxWidth, maxWidth) } - @ViewBuilder func luminareSectionMasked(_ masked: Bool = false) -> some View { + @ViewBuilder func luminareSectionMasked(_ masked: Bool) -> some View { environment(\.luminareSectionIsMasked, masked) } // MARK: Compose - @ViewBuilder func luminareComposeControlSize(_ controlSize: LuminareComposeControlSize = .automatic) -> some View { + @ViewBuilder func luminareComposeControlSize(_ controlSize: LuminareComposeControlSize) -> some View { environment(\.luminareComposeControlSize, controlSize) } - @ViewBuilder func luminareComposeLayout(_ layout: LuminareComposeLayout = .regular) -> some View { + @ViewBuilder func luminareComposeLayout(_ layout: LuminareComposeLayout) -> some View { environment(\.luminareComposeLayout, layout) } - @ViewBuilder func luminareComposeStyle(_ style: LuminareComposeStyle = .automatic) -> some View { + @ViewBuilder func luminareComposeStyle(_ style: LuminareComposeStyle) -> some View { environment(\.luminareComposeStyle, style) } // MARK: Popover - @ViewBuilder func luminarePopoverTrigger(_ trigger: LuminarePopoverTrigger = .hover) -> some View { + @ViewBuilder func luminarePopoverTrigger(_ trigger: LuminarePopoverTrigger) -> some View { environment(\.luminarePopoverTrigger, trigger) } - @ViewBuilder func luminarePopoverShade(_ shade: LuminarePopoverShade = .styled) -> some View { + @ViewBuilder func luminarePopoverShade(_ shade: LuminarePopoverShade) -> some View { environment(\.luminarePopoverShade, shade) } // MARK: Stepper @available(macOS 15.0, *) - @ViewBuilder func luminareStepperAlignment(_ alignment: LuminareStepperAlignment = .trailing) -> some View { + @ViewBuilder func luminareStepperAlignment(_ alignment: LuminareStepperAlignment) -> some View { environment(\.luminareStepperAlignment, alignment) } @available(macOS 15.0, *) - @ViewBuilder func luminareStepperDirection(_ direction: LuminareStepperDirection = .horizontal) -> some View { + @ViewBuilder func luminareStepperDirection(_ direction: LuminareStepperDirection) -> some View { environment(\.luminareStepperDirection, direction) } // MARK: Compact Picker - @ViewBuilder func luminareCompactPickerStyle(_ style: LuminareCompactPickerStyle = .menu) -> some View { + @ViewBuilder func luminareCompactPickerStyle(_ style: LuminareCompactPickerStyle) -> some View { environment(\.luminareCompactPickerStyle, style) } // MARK: List - @ViewBuilder func luminareListContentMargins(_ margins: CGFloat) -> some View { - luminareListContentMargins(top: margins, bottom: margins) - } - - @ViewBuilder func luminareListContentMargins(top: CGFloat = 0, bottom: CGFloat = 0) -> some View { - environment(\.luminareListContentMarginsTop, top) - .environment(\.luminareListContentMarginsBottom, bottom) - } - - @ViewBuilder func luminareListItemCornerRadii(_ radii: RectangleCornerRadii = .init(2)) -> some View { + @ViewBuilder func luminareListItemCornerRadii(_ radii: RectangleCornerRadii) -> some View { environment(\.luminareListItemCornerRadii, radii) } - @ViewBuilder func luminareListItemCornerRadius(_ radius: CGFloat = 2) -> some View { + @ViewBuilder func luminareListItemCornerRadius(_ radius: CGFloat) -> some View { luminareListItemCornerRadii(.init(radius)) } - @ViewBuilder func luminareListItemHeight(_ height: CGFloat = 50) -> some View { + @ViewBuilder func luminareListItemHeight(_ height: CGFloat) -> some View { environment(\.luminareListItemHeight, height) } - @ViewBuilder func luminareListItemHighlightOnHover(_ highlight: Bool = true) -> some View { + @ViewBuilder func luminareListItemHighlightOnHover(_ highlight: Bool) -> some View { environment(\.luminareListItemHighlightOnHover, highlight) } - @ViewBuilder func luminareListFixedHeight(until height: CGFloat? = nil) -> some View { + @ViewBuilder func luminareListFixedHeight(until height: CGFloat?) -> some View { environment(\.luminareListFixedHeightUntil, height) } - @ViewBuilder func luminareListRoundedCorner(top: LuminareListRoundedCornerBehavior = .never, bottom: LuminareListRoundedCornerBehavior = .never) -> some View { - environment(\.luminareListRoundedTopCornerBehavior, top) - .environment(\.luminareListRoundedBottomCornerBehavior, bottom) + @ViewBuilder func luminareListRoundedCorner( + top: LuminareListRoundedCornerBehavior? = nil, + bottom: LuminareListRoundedCornerBehavior? = nil + ) -> some View { + let view = self + if let top { view.environment(\.luminareListRoundedTopCornerBehavior, top) } + if let bottom { view.environment(\.luminareListRoundedBottomCornerBehavior, bottom) } + view } - @ViewBuilder func luminareListRoundedCorner(_ all: LuminareListRoundedCornerBehavior = .never) -> some View { + @ViewBuilder func luminareListRoundedCorner(_ all: LuminareListRoundedCornerBehavior) -> some View { luminareListRoundedCorner(top: all, bottom: all) } // MARK: Picker - @ViewBuilder func luminarePickerRoundedCorner(top: LuminarePickerRoundedCornerBehavior = .never, bottom: LuminarePickerRoundedCornerBehavior = .never) -> some View { - environment(\.luminarePickerRoundedTopCornerBehavior, top) - .environment(\.luminarePickerRoundedBottomCornerBehavior, bottom) + @ViewBuilder func luminarePickerRoundedCorner( + top: LuminarePickerRoundedCornerBehavior? = nil, + bottom: LuminarePickerRoundedCornerBehavior? = nil + ) -> some View { + let view = self + if let top { view.environment(\.luminarePickerRoundedTopCornerBehavior, top) } + if let bottom { view.environment(\.luminarePickerRoundedBottomCornerBehavior, bottom) } + view } - @ViewBuilder func luminarePickerRoundedCorner(_ all: LuminarePickerRoundedCornerBehavior = .never) -> some View { + @ViewBuilder func luminarePickerRoundedCorner(_ all: LuminarePickerRoundedCornerBehavior) -> some View { luminarePickerRoundedCorner(top: all, bottom: all) } } diff --git a/Sources/Luminare/Utilities/LuminareManagerProtocol.swift b/Sources/Luminare/Utilities/LuminareManagerProtocol.swift new file mode 100644 index 0000000..31268a6 --- /dev/null +++ b/Sources/Luminare/Utilities/LuminareManagerProtocol.swift @@ -0,0 +1,73 @@ +// +// LuminareManagerProtocol.swift +// Luminare +// +// Created by KrLite on 2024/12/19. +// + + +import SwiftUI + +public protocol LuminareManagerProtocol: View { + var luminare: LuminareWindow? { get set } + var isVisible: Bool { get } + + var blurRadius: CGFloat? { get } + var minFrame: CGSize { get } + var maxFrame: CGSize { get } + + mutating func show() + mutating func close() + mutating func toggle() +} + +public extension LuminareManagerProtocol { + var isVisible: Bool { + if let luminare { + luminare.isVisible + } else { + false + } + } + + var blurRadius: CGFloat? { + nil + } + + var minFrame: CGSize { + .init(width: 100, height: 100) + } + + var maxFrame: CGSize { + .init(width: CGFloat.infinity, height: CGFloat.infinity) + } +} + +public extension LuminareManagerProtocol { + mutating func show() { + if luminare == nil { + let body = self.body + luminare = LuminareWindow( + blurRadius: blurRadius, + minFrame: minFrame, + maxFrame: maxFrame + ) { body } + luminare?.center() + } + + luminare?.show() + } + + mutating func close() { + luminare?.close() + luminare = nil + } + + mutating func toggle() { + if isVisible { + close() + } else { + show() + } + } +}