-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
50 changed files
with
295 additions
and
28 deletions.
There are no files selected for viewing
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file modified
0
android/src/main/java/com/meshgradient/MeshGradientViewManager.kt
100644 → 100755
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
// | ||
// MeshGradientView.swift | ||
// reactnativemeshgradient | ||
// | ||
// Created by wilmxre on 27.07.2024. | ||
// | ||
|
||
import ExpoModulesCore | ||
import SwiftUI | ||
|
||
@available(iOS 16.0, *) | ||
struct MeshGradientView: View { | ||
var meshWidth: Int | ||
var meshHeight: Int | ||
var points: [SIMD2<Float>] | ||
var primaryColors: [Color] | ||
var secondaryColors: [Color] | ||
var background: Color = .clear | ||
var smoothsColors: Bool | ||
var colorSpace: Gradient.ColorSpace | ||
var borderRadius: CGFloat | ||
var isAnimated: Bool | ||
var animationDuration: TimeInterval | ||
var animationType: String | ||
|
||
@State private var isAnimating = false | ||
|
||
@State var t: Float = 0.0 | ||
@State var timer: Timer? | ||
|
||
var body: some View { | ||
ZStack { | ||
if #available(iOS 18.0, *) { | ||
MeshGradient( | ||
width: meshWidth, | ||
height: meshHeight, | ||
points: animationType == "sine" | ||
? [ | ||
.init(0, 0), .init(0.5, 0), .init(1, 0), | ||
[ | ||
sinInRange( | ||
-0.8...(-0.2), offset: 0.439, timeScale: 0.342, t: t), | ||
sinInRange(0.3...0.7, offset: 3.42, timeScale: 0.984, t: t), | ||
], | ||
[ | ||
sinInRange(0.1...0.8, offset: 0.239, timeScale: 0.084, t: t), | ||
sinInRange(0.2...0.8, offset: 5.21, timeScale: 0.242, t: t), | ||
], | ||
[ | ||
sinInRange(1.0...1.5, offset: 0.939, timeScale: 0.084, t: t), | ||
sinInRange(0.4...0.8, offset: 0.25, timeScale: 0.642, t: t), | ||
], | ||
[ | ||
sinInRange(-0.8...0.0, offset: 1.439, timeScale: 0.442, t: t), | ||
sinInRange(1.4...1.9, offset: 3.42, timeScale: 0.984, t: t), | ||
], | ||
[ | ||
sinInRange(0.3...0.6, offset: 0.339, timeScale: 0.784, t: t), | ||
sinInRange(1.0...1.2, offset: 1.22, timeScale: 0.772, t: t), | ||
], | ||
[ | ||
sinInRange(1.0...1.5, offset: 0.939, timeScale: 0.056, t: t), | ||
sinInRange(1.3...1.7, offset: 0.47, timeScale: 0.342, t: t), | ||
], | ||
] : points, | ||
colors: isAnimating ? secondaryColors : primaryColors, | ||
background: background, | ||
smoothsColors: smoothsColors, | ||
colorSpace: colorSpace | ||
) | ||
.cornerRadius(borderRadius) | ||
.onAppear { | ||
if isAnimated { | ||
animationType == "sine" | ||
? timer = Timer.scheduledTimer( | ||
withTimeInterval: 0.01, repeats: true | ||
) { _ in | ||
t += 0.02 | ||
} | ||
: withAnimation( | ||
.easeInOut(duration: animationDuration).repeatForever( | ||
autoreverses: true) | ||
) { | ||
isAnimating.toggle() | ||
} | ||
} | ||
} | ||
} else { | ||
Text("MeshGradient is not available on this version of iOS.") | ||
} | ||
} | ||
|
||
} | ||
} | ||
|
||
func sinInRange( | ||
_ range: ClosedRange<Float>, offset: Float, timeScale: Float, t: Float | ||
) -> Float { | ||
let amplitude = (range.upperBound - range.lowerBound) / 2 | ||
let midPoint = (range.upperBound + range.lowerBound) / 2 | ||
return midPoint + amplitude * sin(timeScale * t + offset) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,25 @@ | ||
// | ||
// MeshGradient.m | ||
// reactnativemeshgradient | ||
// | ||
// Created by wilmxore on 27.07.2024. | ||
// | ||
|
||
#import <React/RCTViewManager.h> | ||
|
||
@interface RCT_EXTERN_MODULE(MeshGradientViewManager, RCTViewManager) | ||
|
||
RCT_EXPORT_VIEW_PROPERTY(color, NSString) | ||
RCT_EXPORT_VIEW_PROPERTY(isAnimated, BOOL) | ||
RCT_EXPORT_VIEW_PROPERTY(meshWidth, NSInteger) | ||
RCT_EXPORT_VIEW_PROPERTY(meshHeight, NSInteger) | ||
RCT_EXPORT_VIEW_PROPERTY(points, NSArray) | ||
RCT_EXPORT_VIEW_PROPERTY(primaryColors, NSArray) | ||
RCT_EXPORT_VIEW_PROPERTY(secondaryColors, NSArray) | ||
RCT_EXPORT_VIEW_PROPERTY(background, NSString) | ||
RCT_EXPORT_VIEW_PROPERTY(smoothsColors, BOOL) | ||
RCT_EXPORT_VIEW_PROPERTY(colorSpace, NSString) | ||
RCT_EXPORT_VIEW_PROPERTY(borderRadius, CGFloat) | ||
RCT_EXPORT_VIEW_PROPERTY(animationDuration, NSInteger) | ||
RCT_EXPORT_VIEW_PROPERTY(animationType, NSString) | ||
|
||
@end | ||
@end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,36 +1,173 @@ | ||
// | ||
// MeshGradientViewManager.swift | ||
// reactnativemeshgradient | ||
// | ||
// Created by wilmxre on 27.07.2024. | ||
// | ||
|
||
import ExpoModulesCore | ||
import React | ||
import SwiftUI | ||
|
||
@objc(MeshGradientViewManager) | ||
class MeshGradientViewManager: RCTViewManager { | ||
|
||
override func view() -> (MeshGradientView) { | ||
return MeshGradientView() | ||
override func view() -> UIView! { | ||
if #available(iOS 16.0, *) { | ||
return MeshGradientUIView() | ||
} else { | ||
return UIView() | ||
} | ||
} | ||
|
||
@objc override static func requiresMainQueueSetup() -> Bool { | ||
return false | ||
override static func requiresMainQueueSetup() -> Bool { | ||
return true | ||
} | ||
} | ||
|
||
class MeshGradientView : UIView { | ||
@available(iOS 16.0, *) | ||
class MeshGradientUIView: UIView { | ||
private var hostingController: UIHostingController<MeshGradientView>? | ||
|
||
@objc var color: String = "" { | ||
@objc var meshWidth: Int = 3 { | ||
didSet { | ||
self.backgroundColor = hexStringToUIColor(hexColor: color) | ||
updateView() | ||
} | ||
} | ||
|
||
func hexStringToUIColor(hexColor: String) -> UIColor { | ||
let stringScanner = Scanner(string: hexColor) | ||
@objc var meshHeight: Int = 3 { | ||
didSet { | ||
updateView() | ||
} | ||
} | ||
|
||
if(hexColor.hasPrefix("#")) { | ||
stringScanner.scanLocation = 1 | ||
@objc var points: [[NSNumber]] = [] { | ||
didSet { | ||
updateView() | ||
} | ||
var color: UInt32 = 0 | ||
stringScanner.scanHexInt32(&color) | ||
} | ||
|
||
let r = CGFloat(Int(color >> 16) & 0x000000FF) | ||
let g = CGFloat(Int(color >> 8) & 0x000000FF) | ||
let b = CGFloat(Int(color) & 0x000000FF) | ||
@objc var primaryColors: [NSString] = [] { | ||
didSet { | ||
updateView() | ||
} | ||
} | ||
|
||
return UIColor(red: r / 255.0, green: g / 255.0, blue: b / 255.0, alpha: 1) | ||
@objc var secondaryColors: [NSString] = [] { | ||
didSet { | ||
updateView() | ||
} | ||
} | ||
} | ||
|
||
@objc var background: NSString = "" { | ||
didSet { | ||
updateView() | ||
} | ||
} | ||
|
||
@objc var smoothsColors: Bool = true { | ||
didSet { | ||
updateView() | ||
} | ||
} | ||
|
||
@objc var colorSpace: NSString = "device" { | ||
didSet { | ||
updateView() | ||
} | ||
} | ||
|
||
@objc var isAnimated: Bool = false { | ||
didSet { | ||
updateView() | ||
} | ||
} | ||
|
||
@objc var animationDuration: TimeInterval = 5 { | ||
didSet { | ||
updateView() | ||
} | ||
} | ||
|
||
@objc var animationType: String = "sine" { | ||
didSet { | ||
updateView() | ||
} | ||
} | ||
|
||
@objc var borderRadius: CGFloat = 0 { | ||
didSet { | ||
updateView() | ||
} | ||
} | ||
|
||
private func colorFromHexString(hexString: NSString) -> UIColor { | ||
var cString: String = hexString.trimmingCharacters( | ||
in: .whitespacesAndNewlines | ||
).uppercased() | ||
|
||
if cString.hasPrefix("#") { | ||
cString.remove(at: cString.startIndex) | ||
} | ||
|
||
if cString.count != 6 { | ||
return UIColor.white | ||
} | ||
|
||
var rgbValue: UInt64 = 0 | ||
Scanner(string: cString).scanHexInt64(&rgbValue) | ||
|
||
return UIColor( | ||
red: CGFloat((rgbValue & 0xFF0000) >> 16) / 255.0, | ||
green: CGFloat((rgbValue & 0x00FF00) >> 8) / 255.0, | ||
blue: CGFloat(rgbValue & 0x0000FF) / 255.0, | ||
alpha: CGFloat(1.0) | ||
) | ||
} | ||
|
||
private func updateView() { | ||
|
||
let pointsSIMD = points.map { | ||
SIMD2<Float>(Float(truncating: $0[0]), Float(truncating: $0[1])) | ||
} | ||
let primaryColorsSwiftUI = primaryColors.map { | ||
Color(colorFromHexString(hexString: $0)) | ||
} | ||
let secondaryColorsSwiftUI = secondaryColors.map { | ||
Color(colorFromHexString(hexString: $0)) | ||
} | ||
let backgroundColor = Color(colorFromHexString(hexString: background)) | ||
let colorSpaceEnum: Gradient.ColorSpace = | ||
colorSpace == "perceptual" ? .perceptual : .device | ||
|
||
let meshGradientView = MeshGradientView( | ||
meshWidth: meshWidth, | ||
meshHeight: meshHeight, | ||
points: pointsSIMD, | ||
primaryColors: primaryColorsSwiftUI, | ||
secondaryColors: secondaryColorsSwiftUI, | ||
background: backgroundColor, | ||
smoothsColors: smoothsColors, | ||
colorSpace: colorSpaceEnum, | ||
borderRadius: borderRadius, | ||
isAnimated: isAnimated, | ||
animationDuration: animationDuration, | ||
animationType: animationType | ||
) | ||
|
||
if hostingController == nil { | ||
hostingController = UIHostingController(rootView: meshGradientView) | ||
if let hostingView = hostingController?.view { | ||
addSubview(hostingView) | ||
hostingView.translatesAutoresizingMaskIntoConstraints = false | ||
NSLayoutConstraint.activate([ | ||
hostingView.topAnchor.constraint(equalTo: topAnchor), | ||
hostingView.bottomAnchor.constraint(equalTo: bottomAnchor), | ||
hostingView.leadingAnchor.constraint(equalTo: leadingAnchor), | ||
hostingView.trailingAnchor.constraint(equalTo: trailingAnchor), | ||
]) | ||
} | ||
} else { | ||
hostingController?.rootView = meshGradientView | ||
} | ||
} | ||
} |
Empty file.
Empty file.
Empty file.
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Empty file.
Empty file.
Empty file.