1
1
mirror of https://github.com/exyte/Macaw.git synced 2024-07-14 16:30:34 +03:00

Merge remote-tracking branch 'origin/master' into rendering-respects-screen-scale

# Conflicts:
#	Source/views/MacawView.swift
This commit is contained in:
Anton Marunko 2020-07-29 12:01:00 +06:00
commit dafca2d1f6
170 changed files with 864 additions and 540 deletions

View File

@ -1,12 +1,12 @@
language: swift
osx_image: xcode10.2
osx_image: xcode11.5
branches:
only:
- master
script:
- set -o pipefail && xcodebuild test -project Macaw.xcodeproj -scheme 'Macaw iOS' -sdk iphonesimulator ONLY_ACTIVE_ARCH=NO -destination 'platform=iOS Simulator,OS=12.2,name=iPhone X' | xcpretty;
- set -o pipefail && xcodebuild test -project Macaw.xcodeproj -scheme 'Macaw iOS' -sdk iphonesimulator13.5 ONLY_ACTIVE_ARCH=NO -destination 'platform=iOS Simulator,OS=13.5,name=iPhone 11 Pro' | xcpretty;
- set -o pipefail && xcodebuild test -project Macaw.xcodeproj -scheme 'MacawOSX' ONLY_ACTIVE_ARCH=NO | xcpretty;

View File

@ -13,6 +13,9 @@ class AnimationsHierarchyViewController: UIViewController {
@IBOutlet weak var animView: MacawView!
var startCallbacks: [()->()] = []
var stopCallbacks: [()->()] = []
override func viewDidLoad() {
super.viewDidLoad()
@ -20,6 +23,22 @@ class AnimationsHierarchyViewController: UIViewController {
animView.zoom.enable()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
startCallbacks.forEach {
$0()
}
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
stopCallbacks.forEach {
$0()
}
}
func createTree(height: Int) -> Node {
let rect = Rect(w: 10, h: 10)
@ -52,7 +71,14 @@ class AnimationsHierarchyViewController: UIViewController {
func createLeaf(childForm: Locus, xDelta: Double, yDelta: Double) -> Group {
let inset = childForm.bounds().w / 2
let leaf = Shape(form: childForm, fill: Color.teal, place: .move(dx: -inset, dy: -inset))
leaf.placeVar.animation(angle: 2 * .pi, during: 5).cycle().play()
let animation = leaf.placeVar.animation(angle: 2 * .pi, during: 5).cycle()
startCallbacks.append({
animation.play()
})
stopCallbacks.append({
animation.stop()
})
let leafGroup = [leaf].group(place: .move(dx: xDelta, dy: yDelta))
leaf.onTap { _ in

View File

@ -233,7 +233,6 @@
57614B661F83D15600875933 /* PinchEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57E5E10C1E3B393900D1CB28 /* PinchEvent.swift */; };
57614B671F83D15600875933 /* ContentsInterpolation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57A27BCE1E44C4EC0057BD3A /* ContentsInterpolation.swift */; };
57614B681F83D15600875933 /* GroupRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57E5E13E1E3B393900D1CB28 /* GroupRenderer.swift */; };
57614B691F83D15600875933 /* SVGParserRegexHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57E5E1491E3B393900D1CB28 /* SVGParserRegexHelper.swift */; };
57614B6B1F83D15600875933 /* NSTimer+Closure.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57E5E14E1E3B393900D1CB28 /* NSTimer+Closure.swift */; };
57614B6C1F83D15600875933 /* SWXMLHash+TypeConversion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 572CEFC51E2CED4B008C7C83 /* SWXMLHash+TypeConversion.swift */; };
57614B6D1F83D15600875933 /* AnimationSequence.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57E5E0FF1E3B393900D1CB28 /* AnimationSequence.swift */; };
@ -330,7 +329,6 @@
57E5E1AA1E3B393900D1CB28 /* SVGConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57E5E1461E3B393900D1CB28 /* SVGConstants.swift */; };
57E5E1AB1E3B393900D1CB28 /* SVGParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57E5E1471E3B393900D1CB28 /* SVGParser.swift */; };
57E5E1AC1E3B393900D1CB28 /* SVGParserError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57E5E1481E3B393900D1CB28 /* SVGParserError.swift */; };
57E5E1AD1E3B393900D1CB28 /* SVGParserRegexHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57E5E1491E3B393900D1CB28 /* SVGParserRegexHelper.swift */; };
57E5E1AE1E3B393900D1CB28 /* SVGView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57E5E14A1E3B393900D1CB28 /* SVGView.swift */; };
57E5E1AF1E3B393900D1CB28 /* CAAnimationClosure.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57E5E14C1E3B393900D1CB28 /* CAAnimationClosure.swift */; };
57E5E1B01E3B393900D1CB28 /* CGFloat+Double.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57E5E14D1E3B393900D1CB28 /* CGFloat+Double.swift */; };
@ -950,7 +948,6 @@
57E5E1461E3B393900D1CB28 /* SVGConstants.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SVGConstants.swift; sourceTree = "<group>"; };
57E5E1471E3B393900D1CB28 /* SVGParser.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SVGParser.swift; sourceTree = "<group>"; };
57E5E1481E3B393900D1CB28 /* SVGParserError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SVGParserError.swift; sourceTree = "<group>"; };
57E5E1491E3B393900D1CB28 /* SVGParserRegexHelper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SVGParserRegexHelper.swift; sourceTree = "<group>"; };
57E5E14A1E3B393900D1CB28 /* SVGView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SVGView.swift; sourceTree = "<group>"; };
57E5E14C1E3B393900D1CB28 /* CAAnimationClosure.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CAAnimationClosure.swift; sourceTree = "<group>"; };
57E5E14D1E3B393900D1CB28 /* CGFloat+Double.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CGFloat+Double.swift"; sourceTree = "<group>"; };
@ -1731,7 +1728,6 @@
5B1A8C7520A15F7300E5FFAE /* SVGNodeLayout.swift */,
57E5E1471E3B393900D1CB28 /* SVGParser.swift */,
57E5E1481E3B393900D1CB28 /* SVGParserError.swift */,
57E5E1491E3B393900D1CB28 /* SVGParserRegexHelper.swift */,
C4820B171F458D0E008CE0FF /* SVGSerializer.swift */,
57E5E14A1E3B393900D1CB28 /* SVGView.swift */,
);
@ -2811,7 +2807,6 @@
57614B671F83D15600875933 /* ContentsInterpolation.swift in Sources */,
57614B681F83D15600875933 /* GroupRenderer.swift in Sources */,
5B6E192820AC58F900454E7E /* RadialGradient.swift in Sources */,
57614B691F83D15600875933 /* SVGParserRegexHelper.swift in Sources */,
5B6E192A20AC58F900454E7E /* Align.swift in Sources */,
57614B6B1F83D15600875933 /* NSTimer+Closure.swift in Sources */,
5B6E193620AC58F900454E7E /* Stop.swift in Sources */,
@ -2954,7 +2949,6 @@
57A27BCF1E44C4EC0057BD3A /* ContentsInterpolation.swift in Sources */,
57E5E1A31E3B393900D1CB28 /* GroupRenderer.swift in Sources */,
5B6E192720AC58F900454E7E /* RadialGradient.swift in Sources */,
57E5E1AD1E3B393900D1CB28 /* SVGParserRegexHelper.swift in Sources */,
5B6E192920AC58F900454E7E /* Align.swift in Sources */,
57E5E1B11E3B393900D1CB28 /* NSTimer+Closure.swift in Sources */,
30FF4962215CE97300FF653C /* MCAMediaTimingFillMode_iOS.swift in Sources */,
@ -3004,6 +2998,7 @@
isa = XCBuildConfiguration;
buildSettings = {
APPLICATION_EXTENSION_API_ONLY = NO;
CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
DEFINES_MODULE = YES;
DYLIB_COMPATIBILITY_VERSION = 1;
@ -3029,6 +3024,7 @@
isa = XCBuildConfiguration;
buildSettings = {
APPLICATION_EXTENSION_API_ONLY = NO;
CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
DEFINES_MODULE = YES;
DYLIB_COMPATIBILITY_VERSION = 1;
@ -3226,8 +3222,8 @@
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
DEVELOPMENT_TEAM = 7T95R85V93;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
DEVELOPMENT_TEAM = "";
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"$(SDKROOT)",
@ -3249,8 +3245,8 @@
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
DEVELOPMENT_TEAM = 7T95R85V93;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
DEVELOPMENT_TEAM = "";
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"$(SDKROOT)",

View File

@ -5,6 +5,22 @@
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "57FCD26B1D76EA4600CC0FB6"
BuildableName = "Macaw.framework"
BlueprintName = "Macaw"
ReferencedContainer = "container:Macaw.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
@ -23,8 +39,6 @@
</BuildableReference>
</TestableReference>
</Testables>
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
@ -36,8 +50,6 @@
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
@ -45,6 +57,15 @@
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "57FCD26B1D76EA4600CC0FB6"
BuildableName = "Macaw.framework"
BlueprintName = "Macaw"
ReferencedContainer = "container:Macaw.xcodeproj">
</BuildableReference>
</MacroExpansion>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">

View File

@ -20,13 +20,13 @@ class MacawSVGTests: XCTestCase {
private let testFolderName = "MacawTestOutputData"
private let shouldComparePNGImages = true
private let multipleTestsWillRun = false
private let shouldSaveFaildedTestImage = false
private let shouldSaveFailedTestImage = false
override func setUp() {
// Put setup code here. This method is called before the invocation of each test method in the class.
super.setUp()
if shouldSaveFaildedTestImage {
if shouldSaveFailedTestImage {
setupTestFolderDirectory()
}
}
@ -36,6 +36,21 @@ class MacawSVGTests: XCTestCase {
super.tearDown()
}
func compareResults(nodeContent: String?, referenceContent: String?) {
guard let nodeContent = nodeContent else {
XCTFail("nodeContent is empty")
return
}
guard let referenceContent = referenceContent else {
XCTFail("referenceContent is empty")
return
}
if nodeContent != referenceContent {
XCTFail("nodeContent is not equal to referenceContent" + TestUtils.prettyFirstDifferenceBetweenStrings(s1: nodeContent, s2: referenceContent))
}
}
func validate(node: Node, referenceFile: String) {
let bundle = Bundle(for: type(of: TestUtils()))
@ -43,7 +58,7 @@ class MacawSVGTests: XCTestCase {
if let path = bundle.path(forResource: referenceFile, ofType: "reference") {
let clipReferenceContent = try String.init(contentsOfFile: path).trimmingCharacters(in: .newlines)
let result = SVGSerializer.serialize(node: node)
XCTAssertEqual(result, clipReferenceContent)
compareResults(nodeContent: result, referenceContent: clipReferenceContent)
} else {
XCTFail("No file \(referenceFile)")
}
@ -192,13 +207,10 @@ class MacawSVGTests: XCTestCase {
let bundle = Bundle(for: type(of: TestUtils()))
do {
if let path = bundle.path(forResource: referenceFile, ofType: "reference") {
let referenceContent = try String(contentsOfFile: path)
let nodeContent = String(data: getJSONData(node: node), encoding: String.Encoding.utf8)
if nodeContent != referenceContent {
XCTFail("nodeContent is not equal to referenceContent")
}
compareResults(nodeContent: nodeContent, referenceContent: referenceContent)
let nativeImage = getImage(from: referenceFile)
@ -261,9 +273,9 @@ class MacawSVGTests: XCTestCase {
if referenceContentData != nodeContentData {
var failInfo = "referenceContentData is not equal to nodeContentData"
var failInfo = "referenceImageData is not equal to nodeImageData"
if shouldSaveFaildedTestImage {
if shouldSaveFailedTestImage {
let _ = saveImage(image: referenceImage, fileName: referenceFile + "_reference")
let _ = saveImage(image: nodeImage, fileName: referenceFile + "_incorrect")
@ -314,7 +326,7 @@ class MacawSVGTests: XCTestCase {
}
func writeToFile(string: String, fileName: String) -> URL? {
guard let directory = try? FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false) as NSURL else {
guard let directory = try? FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true) as NSURL else {
return .none
}
do {

View File

@ -25,4 +25,133 @@ class TestUtils {
return result
}
class func prettyFirstDifferenceBetweenStrings(s1: String, s2: String) -> String {
return prettyFirstDifferenceBetweenNSStrings(s1: s1 as NSString, s2: s2 as NSString) as String
}
}
/// Find first differing character between two strings
///
/// :param: s1 First String
/// :param: s2 Second String
///
/// :returns: .DifferenceAtIndex(i) or .NoDifference
fileprivate func firstDifferenceBetweenStrings(s1: NSString, s2: NSString) -> FirstDifferenceResult {
let len1 = s1.length
let len2 = s2.length
let lenMin = min(len1, len2)
for i in 0..<lenMin {
if s1.character(at: i) != s2.character(at: i) {
return .DifferenceAtIndex(i)
}
}
if len1 < len2 {
return .DifferenceAtIndex(len1)
}
if len2 < len1 {
return .DifferenceAtIndex(len2)
}
return .NoDifference
}
/// Create a formatted String representation of difference between strings
///
/// :param: s1 First string
/// :param: s2 Second string
///
/// :returns: a string, possibly containing significant whitespace and newlines
fileprivate func prettyFirstDifferenceBetweenNSStrings(s1: NSString, s2: NSString) -> NSString {
let firstDifferenceResult = firstDifferenceBetweenStrings(s1: s1, s2: s2)
return prettyDescriptionOfFirstDifferenceResult(firstDifferenceResult: firstDifferenceResult, s1: s1, s2: s2)
}
/// Create a formatted String representation of a FirstDifferenceResult for two strings
///
/// :param: firstDifferenceResult FirstDifferenceResult
/// :param: s1 First string used in generation of firstDifferenceResult
/// :param: s2 Second string used in generation of firstDifferenceResult
///
/// :returns: a printable string, possibly containing significant whitespace and newlines
fileprivate func prettyDescriptionOfFirstDifferenceResult(firstDifferenceResult: FirstDifferenceResult, s1: NSString, s2: NSString) -> NSString {
func diffString(index: Int, s1: NSString, s2: NSString) -> NSString {
let markerArrow = "\u{2b06}" // ""
let ellipsis = "\u{2026}" // ""
/// Given a string and a range, return a string representing that substring.
///
/// If the range starts at a position other than 0, an ellipsis
/// will be included at the beginning.
///
/// If the range ends before the actual end of the string,
/// an ellipsis is added at the end.
func windowSubstring(s: NSString, range: NSRange) -> String {
let validRange = NSMakeRange(range.location, min(range.length, s.length - range.location))
let substring = s.substring(with: validRange)
let prefix = range.location > 0 ? ellipsis : ""
let suffix = (s.length - range.location > range.length) ? ellipsis : ""
return "\(prefix)\(substring)\(suffix)"
}
// Show this many characters before and after the first difference
let windowPrefixLength = 10
let windowSuffixLength = 10
let windowLength = windowPrefixLength + 1 + windowSuffixLength
let windowIndex = max(index - windowPrefixLength, 0)
let windowRange = NSMakeRange(windowIndex, windowLength)
let sub1 = windowSubstring(s: s1, range: windowRange)
let sub2 = windowSubstring(s: s2, range: windowRange)
let markerPosition = min(windowSuffixLength, index) + (windowIndex > 0 ? 1 : 0)
let markerPrefix = String(repeating: " ", count: markerPosition)
let markerLine = "\(markerPrefix)\(markerArrow)"
return "Difference at index \(index):\n\(sub1)\n\(sub2)\n\(markerLine)" as NSString
}
switch firstDifferenceResult {
case .NoDifference: return "No difference"
case .DifferenceAtIndex(let index): return diffString(index: index, s1: s1, s2: s2)
}
}
/// Result type for firstDifferenceBetweenStrings()
public enum FirstDifferenceResult {
/// Strings are identical
case NoDifference
/// Strings differ at the specified index.
///
/// This could mean that characters at the specified index are different,
/// or that one string is longer than the other
case DifferenceAtIndex(Int)
}
extension FirstDifferenceResult: CustomStringConvertible {
/// Textual representation of a FirstDifferenceResult
public var description: String {
switch self {
case .NoDifference:
return "NoDifference"
case .DifferenceAtIndex(let index):
return "DifferenceAtIndex(\(index))"
}
}
/// Textual representation of a FirstDifferenceResult for debugging purposes
public var debugDescription: String {
return self.description
}
}

Some files were not shown because too many files have changed in this diff Show More