Initial commit
This commit is contained in:
13
WhiteNights/Utils/Color+Extensions.swift
Normal file
13
WhiteNights/Utils/Color+Extensions.swift
Normal file
@ -0,0 +1,13 @@
|
||||
import SwiftUI
|
||||
|
||||
extension Color {
|
||||
init(hex: UInt, alpha: Double = 1) {
|
||||
self.init(
|
||||
.sRGB,
|
||||
red: Double((hex >> 16) & 0xFF)/255,
|
||||
green: Double((hex >> 8) & 0xFF)/255,
|
||||
blue: Double(hex & 0xFF)/255,
|
||||
opacity: alpha
|
||||
)
|
||||
}
|
||||
}
|
71
WhiteNights/Utils/MarqueeText.swift
Normal file
71
WhiteNights/Utils/MarqueeText.swift
Normal file
@ -0,0 +1,71 @@
|
||||
import SwiftUI
|
||||
|
||||
struct MarqueeText: View {
|
||||
let text: String
|
||||
let font: Font
|
||||
let foregroundColor: Color
|
||||
private let speed: CGFloat = 10 // пикселей в секунду
|
||||
|
||||
@State private var textWidth: CGFloat = 0
|
||||
@State private var containerWidth: CGFloat = 0
|
||||
@State private var offset: CGFloat = 0
|
||||
@State private var animationStarted = false
|
||||
|
||||
var body: some View {
|
||||
GeometryReader { geo in
|
||||
Text(text)
|
||||
.font(font)
|
||||
.foregroundColor(foregroundColor)
|
||||
.lineLimit(1)
|
||||
.fixedSize() // важно, чтобы не обрезался
|
||||
.background(WidthGetter())
|
||||
.offset(x: offset)
|
||||
.onAppear {
|
||||
containerWidth = geo.size.width
|
||||
if textWidth > containerWidth, !animationStarted {
|
||||
animationStarted = true
|
||||
let distance = textWidth - containerWidth
|
||||
let duration = Double(distance / speed)
|
||||
withAnimation(.linear(duration: duration).repeatForever(autoreverses: true)) {
|
||||
offset = -distance
|
||||
}
|
||||
}
|
||||
}
|
||||
.clipped()
|
||||
}
|
||||
.frame(height: lineHeight(for: font))
|
||||
.onPreferenceChange(WidthKey.self) { width in
|
||||
textWidth = width
|
||||
}
|
||||
}
|
||||
|
||||
private func lineHeight(for font: Font) -> CGFloat {
|
||||
switch font {
|
||||
case .largeTitle: return 34
|
||||
case .title: return 28
|
||||
case .title2: return 22
|
||||
case .title3: return 20
|
||||
case .headline: return 17
|
||||
case .body: return 17
|
||||
case .callout: return 16
|
||||
case .subheadline: return 15
|
||||
case .caption: return 13
|
||||
case .caption2: return 12
|
||||
default: return 17
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: — Helpers для измерения ширины текста
|
||||
private struct WidthKey: PreferenceKey {
|
||||
static var defaultValue: CGFloat = 0
|
||||
static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) { value = nextValue() }
|
||||
}
|
||||
|
||||
private struct WidthGetter: View {
|
||||
var body: some View {
|
||||
GeometryReader { geo in
|
||||
Color.clear.preference(key: WidthKey.self, value: geo.size.width)
|
||||
}
|
||||
}
|
||||
}
|
17
WhiteNights/Utils/View+CornerRadius.swift
Normal file
17
WhiteNights/Utils/View+CornerRadius.swift
Normal file
@ -0,0 +1,17 @@
|
||||
import SwiftUI
|
||||
|
||||
extension View {
|
||||
func cornerRadius(_ radius: CGFloat, corners: UIRectCorner) -> some View {
|
||||
clipShape(RoundedCorner(radius: radius, corners: corners))
|
||||
}
|
||||
}
|
||||
|
||||
private struct RoundedCorner: Shape {
|
||||
var radius: CGFloat = .infinity
|
||||
var corners: UIRectCorner = .allCorners
|
||||
|
||||
func path(in rect: CGRect) -> Path {
|
||||
let path = UIBezierPath(roundedRect: rect, byRoundingCorners: corners, cornerRadii: CGSize(width: radius, height: radius))
|
||||
return Path(path.cgPath)
|
||||
}
|
||||
}
|
39
WhiteNights/Utils/View+Extensions.swift
Normal file
39
WhiteNights/Utils/View+Extensions.swift
Normal file
@ -0,0 +1,39 @@
|
||||
import SwiftUI
|
||||
|
||||
struct BlockStyle: ViewModifier {
|
||||
// Добавляем свойство для хранения радиуса скругления
|
||||
let cornerRadius: CGFloat
|
||||
|
||||
func body(content: Content) -> some View {
|
||||
content
|
||||
.background(
|
||||
ZStack {
|
||||
Color(hex: 0x806C59)
|
||||
LinearGradient(
|
||||
stops: [
|
||||
.init(color: .white.opacity(0.0), location: 0.0871),
|
||||
.init(color: .white.opacity(0.16), location: 0.6969)
|
||||
],
|
||||
startPoint: .bottomLeading,
|
||||
endPoint: .topTrailing
|
||||
)
|
||||
}
|
||||
.cornerRadius(cornerRadius) // Применяем скругление к фону
|
||||
)
|
||||
.shadow(
|
||||
color: Color.black.opacity(0.10),
|
||||
radius: 8,
|
||||
x: 0,
|
||||
y: 2
|
||||
)
|
||||
// Применяем скругление к содержимому (опционально, но лучше для теней)
|
||||
.cornerRadius(cornerRadius)
|
||||
}
|
||||
}
|
||||
|
||||
extension View {
|
||||
// Изменяем расширение, чтобы оно принимало параметр cornerRadius
|
||||
func blockStyle(cornerRadius: CGFloat) -> some View {
|
||||
modifier(BlockStyle(cornerRadius: cornerRadius))
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user