├── .gitignore ├── .travis.yml ├── LICENSE.txt ├── README.md ├── SPECIFICATION.md ├── browser ├── SwipeBrowser.swift ├── SwipeExporter.swift ├── SwipeTableViewController.swift ├── ios │ ├── SwipeBrowser.xib │ └── SwipeTableViewController.xib └── tvos │ ├── SwipeBrowser.xib │ └── SwipeTableViewController.xib ├── core ├── SwipeAction.swift ├── SwipeBook.swift ├── SwipeDocumentViewer.swift ├── SwipeElement.swift ├── SwipeEvent.swift ├── SwipeEventHandler.swift ├── SwipeExtensions.swift ├── SwipeHttpGet.swift ├── SwipeHttpPost.swift ├── SwipeList.swift ├── SwipeMarkdown.swift ├── SwipeNode.swift ├── SwipePage.swift ├── SwipePageTemplate.swift ├── SwipeParser.swift ├── SwipePath.swift ├── SwipeSynthesizer.swift ├── SwipeTextArea.swift ├── SwipeTextField.swift ├── SwipeTimer.swift ├── SwipeView.swift └── SwipeViewController.swift ├── network ├── SNNotificationManager.swift ├── SwipeAssetManager.swift ├── SwipeConnection.swift ├── SwipePrefetcher.swift └── asset.xcdatamodeld │ └── asset.xcdatamodel │ └── contents └── sample ├── bug_repro ├── AppDelegate.swift ├── Assets.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── IMG_9401.mov ├── Info.plist └── ViewController.swift ├── sample.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcuserdata │ │ └── satoshi.xcuserdatad │ │ └── UserInterfaceState.xcuserstate ├── xcshareddata │ └── xcschemes │ │ ├── sample.xcscheme │ │ └── sampletv.xcscheme └── xcuserdata │ └── satoshi.xcuserdatad │ └── xcschemes │ ├── sample.xcscheme │ └── xcschememanagement.plist ├── sample ├── AppDelegate.swift ├── Assets.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── Info.plist ├── SwipeBrowser+ex.swift ├── SwipeBrowser.xib ├── SwipeVideoController.swift ├── SwipeVideoController.xib ├── iTunesArtwork@2x.png ├── more.png ├── movie.png ├── nasa.png ├── radio.png ├── speech.png └── text.png ├── sampletv ├── AppDelegate.swift ├── Assets.xcassets │ ├── App Icon & Top Shelf Image.brandassets │ │ ├── App Icon - Large.imagestack │ │ │ ├── Back.imagestacklayer │ │ │ │ ├── Content.imageset │ │ │ │ │ └── Contents.json │ │ │ │ └── Contents.json │ │ │ ├── Contents.json │ │ │ ├── Front.imagestacklayer │ │ │ │ ├── Content.imageset │ │ │ │ │ └── Contents.json │ │ │ │ └── Contents.json │ │ │ └── Middle.imagestacklayer │ │ │ │ ├── Content.imageset │ │ │ │ └── Contents.json │ │ │ │ └── Contents.json │ │ ├── App Icon - Small.imagestack │ │ │ ├── Back.imagestacklayer │ │ │ │ ├── Content.imageset │ │ │ │ │ └── Contents.json │ │ │ │ └── Contents.json │ │ │ ├── Contents.json │ │ │ ├── Front.imagestacklayer │ │ │ │ ├── Content.imageset │ │ │ │ │ └── Contents.json │ │ │ │ └── Contents.json │ │ │ └── Middle.imagestacklayer │ │ │ │ ├── Content.imageset │ │ │ │ └── Contents.json │ │ │ │ └── Contents.json │ │ ├── Contents.json │ │ └── Top Shelf Image.imageset │ │ │ └── Contents.json │ ├── Contents.json │ └── LaunchImage.launchimage │ │ └── Contents.json ├── Base.lproj │ └── Main.storyboard └── Info.plist └── script ├── actions.swipe ├── bgm ├── HiddenAgenda.mp3 ├── MonkeysSpinningMonkeys.mp3 ├── TheDescent.mp3 └── music.swipe ├── details ├── details_index.swipe ├── loop_animation.swipe ├── multilingual_strings.swipe └── transition_animation.swipe ├── espresso.png ├── espresso ├── IMG_8152.JPG ├── IMG_8153.m4v ├── IMG_8154.m4v ├── IMG_8155.JPG ├── IMG_8156.JPG ├── IMG_8159.m4v ├── IMG_8160.m4v ├── IMG_8161.JPG ├── IMG_8162.JPG ├── IMG_8163.m4v ├── IMG_8166.m4v ├── IMG_8167.m4v ├── IMG_8168.JPG ├── IMG_8169.m4v ├── IMG_8171.m4v └── espresso_machine_l.swipe ├── index.swipe ├── list.swipe ├── markdown.swipe ├── nasa ├── nasa.swipe ├── nasa000.jpg ├── nasa001.jpg ├── nasa002.jpg ├── nasa003.jpg ├── nasa004.jpg ├── nasa005.jpg ├── nasa006.jpg ├── nasa007.jpg ├── nasa008.jpg ├── nasa009.jpg ├── nasa010.jpg ├── nasa011.jpg ├── nasa012.jpg └── nasa013.jpg ├── network.swipe ├── radiostream.swipe ├── speech.swipe ├── tutorial ├── Icon-180.png ├── chara_walk.png ├── dice.swipe ├── dice2.swipe ├── empty.swipe ├── epsilon.gif ├── izumi_06.mov ├── shuttle.png ├── sound01.wav ├── swipe.swipe └── tutorial.swipe ├── tv ├── focus.swipe └── tv_index.swipe ├── update.swipe ├── vectors.swipe └── videostream.swipe /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | *.pbxuser 3 | *.mode1v3 4 | *.mode2v3 5 | *.perspectivev3 6 | *.xcuserstate 7 | project.xcworkspace/ 8 | xcuserdata/ 9 | 10 | .DS_Store 11 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: objective-c 2 | osx_image: xcode8.1 3 | env: 4 | global: 5 | - LC_CTYPE=en_US.UTF-8 6 | - PROJECT=sample/sample.xcodeproj 7 | matrix: 8 | - SCHEME=sample SDK=iphonesimulator 9 | - SCHEME=sampletv SDK=appletvsimulator 10 | script: 11 | - set -o pipefail 12 | - xcodebuild build -project $PROJECT -scheme $SCHEME -sdk $SDK -configuration Release | xcpretty -c 13 | notifications: 14 | email: false 15 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Satoshi Nakajima (https://github.com/snakajima) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Swipe & Swipe Engine 2 | 3 | [![Build Status](https://travis-ci.org/swipe-org/swipe.svg?branch=master)](https://travis-ci.org/swipe-org/swipe) 4 | 5 | ## What is Swipe? 6 | 7 | Swipe is a **domain-specific, declarative language** for non-developers (such as designers, animators, illustrators, musicians, videographers and comic writers) to create **media-rich/animated documents** that contain photos, videos, images, vector graphics, animations, voices, musics and sound effects, which will be consumed on touch-enabled devices such as smartphones, tablets and touch-enabled set-top-boxes (such as **iPhone and Apple TV**). 8 | 9 | ## What is Swipe Engine? 10 | 11 | Swipe Engine is the **viewer** of documents described in Swipe. Since it supports hyperlinking and allows you to browse from one Swipe document to another, you could even call it a **domain-specific browser**. At this moment (October 2015), Swipe Engine is available for **iOS and tvOS**, but will be ported to other platforms such as Android, Windows and even HTML5 (and we are looking for volunteers to do so). 12 | 13 | ## Why do we need Swipe? 14 | 15 | Because existing platforms (such as HTML5, ePub, Flash and iBooks) require some form of programming to create media-rich/animated documents, which is expensive and error-prone. 16 | 17 | * HTML used be a simple declarative language, but became a full-blown programming environment with DOM and JavaScript. 18 | * Flash used be a great animation tool for designers, but became a full-blown programming environment with the introduction of ActionScript. 19 | * ePub does not directly support animations, and the author needs to embed some JavaScript code to enable animations, which may or may not work depending on the eBook platform. 20 | 21 | That's why I came to the conclusion that it's time to design a new platform, which is powerful enough to describe media-rich documents that allow us to take full advantage of modern devices, but is also **strictly declarative** (no API, no script), easy to read, easy to write and easy to auto-generate. 22 | 23 | ## Why is it open? 24 | 25 | Because I want to make it an industry standard so that everybody can benefit from it. I was lucky enough to work on several successful products, such as Windows 95, which were used by hundreds of millions of people. I'd like to see such a success again. 26 | 27 | ## Is it really free? 28 | 29 | Yes, I still maintain the copyright, but it's **absolutely free**. You can use it for non-commercial and commercial applications, modify it as needed, port it to other platforms (I'd really appreciate if you open source it as well), re-distribute it with your applications, as long as you explicitly mention that your application uses Swipe and recognize me (Satoshi Nakajima) as the copyright holder. 30 | 31 | ## Target Applications 32 | 33 | * Interactive Comics 34 | * Sound Novels 35 | * Graphical Audio Books and Music Albums 36 | * Interactive Videos 37 | * Media-rich Tutorials and Presentations 38 | * Interactive Arts 39 | 40 | ## Primary Audiences 41 | 42 | * Designers/Illustrators 43 | * Animators 44 | * Comic Writers 45 | * Musicians/Artists 46 | * Videographers/Photographers 47 | * Teachers/Educators 48 | * Weekend Programmers 49 | 50 | ## Design Principles 51 | 52 | * Optimized for touch-enabled devices 53 | * 100% declarative (no programming) 54 | * Rich & interactive animations 55 | * Re-invent the video experience 56 | * Customizable page-transitions 57 | * Designer friendly 58 | * Lightweight & portable 59 | 60 | ## Is there an authoring tool? 61 | 62 | I'm working on it. Stay tuned. 63 | 64 | ## Specification 65 | 66 | The specification is available [here](SPECIFICATION.md). 67 | 68 | -------------------------------------------------------------------------------- /browser/SwipeExporter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SwipeExporter.swift 3 | // sample 4 | // 5 | // Created by satoshi on 8/19/16. 6 | // Copyright © 2016 Satoshi Nakajima. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import ImageIO 11 | import MobileCoreServices 12 | import AVFoundation 13 | 14 | class SwipeExporter: NSObject { 15 | enum Error: Swift.Error { 16 | case FailedToCreate 17 | case FailedToFinalize 18 | } 19 | 20 | let swipeViewController:SwipeViewController 21 | let fps:Int 22 | let resolution:CGFloat 23 | var progress = 0.0 as CGFloat // Output: Proress from 0.0 to 1.0 24 | var outputSize = CGSize.zero // Output: Size of generated GIF/video 25 | var pauseDuration = 0.0 as CGFloat 26 | var transitionDuration = 1.0 as CGFloat 27 | 28 | private var iFrame = 0 29 | 30 | init(swipeViewController:SwipeViewController, fps:Int, resolution:CGFloat = 720.0) { 31 | self.swipeViewController = swipeViewController 32 | self.fps = fps 33 | self.resolution = resolution 34 | } 35 | 36 | func exportAsGifAnimation(_ fileURL:URL, startPage:Int, pageCount:Int, progress:@escaping (_ complete:Bool, _ error:Swift.Error?)->Void) { 37 | guard let idst = CGImageDestinationCreateWithURL(fileURL as CFURL, kUTTypeGIF, pageCount * fps + 1, nil) else { 38 | return progress(false, Error.FailedToCreate) 39 | } 40 | CGImageDestinationSetProperties(idst, [String(kCGImagePropertyGIFDictionary): 41 | [String(kCGImagePropertyGIFLoopCount):0]] as CFDictionary) 42 | iFrame = 0 43 | outputSize = swipeViewController.view.frame.size 44 | self.processFrame(idst, startPage:startPage, pageCount: pageCount, progress:progress) 45 | } 46 | 47 | func processFrame(_ idst:CGImageDestination, startPage:Int, pageCount:Int, progress:@escaping (_ complete:Bool, _ error:Swift.Error?)->Void) { 48 | self.progress = CGFloat(iFrame) / CGFloat(fps) / CGFloat(pageCount) 49 | swipeViewController.scrollTo(CGFloat(startPage) + CGFloat(iFrame) / CGFloat(fps)) 50 | 51 | // HACK: This delay is not 100% reliable, but is sufficient practically. 52 | DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(100)) { 53 | progress(false, nil) 54 | let presentationLayer = self.swipeViewController.view.layer.presentation()! 55 | UIGraphicsBeginImageContext(self.swipeViewController.view.frame.size); defer { 56 | UIGraphicsEndImageContext() 57 | } 58 | presentationLayer.render(in: UIGraphicsGetCurrentContext()!) 59 | let image = UIGraphicsGetImageFromCurrentImageContext()! 60 | 61 | CGImageDestinationAddImage(idst, image.cgImage!, [String(kCGImagePropertyGIFDictionary): 62 | [String(kCGImagePropertyGIFDelayTime):0.2]] as CFDictionary) 63 | 64 | self.iFrame += 1 65 | if self.iFrame < pageCount * self.fps + 1 { 66 | self.processFrame(idst, startPage:startPage, pageCount: pageCount, progress:progress) 67 | } else { 68 | if CGImageDestinationFinalize(idst) { 69 | progress(true, nil) 70 | } else { 71 | progress(false, Error.FailedToFinalize) 72 | } 73 | } 74 | } 75 | } 76 | 77 | func exportAsMovie(_ fileURL:URL, startPage:Int, pageCount:Int?, progress:@escaping (_ complete:Bool, _ error:Swift.Error?)->Void) { 78 | // AVAssetWrite will fail if the file already exists 79 | let manager = FileManager.default 80 | if manager.fileExists(atPath: fileURL.path) { 81 | try! manager.removeItem(at: fileURL) 82 | } 83 | 84 | let efps = Int(round(CGFloat(fps) * transitionDuration)) // Effective FPS 85 | let extra = Int(round(CGFloat(fps) * pauseDuration)) 86 | 87 | let limit:Int 88 | if let pageCount = pageCount, startPage + pageCount < swipeViewController.book.pages.count { 89 | limit = pageCount * (efps + extra) + extra + 1 90 | } else { 91 | limit = (swipeViewController.book.pages.count - startPage - 1) * (efps + extra) + extra + 1 92 | } 93 | print("SwipeExporter:exportAsMovie", self.fps, efps, extra, limit) 94 | 95 | let viewSize = swipeViewController.view.frame.size 96 | let scale = min(resolution / min(viewSize.width, viewSize.height), swipeViewController.view.contentScaleFactor) 97 | 98 | outputSize = CGSize(width: viewSize.width * scale, height: viewSize.height * scale) 99 | 100 | self.swipeViewController.scrollTo(CGFloat(startPage)) 101 | DispatchQueue.main.async { // HACK: work-around of empty first page bug 102 | do { 103 | let writer = try AVAssetWriter(url: fileURL, fileType: AVFileType.mov) 104 | let input = AVAssetWriterInput(mediaType: AVMediaType.video, outputSettings: [ 105 | AVVideoCodecKey : AVVideoCodecType.h264, 106 | AVVideoWidthKey : self.outputSize.width, 107 | AVVideoHeightKey : self.outputSize.height 108 | ]) 109 | let adaptor = AVAssetWriterInputPixelBufferAdaptor(assetWriterInput: input, sourcePixelBufferAttributes: [ 110 | kCVPixelBufferPixelFormatTypeKey as String: NSNumber(value: kCVPixelFormatType_32ARGB), 111 | kCVPixelBufferWidthKey as String: self.outputSize.width, 112 | kCVPixelBufferHeightKey as String: self.outputSize.height, 113 | ]) 114 | writer.add(input) 115 | 116 | self.iFrame = 0 117 | 118 | guard writer.startWriting() else { 119 | return progress(false, Error.FailedToFinalize) 120 | } 121 | writer.startSession(atSourceTime: CMTimeMake(value:0, timescale:Int32(self.fps))) 122 | 123 | //self.swipeViewController.scrollTo(CGFloat(startPage)) 124 | input.requestMediaDataWhenReady(on: DispatchQueue.main) { 125 | guard input.isReadyForMoreMediaData else { 126 | print("SwipeExporter:not ready", self.iFrame) 127 | return // Not ready. Just wait. 128 | } 129 | self.progress = 0.5 * CGFloat(self.iFrame) / CGFloat(limit) 130 | progress(false, nil) 131 | 132 | var pixelBufferX: CVPixelBuffer? = nil 133 | let status: CVReturn = CVPixelBufferPoolCreatePixelBuffer(kCFAllocatorDefault, adaptor.pixelBufferPool!, &pixelBufferX) 134 | guard let managedPixelBuffer = pixelBufferX, status == 0 else { 135 | print("failed to allocate pixel buffer") 136 | writer.cancelWriting() 137 | return progress(false, Error.FailedToCreate) 138 | } 139 | 140 | CVPixelBufferLockBaseAddress(managedPixelBuffer, CVPixelBufferLockFlags(rawValue: CVOptionFlags(0))) 141 | let data = CVPixelBufferGetBaseAddress(managedPixelBuffer) 142 | let rgbColorSpace = CGColorSpaceCreateDeviceRGB() 143 | if let context = CGContext(data: data, width: Int(self.outputSize.width), height: Int(self.outputSize.height), bitsPerComponent: 8, bytesPerRow: CVPixelBufferGetBytesPerRow(managedPixelBuffer), space: rgbColorSpace, bitmapInfo: CGImageAlphaInfo.premultipliedFirst.rawValue) { 144 | let xf = CGAffineTransform(scaleX: scale, y: -scale) 145 | context.concatenate(xf.translatedBy(x: 0, y: -viewSize.height)) 146 | let presentationLayer = self.swipeViewController.view.layer.presentation()! 147 | presentationLayer.render(in: context) 148 | //print("SwipeExporter:render", self.iFrame) 149 | } else { 150 | print("SwipeExporter:failed to get context", self.iFrame, self.fps) 151 | } 152 | CVPixelBufferUnlockBaseAddress(managedPixelBuffer, CVPixelBufferLockFlags(rawValue: CVOptionFlags(0))) 153 | 154 | let presentationTime = CMTimeMake(value:Int64(self.iFrame), timescale: Int32(self.fps)) 155 | if !adaptor.append(managedPixelBuffer, withPresentationTime: presentationTime) { 156 | print("SwipeExporter:failed to append", self.iFrame) 157 | writer.cancelWriting() 158 | return progress(false, Error.FailedToCreate) 159 | } 160 | 161 | self.iFrame += 1 162 | if self.iFrame < limit { 163 | let curPage = self.iFrame / (extra + efps) 164 | let offset = self.iFrame % (extra + efps) 165 | if offset == 0 { 166 | self.swipeViewController.scrollTo(CGFloat(startPage + curPage)) 167 | } else if offset > extra { 168 | self.swipeViewController.scrollTo(CGFloat(startPage + curPage) + CGFloat(offset - extra) / CGFloat(efps)) 169 | } 170 | } else { 171 | input.markAsFinished() 172 | print("SwipeExporter: finishWritingWithCompletionHandler") 173 | writer.finishWriting(completionHandler: { 174 | DispatchQueue.main.async { 175 | progress(true, nil) 176 | } 177 | }) 178 | } 179 | } 180 | } catch let error { 181 | progress(false, error) 182 | } 183 | } 184 | } 185 | } 186 | -------------------------------------------------------------------------------- /browser/ios/SwipeTableViewController.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /browser/tvos/SwipeBrowser.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /browser/tvos/SwipeTableViewController.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /core/SwipeAction.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SwipeAction.swift 3 | // 4 | // Created by Pete Stoppani on 5/19/16. 5 | // 6 | 7 | import Foundation 8 | 9 | class SwipeAction: NSObject { 10 | 11 | let info:[String:Any] 12 | 13 | init(info:[String:Any]) { 14 | self.info = info 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /core/SwipeDocumentViewer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SwipeDocumentViewer.swift 3 | // sample 4 | // 5 | // Created by satoshi on 10/13/15. 6 | // Copyright © 2015 Satoshi Nakajima. All rights reserved. 7 | // 8 | 9 | #if os(OSX) 10 | import Cocoa 11 | #else 12 | import UIKit 13 | #endif 14 | 15 | protocol SwipeDocumentViewerDelegate: NSObjectProtocol { 16 | func browseTo(_ url:URL) 17 | func tapped() 18 | } 19 | 20 | protocol SwipeDocumentViewer { 21 | func documentTitle() -> String? 22 | func loadDocument(_ document:[String:Any], size:CGSize, url:URL?, state:[String:Any]?, callback:@escaping (Float, NSError?)->(Void)) throws 23 | func hideUI() -> Bool 24 | func landscape() -> Bool 25 | func setDelegate(_ delegate:SwipeDocumentViewerDelegate) 26 | func becomeZombie() 27 | func saveState() -> [String:Any]? 28 | func languages() -> [[String:Any]]? 29 | func reloadWithLanguageId(_ langId:String) 30 | func moveToPageAt(index:Int) 31 | func pageIndex() -> Int? 32 | func pageCount() -> Int? 33 | } 34 | 35 | enum SwipeError: Swift.Error { 36 | case invalidDocument 37 | } 38 | -------------------------------------------------------------------------------- /core/SwipeEvent.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SwipeEvent.swift 3 | // 4 | // Created by Pete Stoppani on 5/19/16. 5 | // 6 | 7 | import Foundation 8 | 9 | /* Format 10 | { 11 | "": { 12 | "params": {"":{"type":""}, ... }, 13 | "actions": [, ... ] 14 | } 15 | */ 16 | 17 | class SwipeEvent: NSObject { 18 | private let info:[String:Any] 19 | let actions:[SwipeAction] 20 | private(set) lazy var params: [String:Any]? = { 21 | return self.info["params"] as? [String:Any] 22 | }() 23 | 24 | init(type: String, info: [String:Any]) { 25 | self.info = info 26 | if let paramsInfo = info["params"] as? [String:Any] { 27 | NSLog("XdEvent params: \(paramsInfo)") 28 | } 29 | let actionsInfo = info["actions"] as? [[String:Any]] ?? [[String:Any]]() 30 | actions = actionsInfo.map { SwipeAction(info: $0) } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /core/SwipeEventHandler.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SwipeEventHandler.swift 3 | // 4 | // Created by Pete Stoppani on 5/19/16. 5 | // 6 | 7 | import Foundation 8 | 9 | class SwipeEventHandler: NSObject { 10 | static var count = 0 11 | 12 | private var events = [String:SwipeEvent]() 13 | 14 | override init () { 15 | super.init() 16 | SwipeEventHandler.count += 1 17 | //print("SEventHandler init", SwipeEventHandler.count) 18 | } 19 | 20 | deinit { 21 | SwipeEventHandler.count -= 1 22 | //print("SEventHandler deinit", SwipeEventHandler.count) 23 | } 24 | 25 | func parse(_ eventsInfo: [String:Any]) { 26 | for eventType in eventsInfo.keys { 27 | //NSLog("XdEventH parsed event: \(eventType)"); 28 | if let eventInfo = eventsInfo[eventType] as? [String:Any] { 29 | let event = SwipeEvent(type: eventType, info: eventInfo) 30 | events[eventType] = event 31 | } 32 | } 33 | } 34 | 35 | func actionsFor(_ event: String) -> [SwipeAction]? { 36 | return events[event]?.actions 37 | } 38 | 39 | func getEvent(_ event: String) -> SwipeEvent? { 40 | return events[event] 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /core/SwipeExtensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SwipeExtensions.swift 3 | // sample 4 | // 5 | // Created by satoshi on 10/15/15. 6 | // Copyright © 2015 Satoshi Nakajima. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | extension String { 12 | var localized:String { 13 | return NSLocalizedString(self, comment:"") 14 | } 15 | } 16 | 17 | extension URL { 18 | static func url(_ urlString:String, baseURL:URL?) -> URL? { 19 | let url = URL(string: urlString, relativeTo: baseURL) 20 | if let scheme = url?.scheme, scheme.count > 0 { 21 | return url 22 | } 23 | 24 | var components = urlString.components(separatedBy: "/") 25 | if components.count == 1 { 26 | return Bundle.main.url(forResource: urlString, withExtension: nil) 27 | } 28 | let filename = components.last 29 | components.removeLast() 30 | let dir = components.joined(separator: "/") 31 | return Bundle.main.url(forResource: filename, withExtension: nil, subdirectory: dir) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /core/SwipeHttpGet.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SwipeHttpGet.swift 3 | // 4 | // Created by Pete Stoppani on 6/9/16. 5 | // 6 | 7 | import Foundation 8 | 9 | class SwipeHttpGet : SwipeNode { 10 | let TAG = "SwipeHttpGet" 11 | private static var getters = [SwipeHttpGet]() 12 | private var params: [String:Any]? 13 | private var data: [String:Any]? 14 | 15 | static func create(_ parent: SwipeNode, getInfo: [String:Any]) { 16 | let geter = SwipeHttpGet(parent: parent, getInfo: getInfo) 17 | getters.append(geter) 18 | } 19 | 20 | init(parent: SwipeNode, getInfo: [String:Any]) { 21 | super.init(parent: parent) 22 | 23 | if let eventsInfo = getInfo["events"] as? [String:Any] { 24 | eventHandler.parse(eventsInfo) 25 | } 26 | 27 | if let sourceInfo = getInfo["source"] as? [String:Any] { 28 | if let urlString = sourceInfo["url"] as? String, let url = URL(string: urlString) { 29 | SwipeAssetManager.sharedInstance().loadAsset(url, prefix: "", bypassCache:true) { (urlLocal:URL?, error:NSError?) -> Void in 30 | if let urlL = urlLocal, error == nil, let data = try? Data(contentsOf: urlL) { 31 | do { 32 | guard let json = try JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions()) as? [String:Any] else { 33 | self.handleError("get \(urlString): not a dictionary.") 34 | return 35 | } 36 | // Success 37 | if let event = self.eventHandler.getEvent("completion"), let actionsInfo = self.eventHandler.actionsFor("completion") { 38 | self.data = json 39 | self.params = event.params 40 | self.execute(self, actions: actionsInfo) 41 | } 42 | } catch let error as NSError { 43 | self.handleError("get \(urlString): invalid JSON file \(error.localizedDescription)") 44 | return 45 | } 46 | } else { 47 | self.handleError("get \(urlString): \(error?.localizedDescription ?? "")") 48 | } 49 | } 50 | } else { 51 | self.handleError("get missing or invalid url") 52 | } 53 | } else { 54 | self.handleError("get missing source") 55 | } 56 | } 57 | 58 | private func handleError(_ errorMsg: String) { 59 | if let event = self.eventHandler.getEvent("error"), let actionsInfo = self.eventHandler.actionsFor("error") { 60 | self.data = ["message":errorMsg] 61 | self.params = event.params 62 | self.execute(self, actions: actionsInfo) 63 | } else { 64 | NSLog(TAG + errorMsg) 65 | } 66 | } 67 | 68 | func cancel() { 69 | 70 | } 71 | 72 | static func cancelAll() { 73 | for timer in getters { 74 | timer.cancel() 75 | } 76 | 77 | getters.removeAll() 78 | } 79 | 80 | // SwipeNode 81 | 82 | override func getPropertiesValue(_ originator: SwipeNode, info: [String:Any]) -> Any? { 83 | let prop = info.keys.first! 84 | NSLog(TAG + " getPropsVal(\(prop))") 85 | 86 | switch (prop) { 87 | case "params": 88 | if let params = self.params, let data = self.data { 89 | NSLog(TAG + " not checking params \(params)") 90 | var item:[String:Any] = ["params":data] 91 | var path = info 92 | var property = "params" 93 | 94 | while (true) { 95 | if let next = path[property] as? String { 96 | if let sub = item[property] as? [String:Any] { 97 | return sub[next] 98 | } else { 99 | return nil 100 | } 101 | } else if let next = path[property] as? [String:Any] { 102 | if let sub = item[property] as? [String:Any] { 103 | path = next 104 | property = path.keys.first! 105 | item = sub 106 | } else { 107 | return nil 108 | } 109 | } else { 110 | return nil 111 | } 112 | } 113 | 114 | // loop on properties in info until get to a String 115 | } 116 | break; 117 | default: 118 | return nil 119 | } 120 | 121 | return nil 122 | } 123 | 124 | } 125 | -------------------------------------------------------------------------------- /core/SwipeHttpPost.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SwipeHttpPost.swift 3 | // 4 | // Created by Pete Stoppani on 6/16/16. 5 | // 6 | 7 | import Foundation 8 | 9 | class SwipeHttpPost : SwipeNode { 10 | let TAG = "SWPost" 11 | private static var posters = [SwipeHttpPost]() 12 | private var params: [String:Any]? 13 | private var data: [String:Any]? 14 | 15 | static func create(_ parent: SwipeNode, postInfo: [String:Any]) { 16 | let poster = SwipeHttpPost(parent: parent, postInfo: postInfo) 17 | posters.append(poster) 18 | } 19 | 20 | init(parent: SwipeNode, postInfo: [String:Any]) { 21 | super.init(parent: parent) 22 | 23 | if let eventsInfo = postInfo["events"] as? [String:Any] { 24 | eventHandler.parse(eventsInfo) 25 | } 26 | 27 | if let targetInfo = postInfo["target"] as? [String:Any] { 28 | if var urlString = targetInfo["url"] as? String { 29 | if let params = postInfo["params"] as? [String:Any] { 30 | var paramsSeparator = "?" 31 | if urlString.contains("?") { 32 | paramsSeparator = "&" 33 | } 34 | 35 | for param in params.keys { 36 | var val: String? 37 | 38 | if let str = params[param] as? String { 39 | val = str 40 | } else if let valInfo = params[param] as? [String:Any], 41 | let valOfInfo = valInfo["valueOf"] as? [String:Any], 42 | let str = parent.getValue(parent, info:valOfInfo) as? String { 43 | val = str 44 | } 45 | 46 | if val != nil { 47 | urlString.append(paramsSeparator) 48 | urlString.append(param) 49 | urlString.append("=") 50 | urlString.append(val!.replacingOccurrences(of: "?", with: "")) 51 | paramsSeparator = "&" 52 | } 53 | } 54 | } 55 | if let encoded = urlString.addingPercentEncoding(withAllowedCharacters: CharacterSet.urlQueryAllowed), 56 | let url = URL(string: encoded) { 57 | var request = URLRequest(url: url) 58 | request.httpMethod = "POST" 59 | 60 | if let dataStr = postInfo["data"] as? String { 61 | request.httpBody = dataStr.data(using: .utf8) 62 | request.setValue("text/plain; charset=UTF-8", forHTTPHeaderField: "Content-Type") 63 | } 64 | if let data = postInfo["data"] as? [String:Any] { 65 | let evalData = parent.evaluate(data) 66 | do { 67 | request.httpBody = try JSONSerialization.data(withJSONObject: evalData, options: JSONSerialization.WritingOptions.prettyPrinted) 68 | request.setValue("application/json; charset=UTF-8", forHTTPHeaderField: "Content-Type") 69 | } catch let error as NSError { 70 | print("error=\(error)") 71 | self.handleError("post error \(error)") 72 | return 73 | } 74 | } 75 | 76 | if let headers = postInfo["headers"] as? [String:String] { 77 | for h in headers.keys { 78 | request.setValue(headers[h], forHTTPHeaderField: h) 79 | } 80 | } 81 | 82 | let task = URLSession.shared.dataTask(with: request) { data, response, error in 83 | guard error == nil && data != nil else { // check for fundamental networking error 84 | print("error=\(String(describing: error))") 85 | self.handleError("post error \(String(describing: error))") 86 | return 87 | } 88 | 89 | if let httpStatus = response as? HTTPURLResponse, httpStatus.statusCode != 200 { // check for http errors 90 | print("statusCode should be 200, but is \(httpStatus.statusCode)") 91 | print("response = \(String(describing: response))") 92 | self.handleError("post error \(httpStatus.statusCode)") 93 | return 94 | } 95 | 96 | let responseString = String(data: data!, encoding: .utf8) 97 | print("responseString = \(String(describing: responseString))") 98 | 99 | do { 100 | guard let json = try JSONSerialization.jsonObject(with: data!, options: JSONSerialization.ReadingOptions()) as? [String:Any] else { 101 | self.handleError("post \(urlString): not a dictionary.") 102 | return 103 | } 104 | // Success 105 | if let event = self.eventHandler.getEvent("completion"), let actionsInfo = self.eventHandler.actionsFor("completion") { 106 | DispatchQueue.main.async { 107 | self.data = json 108 | self.params = event.params 109 | self.execute(self, actions: actionsInfo) 110 | } 111 | } 112 | } catch let error as NSError { 113 | self.handleError("post \(urlString): invalid JSON file \(error.localizedDescription)") 114 | return 115 | } 116 | } 117 | task.resume() 118 | } else { 119 | self.handleError("post missing or invalid url") 120 | } 121 | } else { 122 | self.handleError("post missing or invalid url") 123 | } 124 | } else { 125 | self.handleError("post missing target") 126 | } 127 | } 128 | 129 | private func handleError(_ errorMsg: String) { 130 | if let event = self.eventHandler.getEvent("error"), let actionsInfo = self.eventHandler.actionsFor("error") { 131 | DispatchQueue.main.async { 132 | self.data = ["message":errorMsg] 133 | self.params = event.params 134 | self.execute(self, actions: actionsInfo) 135 | } 136 | } else { 137 | NSLog(TAG + errorMsg) 138 | } 139 | } 140 | 141 | func cancel() { 142 | 143 | } 144 | 145 | static func cancelAll() { 146 | posters.removeAll() 147 | } 148 | 149 | // SwipeNode 150 | 151 | override func getPropertiesValue(_ originator: SwipeNode, info: [String:Any]) -> Any? { 152 | let prop = info.keys.first! 153 | NSLog(TAG + " getPropsVal(\(prop))") 154 | 155 | switch (prop) { 156 | case "params": 157 | if let params = self.params, let data = self.data { 158 | NSLog(TAG + " not checking params \(params)") 159 | var item:[String:Any] = ["params":data] 160 | var path = info 161 | var property = "params" 162 | 163 | while (true) { 164 | if let next = path[property] as? String { 165 | if let sub = item[property] as? [String:Any] { 166 | let ret = sub[next] 167 | if let str = ret as? String { 168 | return str 169 | } else if let arr = ret as? [Any] { 170 | if arr.count > 0 { 171 | _ = "Array handling needs to be completed" 172 | return arr[0] 173 | } else { 174 | return nil 175 | } 176 | } else { 177 | return ret 178 | } 179 | } else { 180 | return nil 181 | } 182 | } else if let next = path[property] as? [String:Any] { 183 | if let sub = item[property] as? [String:Any] { 184 | path = next 185 | property = path.keys.first! 186 | item = sub 187 | } else { 188 | return nil 189 | } 190 | } else { 191 | return nil 192 | } 193 | } 194 | 195 | // loop on properties in info until get to a String 196 | } 197 | break; 198 | default: 199 | return nil 200 | } 201 | 202 | return nil 203 | } 204 | 205 | } 206 | -------------------------------------------------------------------------------- /core/SwipeMarkdown.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SwipeMarkdown.swift 3 | // Swipe 4 | // 5 | // Created by satoshi on 9/21/15. 6 | // Copyright © 2015 Satoshi Nakajima. All rights reserved. 7 | // 8 | #if os(OSX) 9 | import Cocoa 10 | #else 11 | import UIKit 12 | #endif 13 | 14 | class SwipeMarkdown { 15 | private var attrs = [String:[NSAttributedString.Key:Any]]() 16 | private var prefixes = [ 17 | "-":"\u{2022} ", // bullet (U+2022), http://graphemica.com/%E2%80%A2 18 | "```":" ", 19 | ] 20 | private let scale:CGSize 21 | private var shadow:NSShadow? 22 | 23 | func attributesWith(_ fontSize:CGFloat, paragraphSpacing:CGFloat, fontName:String? = nil) -> [NSAttributedString.Key:Any] { 24 | let style = NSMutableParagraphStyle() 25 | style.lineBreakMode = NSLineBreakMode.byWordWrapping 26 | style.paragraphSpacing = paragraphSpacing * scale.height 27 | var font = UIFont.systemFont(ofSize: fontSize * scale.height) 28 | if let name = fontName, 29 | let namedFont = UIFont(name: name, size: fontSize * scale.height) { 30 | font = namedFont 31 | } 32 | var attrs = [NSAttributedString.Key.font: font, NSAttributedString.Key.paragraphStyle: style] 33 | if let shadow = self.shadow { 34 | attrs[NSAttributedString.Key.shadow] = shadow 35 | } 36 | return attrs 37 | } 38 | 39 | // Use function instead of lazy initializer to work around a probable bug in Swift 40 | private func genAttrs() -> [String:[NSAttributedString.Key:Any]] { 41 | return [ 42 | "#": self.attributesWith(32, paragraphSpacing: 16), 43 | "##": self.attributesWith(28, paragraphSpacing: 14), 44 | "###": self.attributesWith(24, paragraphSpacing: 12), 45 | "####": self.attributesWith(22, paragraphSpacing: 11), 46 | "*": self.attributesWith(20, paragraphSpacing: 10), 47 | "-": self.attributesWith(20, paragraphSpacing: 5), 48 | "```": self.attributesWith(14, paragraphSpacing: 0, fontName: "Courier"), 49 | "```+": self.attributesWith(7, paragraphSpacing: 0, fontName: "Courier"), 50 | ] 51 | } 52 | 53 | init(info:[String:Any]?, scale:CGSize, dimension:CGSize) { 54 | self.scale = scale 55 | if let params = info { 56 | shadow = SwipeParser.parseShadow(params["shadow"], scale: scale) 57 | } 58 | attrs = genAttrs() 59 | 60 | if let markdownInfo = info, 61 | let styles = markdownInfo["styles"] as? [String:Any] { 62 | for (keyMark, value) in styles { 63 | if let attrInfo = value as? [String:Any] { 64 | var attrCopy:[NSAttributedString.Key:Any] 65 | if let attr = attrs[keyMark] { 66 | attrCopy = attr 67 | } else { 68 | attrCopy = self.attributesWith(20, paragraphSpacing: 10) 69 | } 70 | let styleCopy = NSMutableParagraphStyle() 71 | if let style = attrCopy[NSAttributedString.Key.paragraphStyle] as? NSParagraphStyle { 72 | // WARNING: copy all properties 73 | styleCopy.lineBreakMode = style.lineBreakMode 74 | styleCopy.paragraphSpacing = style.paragraphSpacing 75 | } 76 | 77 | for (keyAttr, attrValue) in attrInfo { 78 | switch(keyAttr) { 79 | case "color": 80 | // the value MUST be UIColor or NSColor, not CGColor 81 | attrCopy[NSAttributedString.Key.foregroundColor] = UIColor(cgColor:SwipeParser.parseColor(attrValue)) 82 | case "font": 83 | attrCopy[NSAttributedString.Key.font] = SwipeParser.parseFont(attrValue, scale:scale, full:dimension.height) 84 | case "prefix": 85 | if let prefix = attrValue as? String { 86 | prefixes[keyMark] = prefix 87 | } 88 | case "alignment": 89 | if let alignment = attrValue as? String { 90 | switch(alignment) { 91 | case "center": 92 | styleCopy.alignment = .center 93 | case "right": 94 | styleCopy.alignment = .right 95 | case "left": 96 | styleCopy.alignment = .left 97 | default: 98 | break 99 | } 100 | } 101 | break 102 | /* 103 | // iOS does not allow us to mix multiple shadows 104 | case "shadow": 105 | attr[NSShadowAttributeName] = SwipeParser.parseShadow(attrValue, scale: scale) 106 | */ 107 | default: 108 | break; 109 | } 110 | } 111 | attrCopy[NSAttributedString.Key.paragraphStyle] = styleCopy 112 | attrs[keyMark] = attrCopy 113 | } 114 | } 115 | } 116 | } 117 | 118 | func parse(_ markdowns:[String]) -> NSAttributedString { 119 | let strs = NSMutableAttributedString() 120 | var fCode = false 121 | for (index, markdown) in markdowns.enumerated() { 122 | var (key, body):(String?, String) = { 123 | if markdown == "```" { 124 | fCode = !fCode 125 | return fCode ? (nil, "") : ("```+", "") 126 | } else if fCode { 127 | return ("```", markdown) 128 | } else { 129 | for prefix in attrs.keys { 130 | let result = markdown.commonPrefix(with: prefix + " ", options: NSString.CompareOptions.literal) 131 | if result == prefix + " " { 132 | return (prefix, String(markdown[markdown.index(markdown.startIndex, offsetBy: prefix.count + 1)...])) 133 | } 134 | } 135 | } 136 | return ("*", markdown) 137 | }() 138 | 139 | if let keyPrefix = key { 140 | if let prefix = prefixes[keyPrefix] { 141 | body = prefix + body 142 | } 143 | body += ((index < markdowns.count - 1) ? "\n" : "") 144 | strs.append(NSMutableAttributedString(string: body, attributes: attrs[keyPrefix])) 145 | } 146 | } 147 | //NSLog("Markdown:parse \(strs)") 148 | return strs 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /core/SwipeNode.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SwipeNode.swift 3 | // 4 | // Created by Pete Stoppani on 5/19/16. 5 | // 6 | 7 | import Foundation 8 | 9 | class SwipeNode: NSObject { 10 | var children = [SwipeNode]() 11 | private(set) weak var parent:SwipeNode? 12 | let eventHandler = SwipeEventHandler() 13 | 14 | init(parent: SwipeNode? = nil) { 15 | self.parent = parent 16 | super.init() 17 | } 18 | 19 | func evaluate(_ info:[String:Any]) -> [String:Any] { 20 | var result = [String:Any]() 21 | 22 | for k in info.keys { 23 | var val = info[k] 24 | if let valInfo = val as? [String:Any], let valOfInfo = valInfo["valueOf"] as? [String:Any] { 25 | val = getValue(self, info: valOfInfo) 26 | if val == nil { 27 | val = "" 28 | } 29 | } 30 | 31 | result[k] = val 32 | } 33 | return result 34 | } 35 | 36 | func execute(_ originator: SwipeNode, actions:[SwipeAction]?) { 37 | if actions == nil { 38 | return 39 | } 40 | for action in actions! { 41 | executeAction(originator, action: action) 42 | } 43 | } 44 | 45 | func executeAction(_ originator: SwipeNode, action: SwipeAction) { 46 | if let getInfo = action.info["get"] as? [String:Any] { 47 | SwipeHttpGet.create(self, getInfo: getInfo) 48 | } else if let postInfo = action.info["post"] as? [String:Any] { 49 | SwipeHttpPost.create(self, postInfo: postInfo) 50 | } else if let timerInfo = action.info["timer"] as? [String:Any] { 51 | SwipeTimer.create(self, timerInfo: timerInfo) 52 | } else { 53 | parent?.executeAction(originator, action: action) 54 | } 55 | } 56 | 57 | func getPropertyValue(_ originator: SwipeNode, property: String) -> Any? { 58 | return self.parent?.getPropertyValue(originator, property: property) 59 | } 60 | 61 | func getPropertiesValue(_ originator: SwipeNode, info: [String:Any]) -> Any? { 62 | return self.parent?.getPropertiesValue(originator, info: info) 63 | } 64 | 65 | func getValue(_ originator: SwipeNode, info: [String:Any]) -> Any? { 66 | var up = true 67 | if let val = info["search"] as? String { 68 | up = val != "children" 69 | } 70 | 71 | if let property = info["property"] as? String { 72 | return getPropertyValue(originator, property: property) 73 | } else if let propertyInfo = info["property"] as? [String:Any] { 74 | return getPropertiesValue(originator, info: propertyInfo) 75 | } else { 76 | if up { 77 | return self.parent?.getValue(originator, info: info) 78 | } else { 79 | for c in self.children { 80 | let val = c.getValue(originator, info: info) 81 | if val != nil { 82 | return val 83 | } 84 | } 85 | } 86 | } 87 | 88 | return nil 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /core/SwipePageTemplate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SwipePageTemplate.swift 3 | // Swipe 4 | // 5 | // Created by satoshi on 9/8/15. 6 | // Copyright (c) 2015 Satoshi Nakajima. All rights reserved. 7 | // 8 | 9 | #if os(OSX) 10 | import Cocoa 11 | //public typealias UIColor = NSColor 12 | //public typealias UIFont = NSFont 13 | #else 14 | import UIKit 15 | #endif 16 | import AVFoundation 17 | 18 | private func MyLog(_ text:String, level:Int = 0) { 19 | let s_verbosLevel = 0 20 | if level <= s_verbosLevel { 21 | NSLog(text) 22 | } 23 | } 24 | 25 | class SwipePageTemplate: NSObject, AVAudioPlayerDelegate { 26 | let pageTemplateInfo:[String:Any] 27 | private let baseURL:URL? 28 | private let name:String 29 | private var bgmPlayer:AVAudioPlayer? 30 | private var fDebugEntered = false 31 | 32 | lazy var resourceURLs:[URL:String] = { 33 | var urls = [URL:String]() 34 | if let value = self.pageTemplateInfo["bgm"] as? String, 35 | let url = URL.url(value, baseURL: self.baseURL) { 36 | urls[url] = "" 37 | } 38 | return urls 39 | }() 40 | 41 | init(name:String, info:[String:Any], baseURL:URL?) { 42 | self.baseURL = baseURL 43 | self.name = name 44 | self.pageTemplateInfo = info 45 | } 46 | 47 | // This function is called when a page associated with this pageTemplate is activated (entered) 48 | // AND the previous page is NOT associated with this pageTemplate object. 49 | func didEnter(_ prefetcher:SwipePrefetcher) { 50 | assert(fDebugEntered == false, "re-entering") 51 | fDebugEntered = true 52 | 53 | if let value = self.pageTemplateInfo["bgm"] as? String, 54 | let urlRaw = URL.url(value, baseURL: baseURL), 55 | let url = prefetcher.map(urlRaw) { 56 | MyLog("SWPageTemplate didEnter with bgm=\(value)", level:1) 57 | SwipeAssetManager.sharedInstance().loadAsset(url, prefix: "", bypassCache:false, callback: { (urlLocal:URL?, _) -> Void in 58 | if self.fDebugEntered, 59 | let urlL = urlLocal, 60 | let player = try? AVAudioPlayer(contentsOf: urlL) { 61 | player.delegate = self 62 | player.play() 63 | self.bgmPlayer = player 64 | } 65 | }) 66 | } else { 67 | //NSLog("SWPageTemplate didEnter failed to create URL") 68 | } 69 | } 70 | 71 | // This function is called when a page associated with this pageTemplate is deactivated (leaved) 72 | // AND the subsequent page is not associated with this pageTemplate object. 73 | func didLeave() { 74 | assert(fDebugEntered == true, "leaving without entering") 75 | fDebugEntered = false 76 | 77 | if let player = bgmPlayer { 78 | player.stop() 79 | player.delegate = nil 80 | bgmPlayer = nil 81 | } 82 | } 83 | 84 | // We repeat the bgm 85 | func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) { 86 | if let player = bgmPlayer { 87 | if flag { 88 | player.play() 89 | } 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /core/SwipeSynthesizer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SwipeSynthesizer.swift 3 | // sample 4 | // 5 | // Created by satoshi on 9/21/15. 6 | // Copyright © 2015 Satoshi Nakajima. All rights reserved. 7 | // 8 | 9 | #if os(OSX) 10 | import Cocoa 11 | public typealias AVSpeechSynthesizer = NSSpeechSynthesizer 12 | #else 13 | import UIKit 14 | #endif 15 | 16 | import AVFoundation 17 | 18 | class SwipeSymthesizer: NSObject { 19 | private static let singleton = SwipeSymthesizer() 20 | let synth = AVSpeechSynthesizer() 21 | 22 | static func sharedInstance() -> SwipeSymthesizer { 23 | return SwipeSymthesizer.singleton 24 | } 25 | 26 | // method 27 | func synthesizer() -> AVSpeechSynthesizer { 28 | return self.synth 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /core/SwipeTextArea.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SwipeTextArea.swift 3 | // 4 | // Created by Pete Stoppani on 6/7/16. 5 | // 6 | 7 | import Foundation 8 | 9 | #if os(OSX) 10 | import Cocoa 11 | #else 12 | import UIKit 13 | #endif 14 | 15 | class SwipeTextArea: SwipeView, UITextViewDelegate { 16 | private var screenDimension = CGSize(width: 0, height: 0) 17 | private var textView: UITextView 18 | 19 | init(parent: SwipeNode, info: [String:Any], frame: CGRect, screenDimension: CGSize) { 20 | self.screenDimension = screenDimension 21 | self.textView = UITextView(frame: frame) 22 | super.init(parent: parent, info: info) 23 | self.textView.delegate = self 24 | self.textView.backgroundColor = UIColor.clear 25 | //self.textView.becomeFirstResponder() 26 | self.view = self.textView as UIView 27 | } 28 | 29 | override func setText(_ text:String, scale:CGSize, info:[String:Any], dimension:CGSize, layer:CALayer?) -> Bool { 30 | self.textView.text = text 31 | self.textView.textAlignment = NSTextAlignment.center 32 | 33 | func processAlignment(_ alignment:String) { 34 | switch(alignment) { 35 | case "center": 36 | self.textView.textAlignment = .center 37 | case "left": 38 | self.textView.textAlignment = .left 39 | case "right": 40 | self.textView.textAlignment = .right 41 | case "justified": 42 | self.textView.textAlignment = .justified 43 | default: 44 | break 45 | } 46 | } 47 | if let alignment = info["textAlign"] as? String { 48 | processAlignment(alignment) 49 | } else if let alignments = info["textAlign"] as? [String] { 50 | for alignment in alignments { 51 | processAlignment(alignment) 52 | } 53 | } 54 | let fontSize:CGFloat = { 55 | var ret = 20.0 / 480.0 * dimension.height // default 56 | if let fontSize = info["fontSize"] as? CGFloat { 57 | ret = fontSize 58 | } else if let fontSize = info["fontSize"] as? String { 59 | ret = SwipeParser.parsePercent(fontSize, full: dimension.height, defaultValue: ret) 60 | } 61 | return round(ret * scale.height) 62 | }() 63 | 64 | self.textView.font = UIFont(name: "Helvetica", size: fontSize) 65 | self.textView.textColor = UIColor(cgColor: SwipeParser.parseColor(info["textColor"], defaultColor: UIColor.black.cgColor)) 66 | 67 | parent!.execute(self, actions: parent!.eventHandler.actionsFor("textChanged")) 68 | 69 | return true 70 | } 71 | 72 | override func getPropertyValue(_ originator: SwipeNode, property: String) -> Any? { 73 | switch (property) { 74 | case "text": 75 | return self.textView.text 76 | case "text.length": 77 | return self.textView.text?.count 78 | default: 79 | return super.getPropertyValue(originator, property: property) 80 | } 81 | } 82 | 83 | override func isFirstResponder() -> Bool { 84 | return view!.isFirstResponder 85 | } 86 | 87 | // UITextViewDelegate 88 | 89 | func textViewDidChange(_ textView: UITextView) { 90 | parent!.execute(self, actions: parent!.eventHandler.actionsFor("textChanged")) 91 | } 92 | 93 | func textViewDidBeginEditing(_ textView: UITextView) { 94 | } 95 | 96 | func textViewDidEndEditing(_ textView: UITextView) { 97 | parent!.execute(self, actions: parent!.eventHandler.actionsFor("endEdit")) 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /core/SwipeTextField.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SwipeTextField.swift 3 | // 4 | // Created by Pete Stoppani on 8/24/16. 5 | // 6 | 7 | import Foundation 8 | 9 | #if os(OSX) 10 | import Cocoa 11 | #else 12 | import UIKit 13 | #endif 14 | 15 | class SwipeTextField: SwipeView, UITextFieldDelegate { 16 | private var screenDimension = CGSize(width: 0, height: 0) 17 | 18 | class InternalTextField: UITextField { 19 | weak var wrapper: SwipeTextField? 20 | 21 | init(wrapper: SwipeTextField?, frame: CGRect) { 22 | super.init(frame: frame) 23 | self.wrapper = wrapper 24 | } 25 | 26 | required init?(coder aDecoder: NSCoder) { 27 | fatalError("init(coder:) has not been implemented") 28 | } 29 | 30 | override var canBecomeFocused: Bool { 31 | if let wrapper = self.wrapper, let parent = wrapper.parent as? SwipeView { 32 | return parent.fFocusable 33 | } else { 34 | return super.canBecomeFocused 35 | } 36 | } 37 | 38 | override func didUpdateFocus(in context: UIFocusUpdateContext, with coordinator: UIFocusAnimationCoordinator) { 39 | if let wrapper = self.wrapper, let parent = wrapper.parent as? SwipeView { 40 | // lostFocus must be fired before gainedFocus 41 | if self == context.previouslyFocusedView { 42 | if let actions = parent.eventHandler.actionsFor("lostFocus") { 43 | parent.execute(parent, actions: actions) 44 | } 45 | } 46 | if self == context.nextFocusedView { 47 | if let actions = parent.eventHandler.actionsFor("gainedFocus") { 48 | parent.execute(parent, actions: actions) 49 | } 50 | } 51 | } else { 52 | super.didUpdateFocus(in: context, with: coordinator) 53 | } 54 | } 55 | } 56 | 57 | var textView: InternalTextField? 58 | 59 | init(parent: SwipeView, info: [String:Any], frame: CGRect, screenDimension: CGSize) { 60 | self.screenDimension = screenDimension 61 | super.init(parent: parent, info: info) 62 | self.textView = InternalTextField(wrapper: self, frame: frame) 63 | self.textView!.delegate = self 64 | self.textView!.backgroundColor = UIColor.clear 65 | self.view = self.textView! as UIView 66 | } 67 | 68 | override func setText(_ text:String, scale:CGSize, info:[String:Any], dimension:CGSize, layer:CALayer?) -> Bool { 69 | if let textView = self.textView { 70 | textView.text = text 71 | textView.textAlignment = NSTextAlignment.center 72 | 73 | func processAlignment(_ alignment:String) { 74 | switch(alignment) { 75 | case "center": 76 | textView.textAlignment = .center 77 | case "left": 78 | textView.textAlignment = .left 79 | case "right": 80 | textView.textAlignment = .right 81 | case "justified": 82 | textView.textAlignment = .justified 83 | default: 84 | break 85 | } 86 | } 87 | if let alignment = info["textAlign"] as? String { 88 | processAlignment(alignment) 89 | } else if let alignments = info["textAlign"] as? [String] { 90 | for alignment in alignments { 91 | processAlignment(alignment) 92 | } 93 | } 94 | let fontSize:CGFloat = { 95 | var ret = 20.0 / 480.0 * dimension.height // default 96 | if let fontSize = info["fontSize"] as? CGFloat { 97 | ret = fontSize 98 | } else if let fontSize = info["fontSize"] as? String { 99 | ret = SwipeParser.parsePercent(fontSize, full: dimension.height, defaultValue: ret) 100 | } 101 | return round(ret * scale.height) 102 | }() 103 | 104 | textView.font = UIFont(name: "Helvetica", size: fontSize) 105 | textView.textColor = UIColor(cgColor: SwipeParser.parseColor(info["textColor"], defaultColor: UIColor.black.cgColor)) 106 | 107 | parent!.execute(self, actions: parent!.eventHandler.actionsFor("textChanged")) 108 | } 109 | 110 | return true 111 | } 112 | 113 | override func getPropertyValue(_ originator: SwipeNode, property: String) -> Any? { 114 | switch (property) { 115 | case "text": 116 | return self.textView!.text 117 | case "text.length": 118 | return self.textView?.text?.count 119 | default: 120 | return super.getPropertyValue(originator, property: property) 121 | } 122 | } 123 | 124 | override func isFirstResponder() -> Bool { 125 | return view!.isFirstResponder 126 | } 127 | 128 | // UITextViewDelegate 129 | 130 | func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { 131 | DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.01) { 132 | self.parent!.execute(self, actions: self.parent!.eventHandler.actionsFor("textChanged")) 133 | } 134 | return true 135 | } 136 | 137 | func textFieldDidBeginEditing(_ textField: UITextField) { 138 | } 139 | 140 | func textFieldDidEndEditing(_ textField: UITextField) { 141 | parent!.execute(self, actions: parent!.eventHandler.actionsFor("endEdit")) 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /core/SwipeTimer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SwipeTimer.swift 3 | // 4 | // Created by Pete Stoppani on 5/23/16. 5 | // 6 | 7 | import Foundation 8 | 9 | class SwipeTimer : SwipeNode { 10 | static var timers = [SwipeTimer]() 11 | var timer: Timer? 12 | var repeats = false 13 | 14 | static func create(_ parent: SwipeNode, timerInfo: [String:Any]) { 15 | let timer = SwipeTimer(parent: parent, timerInfo: timerInfo) 16 | timers.append(timer) 17 | } 18 | 19 | init(parent: SwipeNode, timerInfo: [String:Any]) { 20 | super.init(parent: parent) 21 | var duration = 0.2 22 | if let value = timerInfo["duration"] as? Double { 23 | duration = value 24 | } 25 | if let value = timerInfo["repeats"] as? Bool { 26 | repeats = value 27 | } 28 | if let eventsInfo = timerInfo["events"] as? [String:Any] { 29 | eventHandler.parse(eventsInfo) 30 | 31 | self.timer = Timer.scheduledTimer(timeInterval: duration, target:self, selector: #selector(SwipeTimer.didTimerTick(_:)), userInfo: nil, repeats: repeats) 32 | } 33 | } 34 | 35 | func cancel() { 36 | self.timer?.invalidate() 37 | self.timer = nil 38 | } 39 | 40 | static func cancelAll() { 41 | for timer in timers { 42 | timer.cancel() 43 | } 44 | 45 | timers.removeAll() 46 | } 47 | 48 | @objc func didTimerTick(_ timer: Timer) { 49 | if !timer.isValid { 50 | return 51 | } 52 | 53 | if let actions = eventHandler.actionsFor("tick") { 54 | execute(self, actions:actions) 55 | } 56 | 57 | if !repeats { 58 | cancel() 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /core/SwipeView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SwipeView.swift 3 | // 4 | // Created by Pete Stoppani on 5/19/16. 5 | // 6 | 7 | import Foundation 8 | #if os(OSX) 9 | import Cocoa 10 | public typealias UIView = NSView 11 | public typealias UIButton = NSButton 12 | public typealias UIScreen = NSScreen 13 | #else 14 | import UIKit 15 | #endif 16 | 17 | protocol SwipeViewDelegate: NSObjectProtocol { 18 | func addedResourceURLs(_ urls:[URL:String], callback:@escaping () -> Void) 19 | } 20 | 21 | class SwipeView: SwipeNode { 22 | 23 | internal var info = [String:Any]() 24 | internal var fEnabled = true 25 | internal var fFocusable = false 26 | 27 | class InternalView: UIView { 28 | weak var wrapper: SwipeView? 29 | 30 | init(wrapper: SwipeView?, frame: CGRect) { 31 | super.init(frame: frame) 32 | self.wrapper = wrapper 33 | } 34 | 35 | required init?(coder aDecoder: NSCoder) { 36 | fatalError("init(coder:) has not been implemented") 37 | } 38 | 39 | override var canBecomeFocused: Bool { 40 | if let element = self.wrapper as? SwipeElement, let _ = element.helper?.view { 41 | return false 42 | } else if let wrapper = self.wrapper { 43 | return wrapper.fFocusable 44 | } else { 45 | return super.canBecomeFocused 46 | } 47 | } 48 | 49 | override func didUpdateFocus(in context: UIFocusUpdateContext, with coordinator: UIFocusAnimationCoordinator) { 50 | if let wrapper = self.wrapper { 51 | // lostFocus must be fired before gainedFocus 52 | if let actions = wrapper.eventHandler.actionsFor("lostFocus"), self == context.previouslyFocusedView { 53 | wrapper.execute(wrapper, actions: actions) 54 | } 55 | if let actions = wrapper.eventHandler.actionsFor("gainedFocus"), self == context.nextFocusedView { 56 | wrapper.execute(wrapper, actions: actions) 57 | } 58 | } else { 59 | super.didUpdateFocus(in: context, with: coordinator) 60 | } 61 | } 62 | } 63 | var view: UIView? 64 | 65 | init(info: [String:Any]) { 66 | self.info = info 67 | super.init() 68 | } 69 | 70 | init(parent: SwipeNode, info: [String:Any]) { 71 | self.info = info 72 | super.init(parent: parent) 73 | } 74 | 75 | func setupGestureRecognizers() { 76 | var doubleTapRecognizer: UITapGestureRecognizer? 77 | 78 | if eventHandler.actionsFor("doubleTapped") != nil { 79 | doubleTapRecognizer = UITapGestureRecognizer(target: self, action:#selector(SwipeView.didDoubleTap(_:))) 80 | doubleTapRecognizer!.numberOfTapsRequired = 2 81 | doubleTapRecognizer!.cancelsTouchesInView = false 82 | view!.addGestureRecognizer(doubleTapRecognizer!) 83 | } 84 | 85 | let tapRecognizer = UITapGestureRecognizer(target: self, action:#selector(SwipeView.didTap(_:))) 86 | if doubleTapRecognizer != nil { 87 | tapRecognizer.require(toFail: doubleTapRecognizer!) 88 | } 89 | tapRecognizer.cancelsTouchesInView = false 90 | view!.addGestureRecognizer(tapRecognizer) 91 | } 92 | 93 | lazy var name:String = { 94 | if let value = self.info["id"] as? String { 95 | return value 96 | } 97 | return "" // default 98 | }() 99 | 100 | lazy var data:Any = { 101 | if let value = self.info["data"] as? String { 102 | return value 103 | } else if let value = self.info["data"] as? [String:Any] { 104 | return value 105 | } 106 | return "" // default 107 | }() 108 | 109 | func endEditing() { 110 | if let view = self.view { 111 | let ended = view.endEditing(true) 112 | if !ended { 113 | if let p = self.parent as? SwipeView { 114 | p.endEditing() 115 | } 116 | } 117 | } 118 | } 119 | 120 | func setText(_ text:String, scale:CGSize, info:[String:Any], dimension:CGSize, layer:CALayer?) -> Bool { 121 | return false 122 | } 123 | 124 | func tapped() { 125 | if let p = self.parent as? SwipeView { 126 | p.tapped() 127 | } 128 | } 129 | 130 | private func completeTap() { 131 | endEditing() 132 | tapped() 133 | } 134 | 135 | @objc func didTap(_ recognizer: UITapGestureRecognizer) { 136 | if let actions = eventHandler.actionsFor("tapped"), fEnabled { 137 | execute(self, actions: actions) 138 | completeTap() 139 | } else if let p = self.parent as? SwipeView { 140 | p.didTap(recognizer) 141 | // parent will completeTap() 142 | } else { 143 | completeTap() 144 | } 145 | } 146 | 147 | @objc func didDoubleTap(_ recognizer: UITapGestureRecognizer) { 148 | if let actions = eventHandler.actionsFor("doubleTapped"), fEnabled { 149 | execute(self, actions: actions) 150 | } 151 | } 152 | 153 | override func executeAction(_ originator: SwipeNode, action: SwipeAction) { 154 | if let updateInfo = action.info["update"] as? [String:Any] { 155 | var name = "*"; // default is 'self' 156 | if let value = updateInfo["id"] as? String { 157 | name = value 158 | } 159 | var up = true 160 | if let value = updateInfo["search"] as? String { 161 | up = value != "children" 162 | } 163 | _ = updateElement(originator, name:name, up:up, info: updateInfo) 164 | } else if let appendInfo = action.info["append"] as? [String:Any] { 165 | var name = "*"; // default is 'self' 166 | if let value = appendInfo["id"] as? String { 167 | name = value 168 | } 169 | var up = true 170 | if let value = appendInfo["search"] as? String { 171 | up = value != "children" 172 | } 173 | _ = appendList(originator, name:name, up:up, info: appendInfo) 174 | } else { 175 | super.executeAction(originator, action: action) 176 | } 177 | } 178 | 179 | func updateElement(_ originator: SwipeNode, name: String, up: Bool, info: [String:Any]) -> Bool { 180 | return false 181 | } 182 | 183 | override func getPropertyValue(_ originator: SwipeNode, property: String) -> Any? { 184 | switch (property) { 185 | case "data": 186 | return self.data 187 | case "screenX": 188 | return self.view!.superview?.convert(self.view!.frame.origin, to: nil).x 189 | case "screenY": 190 | return self.view!.superview?.convert(self.view!.frame.origin, to: nil).y 191 | case "x": 192 | return self.view!.frame.origin.x 193 | case "y": 194 | return self.view!.frame.origin.y 195 | case "w": 196 | return self.view!.frame.size.width 197 | case "h": 198 | return self.view!.frame.size.height 199 | default: 200 | return nil 201 | } 202 | } 203 | func appendList(_ originator: SwipeNode, name: String, up: Bool, info: [String:Any]) -> Bool { 204 | return false 205 | } 206 | 207 | func appendList(_ originator: SwipeNode, info: [String:Any]) { 208 | } 209 | 210 | func isFirstResponder() -> Bool { 211 | return false 212 | } 213 | 214 | func findFirstResponder() -> SwipeView? { 215 | if self.isFirstResponder() { 216 | return self 217 | } 218 | 219 | for c in self.children { 220 | if let e = c as? SwipeElement { 221 | if let fr = e.findFirstResponder() { 222 | return fr 223 | } 224 | } 225 | } 226 | 227 | return nil; 228 | } 229 | 230 | } 231 | -------------------------------------------------------------------------------- /network/SNNotificationManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SNNotificationManager.swift 3 | // 4 | // Created by satoshi on 3/30/15. 5 | // Copyright (c) 2015 Satoshi Nakajima. All rights reserved. 6 | // 7 | 8 | #if os(OSX) 9 | import Cocoa 10 | #else 11 | import UIKit 12 | #endif 13 | 14 | class SNNotificationManager { 15 | var observers = [NSObjectProtocol]() 16 | 17 | deinit { 18 | clear() 19 | } 20 | 21 | func addObserver(forName name: Notification.Name?, object obj: Any?, queue: OperationQueue?, using block: @escaping (Notification) -> Void) { 22 | let observer = NotificationCenter.default.addObserver(forName: name, object: obj, queue: queue, using: block) 23 | observers.append(observer) 24 | } 25 | 26 | func clear() { 27 | for observer in observers { 28 | NotificationCenter.default.removeObserver(observer) 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /network/SwipeConnection.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SwipeConnection.swift 3 | // sample 4 | // 5 | // Created by satoshi on 10/9/15. 6 | // Copyright © 2015 Satoshi Nakajima. All rights reserved. 7 | // 8 | 9 | #if os(OSX) 10 | import Cocoa 11 | #else 12 | import UIKit 13 | #endif 14 | 15 | import CoreData 16 | 17 | private func MyLog(_ text:String, level:Int = 0) { 18 | let s_verbosLevel = 0 19 | if level <= s_verbosLevel { 20 | NSLog(text) 21 | } 22 | } 23 | 24 | class SwipeConnection: NSObject { 25 | private static var connections = [URL:SwipeConnection]() 26 | static let session:URLSession = { 27 | let config = URLSessionConfiguration.default 28 | config.urlCache = nil // disable cache by URLSession (because we do) 29 | return URLSession(configuration: config, delegate: nil, delegateQueue: OperationQueue.main) 30 | }() 31 | static func connection(_ url:URL, urlLocal:URL, entity:NSManagedObject) -> SwipeConnection { 32 | if let connection = connections[url] { 33 | return connection 34 | } 35 | let connection = SwipeConnection(url: url, urlLocal: urlLocal, entity:entity) 36 | //connection.start() 37 | let session = SwipeConnection.session 38 | let start = Date() 39 | let task = session.downloadTask(with: url) { (urlTemp:URL?, res:URLResponse?, error:Swift.Error?) -> Void in 40 | assert(Thread.current == Thread.main, "thread error") 41 | let duration = Date().timeIntervalSince(start) 42 | if let urlT = urlTemp { 43 | if let httpRes = res as? HTTPURLResponse { 44 | if httpRes.statusCode == 200 { 45 | let fm = FileManager.default 46 | do { 47 | let attr = try fm.attributesOfItem(atPath: urlT.path) 48 | if let size = attr[FileAttributeKey.size] as? Int { 49 | connection.fileSize = size 50 | SwipeAssetManager.sharedInstance().wasFileLoaded(connection) 51 | } 52 | } catch { 53 | MyLog("SWConn failed to get attributes (but ignored)") 54 | } 55 | MyLog("SWConn loaded \(url.lastPathComponent) in \(duration)s (\(connection.fileSize))", level:1) 56 | do { 57 | if fm.fileExists(atPath: urlLocal.path) { 58 | try fm.removeItem(at: urlLocal) 59 | } 60 | try fm.copyItem(at: urlT, to: urlLocal) 61 | } catch { 62 | connection.callbackAll(error as NSError) 63 | return 64 | } 65 | } else { 66 | MyLog("SWConn HTTP error (\(url.lastPathComponent), \(httpRes.statusCode))") 67 | connection.callbackAll(NSError(domain: NSURLErrorDomain, code: httpRes.statusCode, userInfo: nil)) 68 | return 69 | } 70 | } else { 71 | MyLog("SWConn no HTTPURLResponse, something is wrong!") 72 | } 73 | } else { 74 | MyLog("SWConn network error (\(url.lastPathComponent), \(String(describing: error)))") 75 | } 76 | connection.callbackAll(error as NSError?) 77 | } 78 | task.resume() 79 | return connection 80 | } 81 | let url, urlLocal:URL 82 | let entity:NSManagedObject 83 | var callbacks = Array<(NSError?) -> Void>() 84 | var fileSize = 0 85 | 86 | private init(url:URL, urlLocal:URL, entity:NSManagedObject) { 87 | self.url = url 88 | self.urlLocal = urlLocal 89 | self.entity = entity 90 | super.init() 91 | SwipeConnection.connections[url] = self 92 | } 93 | deinit { 94 | //MyLog("SWCon deinit \(url.lastPathComponent)") 95 | } 96 | 97 | func load(_ callback:@escaping (NSError?) -> Void) { 98 | callbacks.append(callback) 99 | } 100 | 101 | func callbackAll(_ error: NSError?) { 102 | SwipeConnection.connections.removeValue(forKey: self.url) 103 | for callback in callbacks { 104 | callback(error) 105 | } 106 | } 107 | 108 | } 109 | -------------------------------------------------------------------------------- /network/SwipePrefetcher.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SwipePrefetcher.swift 3 | // sample 4 | // 5 | // Created by satoshi on 10/12/15. 6 | // Copyright © 2015 Satoshi Nakajima. All rights reserved. 7 | // 8 | 9 | #if os(OSX) 10 | import Cocoa 11 | #else 12 | import UIKit 13 | #endif 14 | 15 | 16 | private func MyLog(_ text:String, level:Int = 0) { 17 | let s_verbosLevel = 0 18 | if level <= s_verbosLevel { 19 | NSLog(text) 20 | } 21 | } 22 | 23 | class SwipePrefetcher { 24 | private var urls = [URL:String]() 25 | private var urlsFetching = [URL]() 26 | private var urlsFetched = [URL:URL]() 27 | private var urlsFailed = [URL]() 28 | private var errors = [NSError]() 29 | private var fComplete = false 30 | private var _progress = Float(0) 31 | 32 | var progress:Float { 33 | return _progress 34 | } 35 | 36 | init(urls:[URL:String]) { 37 | self.urls = urls 38 | } 39 | 40 | func start(_ callback:@escaping (Bool, [URL], [NSError]) -> Void) { 41 | if fComplete { 42 | MyLog("SWPrefe already completed", level:1) 43 | callback(true, self.urlsFailed, self.errors) 44 | return 45 | } 46 | 47 | let manager = SwipeAssetManager.sharedInstance() 48 | var count = 0 49 | _progress = 0 50 | let fileManager = FileManager.default 51 | for (url,prefix) in urls { 52 | if url.scheme == "file" { 53 | if fileManager.fileExists(atPath: url.path) { 54 | urlsFetched[url] = url 55 | } else { 56 | // On-demand resource support 57 | urlsFetched[url] = Bundle.main.url(forResource: url.lastPathComponent, withExtension: nil) 58 | MyLog("SWPrefe onDemand resource at \(String(describing:urlsFetched[url])) instead of \(url)", level:1) 59 | } 60 | } else { 61 | count += 1 62 | urlsFetching.append(url) 63 | manager.loadAsset(url, prefix: prefix, bypassCache:false, callback: { (urlLocal:URL?, error:NSError?) -> Void in 64 | if let urlL = urlLocal { 65 | self.urlsFetched[url] = urlL 66 | } else { 67 | self.urlsFailed.append(url) 68 | if let error = error { 69 | self.errors.append(error) 70 | } 71 | } 72 | count -= 1 73 | if (count == 0) { 74 | self.fComplete = true 75 | self._progress = 1 76 | MyLog("SWPrefe completed \(self.urlsFetched.count)", level: 1) 77 | callback(true, self.urlsFailed, self.errors) 78 | } else { 79 | self._progress = Float(self.urls.count - count) / Float(self.urls.count) 80 | callback(false, self.urlsFailed, self.errors) 81 | } 82 | }) 83 | } 84 | } 85 | if count == 0 { 86 | self.fComplete = true 87 | self._progress = 1 88 | callback(true, urlsFailed, errors) 89 | } 90 | } 91 | 92 | func append(_ urls:[URL:String], callback:@escaping (Bool, [URL], [NSError]) -> Void) { 93 | let manager = SwipeAssetManager.sharedInstance() 94 | var count = 0 95 | _progress = 0 96 | let fileManager = FileManager.default 97 | for (url,prefix) in urls { 98 | self.urls[url] = prefix 99 | if url.scheme == "file" { 100 | if fileManager.fileExists(atPath: url.path) { 101 | urlsFetched[url] = url 102 | } else { 103 | // On-demand resource support 104 | urlsFetched[url] = Bundle.main.url(forResource: url.lastPathComponent, withExtension: nil) 105 | MyLog("SWPrefe onDemand resource at \(String(describing: urlsFetched[url])) instead of \(url)", level:1) 106 | } 107 | } else { 108 | count += 1 109 | urlsFetching.append(url) 110 | manager.loadAsset(url, prefix: prefix, bypassCache:false, callback: { (urlLocal:URL?, error:NSError?) -> Void in 111 | if let urlL = urlLocal { 112 | self.urlsFetched[url] = urlL 113 | } else if let error = error { 114 | self.urlsFailed.append(url) 115 | self.errors.append(error) 116 | } 117 | count -= 1 118 | if (count == 0) { 119 | self.fComplete = true 120 | self._progress = 1 121 | MyLog("SWPrefe completed \(self.urlsFetched.count)", level: 1) 122 | callback(true, self.urlsFailed, self.errors) 123 | } else { 124 | self._progress = Float(self.urls.count - count) / Float(self.urls.count) 125 | callback(false, self.urlsFailed, self.errors) 126 | } 127 | }) 128 | } 129 | } 130 | if count == 0 { 131 | self.fComplete = true 132 | self._progress = 1 133 | callback(true, urlsFailed, errors) 134 | } 135 | } 136 | func map(_ url:URL) -> URL? { 137 | return urlsFetched[url] 138 | } 139 | 140 | static func extensionForType(_ memeType:String) -> String { 141 | let ext:String 142 | if memeType == "video/quicktime" { 143 | ext = ".mov" 144 | } else if memeType == "video/mp4" { 145 | ext = ".mp4" 146 | } else { 147 | ext = "" 148 | } 149 | return ext 150 | } 151 | 152 | static func isMovie(_ mimeType:String) -> Bool { 153 | return mimeType == "video/quicktime" || mimeType == "video/mp4" 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /network/asset.xcdatamodeld/asset.xcdatamodel/contents: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /sample/bug_repro/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // bug_repro 4 | // 5 | // Created by satoshi on 8/23/16. 6 | // Copyright © 2016 Satoshi Nakajima. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | 17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey : Any]? = nil) -> Bool { 18 | // Override point for customization after application launch. 19 | return true 20 | } 21 | 22 | func applicationWillResignActive(_ application: UIApplication) { 23 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 24 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 25 | } 26 | 27 | func applicationDidEnterBackground(_ application: UIApplication) { 28 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 29 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 30 | } 31 | 32 | func applicationWillEnterForeground(_ application: UIApplication) { 33 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 34 | } 35 | 36 | func applicationDidBecomeActive(_ application: UIApplication) { 37 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 38 | } 39 | 40 | func applicationWillTerminate(_ _ application: UIApplication) { 41 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 42 | } 43 | 44 | 45 | } 46 | 47 | -------------------------------------------------------------------------------- /sample/bug_repro/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | } 33 | ], 34 | "info" : { 35 | "version" : 1, 36 | "author" : "xcode" 37 | } 38 | } -------------------------------------------------------------------------------- /sample/bug_repro/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /sample/bug_repro/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /sample/bug_repro/IMG_9401.mov: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swipe-org/swipe/3529d5a1fa808c0885942e521798895bcf6199b7/sample/bug_repro/IMG_9401.mov -------------------------------------------------------------------------------- /sample/bug_repro/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | 33 | UISupportedInterfaceOrientations 34 | 35 | UIInterfaceOrientationPortrait 36 | UIInterfaceOrientationLandscapeLeft 37 | UIInterfaceOrientationLandscapeRight 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /sample/bug_repro/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // bug_repro 4 | // 5 | // Created by satoshi on 8/23/16. 6 | // Copyright © 2016 Satoshi Nakajima. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import AVFoundation 11 | 12 | // 13 | // I reported this bug to Apple on August 25, 2016. Bug# 27982201. 14 | // 15 | class ViewController: UIViewController { 16 | @IBOutlet var viewMain:UIView! 17 | 18 | override func viewDidLoad() { 19 | super.viewDidLoad() 20 | 21 | if let urlVideo = Bundle.main.url(forResource: "IMG_9401", withExtension: "mov") { 22 | let videoPlayer = AVPlayer(playerItem: AVPlayerItem(url: urlVideo)) 23 | let videoLayer = AVPlayerLayer(player: videoPlayer) 24 | videoLayer.frame = viewMain.bounds 25 | viewMain.layer.addSublayer(videoLayer) 26 | videoPlayer.play() 27 | } 28 | } 29 | 30 | @IBAction func test() { 31 | UIGraphicsBeginImageContext(view.frame.size) 32 | if let layer = viewMain.layer.presentation() { 33 | layer.render(in: UIGraphicsGetCurrentContext()!) 34 | } 35 | UIGraphicsEndImageContext() 36 | } 37 | } 38 | 39 | -------------------------------------------------------------------------------- /sample/sample.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /sample/sample.xcodeproj/project.xcworkspace/xcuserdata/satoshi.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swipe-org/swipe/3529d5a1fa808c0885942e521798895bcf6199b7/sample/sample.xcodeproj/project.xcworkspace/xcuserdata/satoshi.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /sample/sample.xcodeproj/xcshareddata/xcschemes/sample.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 37 | 38 | 39 | 40 | 41 | 42 | 52 | 54 | 60 | 61 | 62 | 63 | 69 | 71 | 77 | 78 | 79 | 80 | 82 | 83 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /sample/sample.xcodeproj/xcshareddata/xcschemes/sampletv.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 37 | 38 | 39 | 40 | 41 | 42 | 52 | 54 | 60 | 61 | 62 | 63 | 69 | 71 | 77 | 78 | 79 | 80 | 82 | 83 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /sample/sample.xcodeproj/xcuserdata/satoshi.xcuserdatad/xcschemes/sample.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 37 | 38 | 39 | 40 | 41 | 42 | 52 | 54 | 60 | 61 | 62 | 63 | 69 | 71 | 77 | 78 | 79 | 80 | 82 | 83 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /sample/sample.xcodeproj/xcuserdata/satoshi.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | bug_repro.xcscheme 8 | 9 | orderHint 10 | 3 11 | 12 | sample.xcscheme 13 | 14 | orderHint 15 | 0 16 | 17 | sample.xcscheme_^#shared#^_ 18 | 19 | orderHint 20 | 1 21 | 22 | sampletv.xcscheme 23 | 24 | orderHint 25 | 1 26 | 27 | sampletv.xcscheme_^#shared#^_ 28 | 29 | orderHint 30 | 2 31 | 32 | 33 | SuppressBuildableAutocreation 34 | 35 | 7473AC481D6D3E5600DA34B1 36 | 37 | primary 38 | 39 | 40 | 74EA71761BD047F0006808EA 41 | 42 | primary 43 | 44 | 45 | 74EA71E01BD0B0EE006808EA 46 | 47 | primary 48 | 49 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /sample/sample/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // sample 4 | // 5 | // Created by satoshi on 10/15/15. 6 | // Copyright © 2015 Satoshi Nakajima. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | internal func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool { 17 | // Override point for customization after application launch. 18 | return true 19 | } 20 | 21 | func applicationWillResignActive(_ application: UIApplication) { 22 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 23 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 24 | } 25 | 26 | func applicationDidEnterBackground(_ application: UIApplication) { 27 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 28 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 29 | } 30 | 31 | func applicationWillEnterForeground(_ application: UIApplication) { 32 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 33 | } 34 | 35 | func applicationDidBecomeActive(_ application: UIApplication) { 36 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 37 | } 38 | 39 | func applicationWillTerminate(_ application: UIApplication) { 40 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 41 | } 42 | 43 | func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask { 44 | return .landscapeLeft 45 | } 46 | } 47 | 48 | -------------------------------------------------------------------------------- /sample/sample/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | } 33 | ], 34 | "info" : { 35 | "version" : 1, 36 | "author" : "xcode" 37 | } 38 | } -------------------------------------------------------------------------------- /sample/sample/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /sample/sample/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | -------------------------------------------------------------------------------- /sample/sample/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | NSAppTransportSecurity 26 | 27 | NSAllowsArbitraryLoads 28 | 29 | 30 | UILaunchStoryboardName 31 | LaunchScreen 32 | UIMainStoryboardFile 33 | Main 34 | UIRequiredDeviceCapabilities 35 | 36 | armv7 37 | 38 | UIRequiresFullScreen 39 | 40 | UISupportedInterfaceOrientations 41 | 42 | UIInterfaceOrientationPortrait 43 | UIInterfaceOrientationLandscapeLeft 44 | UIInterfaceOrientationLandscapeRight 45 | 46 | NSPhotoLibraryUsageDescription 47 | To save generated movie files. 48 | 49 | 50 | -------------------------------------------------------------------------------- /sample/sample/SwipeBrowser+ex.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SwipeBrowser+ex.swift 3 | // sample 4 | // 5 | // Created by satoshi on 8/23/16. 6 | // Copyright © 2016 Satoshi Nakajima. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | extension SwipeBrowser { 12 | @IBAction func export() { 13 | let alert = UIAlertController(title: nil, message: nil, preferredStyle: UIAlertController.Style.actionSheet) 14 | alert.popoverPresentationController?.sourceView = btnExport 15 | alert.addAction(UIAlertAction(title: "Cancel".localized, style: .cancel, handler:nil)) 16 | alert.addAction(UIAlertAction(title: "Movie".localized, style: .default) { 17 | (_:UIAlertAction) -> Void in 18 | self.exportAsMovie() 19 | }) 20 | alert.addAction(UIAlertAction(title: "GIF Animation".localized, style: .default) { 21 | (_:UIAlertAction) -> Void in 22 | self.exportAsGifAnimation() 23 | }) 24 | self.present(alert, animated: true, completion: nil) 25 | } 26 | 27 | func exportAsGifAnimation() { 28 | guard let swipeVC = controller as? SwipeViewController else { 29 | return 30 | } 31 | let docURL = try! FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true) 32 | let fileURL = docURL.appendingPathComponent("swipe.gif") 33 | 34 | self.viewLoading?.alpha = 1.0 35 | self.labelLoading?.text = "Exporting as a GIF animation...".localized 36 | let exporter = SwipeExporter(swipeViewController: swipeVC, fps:4) 37 | exporter.exportAsGifAnimation(fileURL, startPage: swipeVC.book.pageIndex, pageCount: 3) { (complete, error) -> Void in 38 | self.progress?.progress = Float(exporter.progress) 39 | if complete { 40 | print("GIF animation export done") 41 | UIView.animate(withDuration: 0.2, animations: { () -> Void in 42 | self.viewLoading?.alpha = 0.0 43 | }, completion: { (_:Bool) -> Void in 44 | let activity = UIActivityViewController(activityItems: [fileURL], applicationActivities: nil) 45 | activity.popoverPresentationController?.sourceView = self.btnExport 46 | self.present(activity, animated: true, completion: nil) 47 | }) 48 | } else if let error = error { 49 | self.viewLoading?.alpha = 0.0 50 | print("Error", error) 51 | } else { 52 | print("progress", exporter.progress) 53 | } 54 | } 55 | } 56 | 57 | func exportAsMovie() { 58 | guard let swipeVC = controller as? SwipeViewController else { 59 | return 60 | } 61 | let docURL = try! FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true) 62 | let fileURL = docURL.appendingPathComponent("swipe.mov") 63 | 64 | self.viewLoading?.alpha = 1.0 65 | self.labelLoading?.text = "Exporting as a movie...".localized 66 | let exporter = SwipeExporter(swipeViewController: swipeVC, fps:30, resolution:720.0) 67 | exporter.exportAsMovie(fileURL, startPage: swipeVC.book.pageIndex, pageCount: nil) { (complete, error) -> Void in 68 | self.progress?.progress = Float(exporter.progress) 69 | if complete { 70 | print("Movie export done", exporter.outputSize) 71 | UIView.animate(withDuration: 0.2, animations: { () -> Void in 72 | self.viewLoading?.alpha = 0.0 73 | }, completion: { (_:Bool) -> Void in 74 | let activity = UIActivityViewController(activityItems: [fileURL], applicationActivities: nil) 75 | activity.popoverPresentationController?.sourceView = self.btnExport 76 | self.present(activity, animated: true, completion: nil) 77 | }) 78 | } else if let error = error { 79 | self.viewLoading?.alpha = 0.0 80 | print("Error", error) 81 | } else { 82 | print("progress", exporter.progress) 83 | } 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /sample/sample/SwipeVideoController.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /sample/sample/iTunesArtwork@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swipe-org/swipe/3529d5a1fa808c0885942e521798895bcf6199b7/sample/sample/iTunesArtwork@2x.png -------------------------------------------------------------------------------- /sample/sample/more.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swipe-org/swipe/3529d5a1fa808c0885942e521798895bcf6199b7/sample/sample/more.png -------------------------------------------------------------------------------- /sample/sample/movie.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swipe-org/swipe/3529d5a1fa808c0885942e521798895bcf6199b7/sample/sample/movie.png -------------------------------------------------------------------------------- /sample/sample/nasa.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swipe-org/swipe/3529d5a1fa808c0885942e521798895bcf6199b7/sample/sample/nasa.png -------------------------------------------------------------------------------- /sample/sample/radio.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swipe-org/swipe/3529d5a1fa808c0885942e521798895bcf6199b7/sample/sample/radio.png -------------------------------------------------------------------------------- /sample/sample/speech.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swipe-org/swipe/3529d5a1fa808c0885942e521798895bcf6199b7/sample/sample/speech.png -------------------------------------------------------------------------------- /sample/sample/text.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swipe-org/swipe/3529d5a1fa808c0885942e521798895bcf6199b7/sample/sample/text.png -------------------------------------------------------------------------------- /sample/sampletv/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // sampletv 4 | // 5 | // Created by satoshi on 10/15/15. 6 | // Copyright © 2015 Satoshi Nakajima. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | 17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey : Any]? = nil) -> Bool { 18 | // Override point for customization after application launch. 19 | SwipeAssetManager.sharedInstance().flush() 20 | return true 21 | } 22 | 23 | func applicationWillResignActive(_ application: UIApplication) { 24 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 25 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 26 | } 27 | 28 | func applicationDidEnterBackground(_ application: UIApplication) { 29 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 30 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 31 | } 32 | 33 | func applicationWillEnterForeground(_ application: UIApplication) { 34 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 35 | } 36 | 37 | func applicationDidBecomeActive(_ application: UIApplication) { 38 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 39 | } 40 | 41 | func applicationWillTerminate(_ application: UIApplication) { 42 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 43 | } 44 | 45 | 46 | } 47 | 48 | -------------------------------------------------------------------------------- /sample/sampletv/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Large.imagestack/Back.imagestacklayer/Content.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "tv", 5 | "scale" : "1x" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /sample/sampletv/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Large.imagestack/Back.imagestacklayer/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /sample/sampletv/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Large.imagestack/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "layers" : [ 3 | { 4 | "filename" : "Front.imagestacklayer" 5 | }, 6 | { 7 | "filename" : "Middle.imagestacklayer" 8 | }, 9 | { 10 | "filename" : "Back.imagestacklayer" 11 | } 12 | ], 13 | "info" : { 14 | "version" : 1, 15 | "author" : "xcode" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /sample/sampletv/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Large.imagestack/Front.imagestacklayer/Content.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "tv", 5 | "scale" : "1x" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /sample/sampletv/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Large.imagestack/Front.imagestacklayer/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /sample/sampletv/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Large.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "tv", 5 | "scale" : "1x" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /sample/sampletv/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Large.imagestack/Middle.imagestacklayer/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /sample/sampletv/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Small.imagestack/Back.imagestacklayer/Content.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "tv", 5 | "scale" : "1x" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /sample/sampletv/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Small.imagestack/Back.imagestacklayer/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /sample/sampletv/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Small.imagestack/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "layers" : [ 3 | { 4 | "filename" : "Front.imagestacklayer" 5 | }, 6 | { 7 | "filename" : "Middle.imagestacklayer" 8 | }, 9 | { 10 | "filename" : "Back.imagestacklayer" 11 | } 12 | ], 13 | "info" : { 14 | "version" : 1, 15 | "author" : "xcode" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /sample/sampletv/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Small.imagestack/Front.imagestacklayer/Content.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "tv", 5 | "scale" : "1x" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /sample/sampletv/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Small.imagestack/Front.imagestacklayer/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /sample/sampletv/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Small.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "tv", 5 | "scale" : "1x" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /sample/sampletv/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Small.imagestack/Middle.imagestacklayer/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /sample/sampletv/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "assets" : [ 3 | { 4 | "size" : "1280x768", 5 | "idiom" : "tv", 6 | "filename" : "App Icon - Large.imagestack", 7 | "role" : "primary-app-icon" 8 | }, 9 | { 10 | "size" : "400x240", 11 | "idiom" : "tv", 12 | "filename" : "App Icon - Small.imagestack", 13 | "role" : "primary-app-icon" 14 | }, 15 | { 16 | "size" : "1920x720", 17 | "idiom" : "tv", 18 | "filename" : "Top Shelf Image.imageset", 19 | "role" : "top-shelf-image" 20 | } 21 | ], 22 | "info" : { 23 | "version" : 1, 24 | "author" : "xcode" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /sample/sampletv/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "tv", 5 | "scale" : "1x" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /sample/sampletv/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /sample/sampletv/Assets.xcassets/LaunchImage.launchimage/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "orientation" : "landscape", 5 | "idiom" : "tv", 6 | "extent" : "full-screen", 7 | "minimum-system-version" : "9.0", 8 | "scale" : "1x" 9 | } 10 | ], 11 | "info" : { 12 | "version" : 1, 13 | "author" : "xcode" 14 | } 15 | } -------------------------------------------------------------------------------- /sample/sampletv/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /sample/sampletv/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UIMainStoryboardFile 26 | Main 27 | UIRequiredDeviceCapabilities 28 | 29 | arm64 30 | 31 | NSAppTransportSecurity 32 | 33 | NSAllowsArbitraryLoads 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /sample/script/actions.swipe: -------------------------------------------------------------------------------- 1 | { 2 | "pages": [ 3 | { 4 | "play":"never", 5 | "elements": [ 6 | { 7 | "text": "tap me to start timer", "fontSize":20, "pos": ["50%", "33%"], "w":"90%", "h":"10%", "bc":"#fdd", "focusable":true, 8 | "events": { 9 | "tapped": { 10 | "actions": [ 11 | { 12 | "update": { 13 | "text":"tapped", 14 | "bc":"#fee", 15 | "duration":0.5, 16 | "events":{ 17 | "completion":{ 18 | "actions":[ 19 | { "update":{ "text":"tap me", "bc":"#fdd","enabled":false } } 20 | ] 21 | } 22 | } 23 | } 24 | }, 25 | { 26 | "timer": { 27 | "duration":1, "repeats":true, 28 | "events":{ 29 | "tick":{ 30 | "actions":[ 31 | { "update":{ "text":"tick", "duration":0.5, "events":{ "completion":{ "actions":[ { "update":{ "text":"tock" }}]}}}} 32 | ] 33 | } 34 | } 35 | } 36 | } 37 | ] 38 | } 39 | } 40 | } 41 | ] 42 | } 43 | ] 44 | } 45 | -------------------------------------------------------------------------------- /sample/script/bgm/HiddenAgenda.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swipe-org/swipe/3529d5a1fa808c0885942e521798895bcf6199b7/sample/script/bgm/HiddenAgenda.mp3 -------------------------------------------------------------------------------- /sample/script/bgm/MonkeysSpinningMonkeys.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swipe-org/swipe/3529d5a1fa808c0885942e521798895bcf6199b7/sample/script/bgm/MonkeysSpinningMonkeys.mp3 -------------------------------------------------------------------------------- /sample/script/bgm/TheDescent.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swipe-org/swipe/3529d5a1fa808c0885942e521798895bcf6199b7/sample/script/bgm/TheDescent.mp3 -------------------------------------------------------------------------------- /sample/script/bgm/music.swipe: -------------------------------------------------------------------------------- 1 | { 2 | "type":"net.swipe.swipe", 3 | "title": "Scenes with BGM", 4 | "resources":["bgm"], 5 | "templates": { 6 | "elements":{ 7 | "caption": { "y":"bottom", "h":"10%", "fontSize":"5%" }, 8 | }, 9 | "pages":{ 10 | "sun": { "bc":"#ff8", "bgm":"TheDescent.mp3", "elements":[ { "template":"caption", "text":"Scene 1"} ] }, 11 | "ocean": { "bc":"#77f", "bgm":"HiddenAgenda.mp3", "elements":[ { "template":"caption", "text":"Scene 2"} ] }, 12 | "orange": { "bc":"#f80", "bgm":"MonkeysSpinningMonkeys.mp3", "elements":[ { "template":"caption", "text":"Scene 3"} ] }, 13 | }, 14 | }, 15 | "pages":[ 16 | { 17 | "template":"sun", 18 | "elements":[ 19 | ] 20 | }, 21 | { 22 | "template":"sun", 23 | "elements":[ 24 | ] 25 | }, 26 | { 27 | "template":"sun", 28 | "elements":[ 29 | ] 30 | }, 31 | { 32 | "template":"ocean", 33 | "elements":[ 34 | ] 35 | }, 36 | { 37 | "template":"ocean", 38 | "elements":[ 39 | ] 40 | }, 41 | { 42 | "template":"ocean", 43 | "elements":[ 44 | ] 45 | }, 46 | { 47 | "template":"orange", 48 | "elements":[ 49 | ] 50 | }, 51 | { 52 | "template":"orange", 53 | "elements":[ 54 | ] 55 | }, 56 | { 57 | "template":"orange", 58 | "elements":[ 59 | ] 60 | }, 61 | ] 62 | } 63 | -------------------------------------------------------------------------------- /sample/script/details/details_index.swipe: -------------------------------------------------------------------------------- 1 | { 2 | "type":"net.swipe.list", 3 | "rowHeight":"10%", 4 | "languages":[ 5 | {"id": "en", "title": "English"}, 6 | {"id": "de", "title": "German"} 7 | ], 8 | "strings": { 9 | "anim": {"en":"Animation", "de": "Animation"}, 10 | "trans": {"en":"Transition Animation", "de": "Übergangsanimation"}, 11 | "loop": {"en":"Loop Animation", "de": "Schleifenanimation"} 12 | }, 13 | "sections":[ 14 | { 15 | "title":{ "ref":"anim" }, 16 | "items":[ 17 | { "url":"transition_animation.swipe", "title":{ "ref":"trans" }, "icon":"more.png" }, 18 | { "url":"loop_animation.swipe", "title":{ "ref":"loop" }, "icon":"more.png" } 19 | ] 20 | }, 21 | { 22 | "title":{ "en":"Language", "de":"Sprache" }, 23 | "items":[ 24 | { 25 | "url":"multilingual_strings.swipe", 26 | "title":{ "en":"Multilingual Strings", "de":"Mehrsprachige Strings" }, 27 | "text":{ "en":"This example has translation", "de":"Dieses beispiel hat übersetzung" }, 28 | "icon":"more.png" 29 | } 30 | ] 31 | } 32 | ] 33 | } 34 | -------------------------------------------------------------------------------- /sample/script/details/loop_animation.swipe: -------------------------------------------------------------------------------- 1 | { 2 | "elements": { 3 | "ball": { "text":"😄", "fontSize":44 }, 4 | }, 5 | "pages": [ 6 | { 7 | "elements": [ 8 | { "text":"Loop styles" } 9 | ] 10 | }, 11 | { 12 | "elements": [ 13 | { "text":"vibrate", "textAlign":"left", "y":-150 }, 14 | { "element":"ball", "y":-100, "loop":{ "style":"vibrate" } }, 15 | { "text":"blink", "textAlign":"left", "y":-50 }, 16 | { "element":"ball", "y":0, "loop":{ "style":"blink" } }, 17 | { "text":"wiggle", "textAlign":"left", "y":50 }, 18 | { "element":"ball", "y":100, "loop":{ "style":"wiggle" } }, 19 | { "text":"spin", "textAlign":"left", "y":150 }, 20 | { "element":"ball", "y":200, "loop":{ "style":"spin" } }, 21 | ] 22 | }, 23 | { 24 | "elements": [ 25 | { "text":"Loop count" } 26 | ] 27 | }, 28 | { 29 | "elements": [ 30 | { "text":"\"count\":1 (default)", "textAlign":"left", "y":-150 }, 31 | { "element":"ball", "y":-100, "loop":{ "style":"vibrate" } }, 32 | { "text":"\"count\":2", "textAlign":"left", "y":-50 }, 33 | { "element":"ball", "y":0, "loop":{ "style":"vibrate", "count":2 } }, 34 | { "text":"\"count\":4", "textAlign":"left", "y":50 }, 35 | { "element":"ball", "y":100, "loop":{ "style":"vibrate", "count":4 } }, 36 | ] 37 | }, 38 | { 39 | "elements": [ 40 | { "text":"Vibration delta" } 41 | ] 42 | }, 43 | { 44 | "elements": [ 45 | { "text":"\"delta\":10 (default)", "textAlign":"left", "y":-150 }, 46 | { "element":"ball", "y":-100, "loop":{ "style":"vibrate" } }, 47 | { "text":"\"delta\":20", "textAlign":"left", "y":-50 }, 48 | { "element":"ball", "y":0, "loop":{ "style":"vibrate", "delta":20 } }, 49 | { "text":"\"delta\":40", "textAlign":"left", "y":50 }, 50 | { "element":"ball", "y":100, "loop":{ "style":"vibrate", "delta":40 } }, 51 | ] 52 | }, 53 | { 54 | "elements": [ 55 | { "text":"Wiggle delta" } 56 | ] 57 | }, 58 | { 59 | "elements": [ 60 | { "text":"\"delta\":15 (default)", "textAlign":"left", "y":-150 }, 61 | { "element":"ball", "y":-100, "loop":{ "style":"wiggle" } }, 62 | { "text":"\"delta\":30", "textAlign":"left", "y":-50 }, 63 | { "element":"ball", "y":0, "loop":{ "style":"wiggle", "delta":30 } }, 64 | { "text":"\"delta\":60", "textAlign":"left", "y":50 }, 65 | { "element":"ball", "y":100, "loop":{ "style":"wiggle", "delta":60 } }, 66 | ] 67 | }, 68 | { 69 | "elements": [ 70 | { "text":"Spin direction" } 71 | ] 72 | }, 73 | { 74 | "elements": [ 75 | { "text":"\"clockwise\":true (default)", "textAlign":"left", "y":-50 }, 76 | { "element":"ball", "y":0, "loop":{ "style":"spin" } }, 77 | { "text":"\"clockwise\":false", "textAlign":"left", "y":50 }, 78 | { "element":"ball", "y":100, "loop":{ "style":"spin", "clockwise":false } }, 79 | ] 80 | }, 81 | ] 82 | } 83 | -------------------------------------------------------------------------------- /sample/script/details/multilingual_strings.swipe: -------------------------------------------------------------------------------- 1 | { 2 | "languages":[ 3 | {"id": "en", "title": "English"}, 4 | {"id": "de", "title": "German"}, 5 | ], 6 | "pages":[ 7 | { 8 | "strings": { 9 | "good day": {"*":"good day", "de": "Guten Tag"}, 10 | "good evening": {"*":"good evening", "de": "guten Abend"}, 11 | }, 12 | 13 | "elements":[ 14 | { "text":{"ref":"good day"}, "h":"20%", "pos":["50%", "12%"]}, 15 | { "text":{"ref":"good evening"}, "h":"20%", "pos":["50%", "34%"]}, 16 | ], 17 | }, 18 | { 19 | "elements":[ 20 | { "text":{"*":"good morning", "de": "guten Morgen"}, "h":"20%", "pos":["50%", "12%"]}, 21 | { "text":{"*":"good afternoon", "de": "guten Nachmittag"}, "h":"20%", "pos":["50%", "34%"]}, 22 | ] 23 | } 24 | ] 25 | } 26 | -------------------------------------------------------------------------------- /sample/script/details/transition_animation.swipe: -------------------------------------------------------------------------------- 1 | { 2 | "elements": { 3 | "ball": { "text":"⚽️", "textAlign":"left", "x":50 }, 4 | }, 5 | "pages": [ 6 | { 7 | "elements": [ 8 | { "text":"\"animation\": \"auto\" (default)" } 9 | ] 10 | }, 11 | { 12 | "duration": 1, 13 | "elements": [ 14 | { "text":"\"timing\": [0.0, 0.5]", "textAlign":"left", "y":-150 }, 15 | { "element":"ball", "y":-100, "to":{ "translate":[200, 0], "timing":[0.0, 0.5] } }, 16 | { "text":"\"timing\": [0.25, 0.75]", "textAlign":"left", "y":-50 }, 17 | { "element":"ball", "y":0, "to":{ "translate":[200, 0], "timing":[0.25, 0.75] } }, 18 | { "text":"\"timing\": [0.5, 1.0]", "textAlign":"left", "y":50 }, 19 | { "element":"ball", "y":100, "to":{ "translate":[200, 0], "timing":[0.5, 1.0] } }, 20 | { "text":"\"timing\": [0.0, 1.0] (default)", "textAlign":"left", "y":150 }, 21 | { "element":"ball", "y":200, "to":{ "translate":[200, 0] } }, 22 | ] 23 | }, 24 | { 25 | "elements": [ 26 | { "text":"\"animation\": \"scroll\"" } 27 | ] 28 | }, 29 | { 30 | "animation":"scroll", 31 | "elements": [ 32 | { "text":"\"timing\": [0.0, 0.5]", "textAlign":"left", "y":-150 }, 33 | { "element":"ball", "y":-100, "to":{ "translate":[200, 0], "timing":[0.0, 0.5] } }, 34 | { "text":"\"timing\": [0.25, 0.75]", "textAlign":"left", "y":-50 }, 35 | { "element":"ball", "y":0, "to":{ "translate":[200, 0], "timing":[0.25, 0.75] } }, 36 | { "text":"\"timing\": [0.5, 1.0]", "textAlign":"left", "y":50 }, 37 | { "element":"ball", "y":100, "to":{ "translate":[200, 0], "timing":[0.5, 1.0] } }, 38 | { "text":"\"timing\": [0.0, 1.0] (default)", "textAlign":"left", "y":150 }, 39 | { "element":"ball", "y":200, "to":{ "translate":[200, 0] } }, 40 | ] 41 | } 42 | ] 43 | } 44 | -------------------------------------------------------------------------------- /sample/script/espresso.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swipe-org/swipe/3529d5a1fa808c0885942e521798895bcf6199b7/sample/script/espresso.png -------------------------------------------------------------------------------- /sample/script/espresso/IMG_8152.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swipe-org/swipe/3529d5a1fa808c0885942e521798895bcf6199b7/sample/script/espresso/IMG_8152.JPG -------------------------------------------------------------------------------- /sample/script/espresso/IMG_8153.m4v: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swipe-org/swipe/3529d5a1fa808c0885942e521798895bcf6199b7/sample/script/espresso/IMG_8153.m4v -------------------------------------------------------------------------------- /sample/script/espresso/IMG_8154.m4v: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swipe-org/swipe/3529d5a1fa808c0885942e521798895bcf6199b7/sample/script/espresso/IMG_8154.m4v -------------------------------------------------------------------------------- /sample/script/espresso/IMG_8155.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swipe-org/swipe/3529d5a1fa808c0885942e521798895bcf6199b7/sample/script/espresso/IMG_8155.JPG -------------------------------------------------------------------------------- /sample/script/espresso/IMG_8156.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swipe-org/swipe/3529d5a1fa808c0885942e521798895bcf6199b7/sample/script/espresso/IMG_8156.JPG -------------------------------------------------------------------------------- /sample/script/espresso/IMG_8159.m4v: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swipe-org/swipe/3529d5a1fa808c0885942e521798895bcf6199b7/sample/script/espresso/IMG_8159.m4v -------------------------------------------------------------------------------- /sample/script/espresso/IMG_8160.m4v: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swipe-org/swipe/3529d5a1fa808c0885942e521798895bcf6199b7/sample/script/espresso/IMG_8160.m4v -------------------------------------------------------------------------------- /sample/script/espresso/IMG_8161.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swipe-org/swipe/3529d5a1fa808c0885942e521798895bcf6199b7/sample/script/espresso/IMG_8161.JPG -------------------------------------------------------------------------------- /sample/script/espresso/IMG_8162.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swipe-org/swipe/3529d5a1fa808c0885942e521798895bcf6199b7/sample/script/espresso/IMG_8162.JPG -------------------------------------------------------------------------------- /sample/script/espresso/IMG_8163.m4v: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swipe-org/swipe/3529d5a1fa808c0885942e521798895bcf6199b7/sample/script/espresso/IMG_8163.m4v -------------------------------------------------------------------------------- /sample/script/espresso/IMG_8166.m4v: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swipe-org/swipe/3529d5a1fa808c0885942e521798895bcf6199b7/sample/script/espresso/IMG_8166.m4v -------------------------------------------------------------------------------- /sample/script/espresso/IMG_8167.m4v: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swipe-org/swipe/3529d5a1fa808c0885942e521798895bcf6199b7/sample/script/espresso/IMG_8167.m4v -------------------------------------------------------------------------------- /sample/script/espresso/IMG_8168.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swipe-org/swipe/3529d5a1fa808c0885942e521798895bcf6199b7/sample/script/espresso/IMG_8168.JPG -------------------------------------------------------------------------------- /sample/script/espresso/IMG_8169.m4v: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swipe-org/swipe/3529d5a1fa808c0885942e521798895bcf6199b7/sample/script/espresso/IMG_8169.m4v -------------------------------------------------------------------------------- /sample/script/espresso/IMG_8171.m4v: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swipe-org/swipe/3529d5a1fa808c0885942e521798895bcf6199b7/sample/script/espresso/IMG_8171.m4v -------------------------------------------------------------------------------- /sample/script/espresso/espresso_machine_l.swipe: -------------------------------------------------------------------------------- 1 | { 2 | "type":"net.swipe.swipe", 3 | "title": "Breville BES870XL Quick Start Guide", 4 | "dimension":[640.0, 0.0], 5 | "orientation":"landscape", 6 | "resources":["es1"], 7 | "templates": { 8 | "pages":{ 9 | "*":{ 10 | "bc":"#333", 11 | }, 12 | }, 13 | "elements":{ 14 | "full": { "w":"fill", "h":"fill" }, 15 | "caption" : { "y":"bottom", "h":100, "x":"5%", "w":"90%" }, 16 | "play" : { "x":"center", "y":"center", "w":80, "h":80, "text":"▶︎", "fontSize":40.0, "textColor":"#fffe", 17 | "borderWidth":3.0, "borderColor":"#fffe", 18 | "bc":"#000a", "cornerRadius":40, "action":"play" }, 19 | }, 20 | }, 21 | "markdown":{ 22 | "shadow":{ "color":"black", "offset":[2,2], "blurRadius":2 }, 23 | "styles":{ 24 | "#":{ "color":"white", "alignment":"center", "font":{ "size":24 } }, 25 | "##":{ "color":"white", "font":{ "size":20 } }, 26 | }, 27 | }, 28 | "pages":[ 29 | { 30 | "elements":[ 31 | { "template":"full", "img":"IMG_8152.JPG" }, 32 | { "template":"caption", "markdown":["# Breville BES870XL","# Quick Start Guide"] }, 33 | ] 34 | }, 35 | { 36 | "elements":[ 37 | { "template":"full", "video":"IMG_8153.m4v" }, 38 | { "template":"caption", "markdown":"## 1. Attach the filter basket to the portafilter." }, 39 | { "template":"play" }, 40 | ] 41 | }, 42 | { 43 | "elements":[ 44 | { "template":"full", "video":"IMG_8154.m4v" }, 45 | { "template":"caption", "markdown":"## 2. Set the filter below the grinder, and tap the handle once." }, 46 | { "template":"play" }, 47 | ] 48 | }, 49 | { 50 | "elements":[ 51 | { "template":"full", "img":"IMG_8155.JPG" }, 52 | { "template":"caption", "markdown":"## 3. It is normal for the portafilter to appear overfilled." } 53 | ] 54 | }, 55 | { 56 | "elements":[ 57 | { "template":"full", "img":"IMG_8156.JPG" }, 58 | { "template":"caption", "markdown":"## 4. Tamp the gound coffee down firmly using 30-40lbs of pressure." } 59 | ] 60 | }, 61 | { 62 | "elements":[ 63 | { "template":"full", "video":"IMG_8159.m4v" }, 64 | { "template":"caption", "markdown":"## 5. Insert the Portafilter underneath the group head, and rotate the handle towards the center." }, 65 | { "template":"play" }, 66 | ] 67 | }, 68 | { 69 | "elements":[ 70 | { "template":"full", "video":"IMG_8160.m4v" }, 71 | { "template":"caption", "markdown":"## 6. Press the 1 CPU button once to extract a single shot of expresso at the preset volume." }, 72 | { "template":"play" }, 73 | ] 74 | }, 75 | { 76 | "elements":[ 77 | { "template":"full", "img":"IMG_8161.JPG" }, 78 | { "template":"caption", "markdown":"## 7. The espresso will start flow after 4-7 seconds and should be the consistency of driping honey." } 79 | ] 80 | }, 81 | { 82 | "elements":[ 83 | { "template":"full", "img":"IMG_8162.JPG" }, 84 | { "template":"caption", "markdown":"## 8. Fill the jug with milk just below the \"V\" at the bottom of the spout." }, 85 | ] 86 | }, 87 | { 88 | "elements":[ 89 | { "template":"full", "video":"IMG_8163.m4v" }, 90 | { "template":"caption", "markdown":"## 9. Set the STEAM/HOT WATER dial to the STEAM position." }, 91 | { "template":"play" }, 92 | ] 93 | }, 94 | { 95 | "elements":[ 96 | { "template":"full", "video":"IMG_8171.m4v" }, 97 | { "template":"caption", "markdown":"## 10. With the milk spinning, slowly lower the jug to introduce air into the milk." }, 98 | { "template":"play" }, 99 | ] 100 | }, 101 | { 102 | "elements":[ 103 | { "template":"full", "video":"IMG_8166.m4v" }, 104 | { "template":"caption", "markdown":"## 11. Tap the jug on the table to collapse large bubbles." }, 105 | { "template":"play" }, 106 | ] 107 | }, 108 | { 109 | "elements":[ 110 | { "template":"full", "video":"IMG_8167.m4v" }, 111 | { "template":"caption", "markdown":"## 12. Pour milk directly into the expresso." }, 112 | { "template":"play" }, 113 | ] 114 | }, 115 | { 116 | "duration":5.0, 117 | "elements":[ 118 | { "template":"full", "img":"IMG_8168.JPG", "to":{ "scale":1.2 } }, 119 | { "template":"caption", "markdown":"# Enjoy your late!" } 120 | ] 121 | }, 122 | ] 123 | } 124 | -------------------------------------------------------------------------------- /sample/script/index.swipe: -------------------------------------------------------------------------------- 1 | { 2 | "type":"net.swipe.list", 3 | "rowHeight":"10%", 4 | "items":[ 5 | { "url":"swipe.swipe", "title":"Swipe Overview", "icon":"Icon-180.png" }, 6 | { "url":"tutorial.swipe", "title":"Swipe Tutorial", "icon":"Icon-180.png" }, 7 | { "url":"empty.swipe", "title":"Empty", "icon":"Icon-180.png" }, 8 | { "url":"dice.swipe", "title":"Dice (scale, rotate, translate)", "icon":"https://satoshi.blogs.com/swipe/Dice1.png" }, 9 | { "url":"dice2.swipe", "title":"Dice (loop, scale)", "icon":"https://satoshi.blogs.com/swipe/Dice1.png" }, 10 | { "url":"videostream.swipe", "title":"Streaming Video", "icon":"movie.png" }, 11 | { "url":"radiostream.swipe", "title":"Internet Radio Stations", "icon":"radio.png" }, 12 | { "url":"markdown.swipe", "title":"Rich Text", "icon":"text.png" }, 13 | { "url":"speech.swipe", "title":"Speech", "icon":"speech.png" }, 14 | { "url":"vectors.swipe", "title":"Vectors", "icon":"Icon-180.png" }, 15 | { "url":"nasa.swipe", "title":"NASA HQ Images", "icon":"nasa.png" }, 16 | { "url":"espresso_machine_l.swipe", "title":"Espresso Machine", "icon":"espresso.png" }, 17 | { "url":"actions.swipe", "title":"Events & Actions", "icon":"Icon-180.png" }, 18 | { "url":"update.swipe", "title":"Properties", "icon":"Icon-180.png" }, 19 | { "url":"list.swipe", "title":"List", "icon":"Icon-180.png" }, 20 | { "url":"network.swipe", "title":"Network", "icon":"Icon-180.png" }, 21 | { "url":"details_index.swipe", "title":"Property Details", "icon":"more.png" }, 22 | { "url":"http://www.swipe.net/index.swipe", "title":"More...", "text":"Load a list from a remote host", "icon":"more.png" }, 23 | { "url":"http://satoshi.blogs.com/swipe/index2.swipe", "title":"More 2...", "text":"Load a list from a remote host", "icon":"more.png" }, 24 | { "url":"tv_index.swipe", "title":"TV", "icon":"more.png" }, 25 | { "url":"https://raw.githubusercontent.com/swipe-org/swipe-sample/master/index.swipe", "title":"Test Cases", "icon":"Icon-180.png" }, 26 | { "url":"https://raw.githubusercontent.com/swipe-org/swipe-sample/staging/index.swipe", "title":"Test Cases (Staging)", "icon":"Icon-180.png" } 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /sample/script/list.swipe: -------------------------------------------------------------------------------- 1 | { 2 | "templates": { 3 | "elements": { 4 | "even": { 5 | "bc":"#eef", 6 | "elements": [ 7 | { "img":"icon-180.png", "pos":[20,"50%"], "w":20 }, 8 | { 9 | "x":40, "w":"80%", "textAlign":"left", "fontSize":20, 10 | "text":{"valueOf":{"id":"aList", "property":{"items":{"data":{"person":{"id":"first"}}}}}} 11 | } 12 | ] 13 | }, 14 | "odd": { 15 | "bc":"#efe", 16 | "elements": [ 17 | { "img":"icon-180.png", "pos":["90%","50%"], "w":20}, 18 | { 19 | "x":0, "w":"80%", "textAlign":"right", "fontSize":20, 20 | "text":{"valueOf":{"id":"aList", "property":{"items":{"data":{"person":{"id":"first"}}}}}} 21 | } 22 | ] 23 | } 24 | } 25 | }, 26 | "pages": [ 27 | { 28 | "id": "main", 29 | "play":"never", 30 | "elements": [ 31 | { "bc":"#fdd"}, 32 | { "h":"8%", "w":"45%", "pos":["25%","95%"],"text":"selected", "textAlign":"right"}, 33 | { "id":"echo", "h":"8%", "w":"45%", "pos":["75%","95%"],"text":""}, 34 | { "text":"Tap on List Items", "h":"8%", "pos":["50%", "5%"]}, 35 | { 36 | "id": "aList", 37 | "h":"80%", 38 | "w":"90%", 39 | "bc":"#ffe", 40 | "pos":["50%","50%"], 41 | "list": { 42 | "selectedItem":2, 43 | "items": [ 44 | { "elements":[ {"template":"even" }], "data":{ "person": { "id": { "first":"fred", "last":"flintstone"}}}}, 45 | { "elements":[ {"template":"odd" }], "data":{ "person": { "id": { "first":"wilma", "last":"flintstone"}}}}, 46 | { "elements":[ {"template":"even" }], "data":{ "person": { "id": { "first":"pebbles", "last":"flintstone"}}}}, 47 | { "elements":[ {"template":"odd" }], "data":{ "person": { "id": { "first":"barney", "last":"rubble"}}}}, 48 | { "elements":[ {"template":"even" }], "data":{ "person": { "id": { "first":"betty", "last":"rubble"}}}}, 49 | { "elements":[ {"template":"odd" }], "data":{ "person": { "id": { "first":"bambam", "last":"rubble"}}}} 50 | ] 51 | }, 52 | "events": { 53 | "rowSelected": { 54 | "actions": [ 55 | { "update": { "id":"echo", "text":{"valueOf":{"id":"aList", "property":"selectedItem"}}}} 56 | ] 57 | } 58 | } 59 | } 60 | ], 61 | "events": { 62 | "load": { 63 | "actions": [ 64 | { "update": { "id":"echo", "text":{"valueOf":{"id":"aList", "property":"selectedItem"}}}} 65 | ] 66 | } 67 | } 68 | } 69 | ] 70 | } 71 | -------------------------------------------------------------------------------- /sample/script/markdown.swipe: -------------------------------------------------------------------------------- 1 | { 2 | "title":"Text", 3 | "markdown":{ 4 | "shadow":{ "color":"blue", "offset":[-2,2], "radius":3, "opacity":0.5 }, 5 | "styles":{ 6 | "#":{ "color":"#008", "font":{ "size":"5%", "name":["foo", "Courier"] }}, 7 | "##":{ "color":"#080", "font":{ "size":"4.5%", "name":"Chalkboard SE" }}, 8 | "###":{ "color":"#800", "font":{ "size":"4%", "name":"Helvetica" }}, 9 | "####":{ "color":"#800", "font":{ "size":"3.5%"}}, 10 | "#5":{ "color":"red", "font":{ "size":"3.5%"}}, 11 | "*":{ "color":"#880", "font":{ "size":"3%", "name":"Helvetica" }}, 12 | "-":{ "color":"#808", "font":{ "size":"3%", "name":"Helvetica" }, "prefix":"◆ "}, 13 | "+":{ "color":"#808", "font":{ "size":"3%", "name":"Helvetica" }, "prefix":"✅ "}, 14 | "```":{ "color":"#808", "prefix":"| " }, 15 | }, 16 | }, 17 | "templates": { 18 | "elements":{ 19 | "slide": { "x":"10%", "y":"10%", "w":"80%", "h":"80%", "bc":"#ffc" }, 20 | }, 21 | }, 22 | "pages":[ 23 | { 24 | "elements":[ 25 | { "template":"slide","markdown":[ 26 | "# スマートフォンの時代に相応しい『漫画・アニメ体験』と、その実現に必要なマークアップ言語の提案", 27 | ]}, 28 | ], 29 | }, 30 | { 31 | "elements":[ 32 | { "template":"slide", "markdown":[ 33 | "#5 Keywords", 34 | "- Comic (Manga)", 35 | "- Multi-media eBook", 36 | "- Smartphone User Experience", 37 | "+ Native vs. HTML5", 38 | "+ Domain-Specific Language", 39 | "+ Declarative Language", 40 | ]}, 41 | ], 42 | }, 43 | { 44 | "elements":[ 45 | { "template":"slide", "markdown":[ 46 | "# Hello World", 47 | "## Hello World", 48 | "### Hello World", 49 | "#### Hello World", 50 | "Hello World", 51 | "Hello World", 52 | "- Hello World", 53 | "- Hello World", 54 | "- Hello World", 55 | ]}, 56 | ], 57 | }, 58 | { 59 | "elements":[ 60 | { "template":"slide", "markdown":[ 61 | "Hello World", 62 | "Hello World", 63 | "```", 64 | "Hello World {", 65 | " Hello World {", 66 | " Hello World", 67 | " }", 68 | "} Hello World", 69 | "```", 70 | "Hello World", 71 | "Hello World", 72 | ]}, 73 | ], 74 | }, 75 | { 76 | "elements":[ 77 | { "template":"slide", "text":"Hello World" }, 78 | ], 79 | }, 80 | ], 81 | } 82 | -------------------------------------------------------------------------------- /sample/script/nasa/nasa.swipe: -------------------------------------------------------------------------------- 1 | { 2 | "type":"net.swipe.swipe", 3 | "title": "NASA HQ Images", 4 | "orientation":"landscape", 5 | "viewstate":false, 6 | "resources":["nasa", "bgm"], 7 | "templates": { 8 | "pages":{ 9 | "descent": { "duration":5.0, "bgm":"TheDescent.mp3" }, 10 | "agenda": { "duration":5.0, "bgm":"HiddenAgenda.mp3" }, 11 | "monkey": { "duration":5.0, "bgm":"MonkeysSpinningMonkeys.mp3" }, 12 | }, 13 | "elements":{ 14 | "caption": { "y":"top", "h":"5%" }, 15 | "full": { "x":"center", "y":"bottom", "w":"fill", "to":{ "scale":1.1 } }, 16 | }, 17 | }, 18 | "markdown":{ 19 | "shadow":{ "color":"#000", "offset":[-2,2] }, 20 | "styles":{ 21 | "+":{ "color":"#fff8", "prefix":"♫ ", "alignment":"right", 22 | "font":{ "size":"3.5%", "name":"Helvetica" }}, 23 | }, 24 | }, 25 | "pages":[ 26 | { 27 | "template":"descent", 28 | "elements":[ 29 | { "template":"full", "img":"nasa001.jpg" }, 30 | { "template":"caption", "markdown":"+ The Decent by Kevin MacLeod"}, 31 | ] 32 | }, 33 | { 34 | "template":"descent", 35 | "elements":[ 36 | { "template":"full", "img":"nasa002.jpg"}, 37 | ] 38 | }, 39 | { 40 | "template":"descent", 41 | "elements":[ 42 | { "template":"full", "img":"nasa003.jpg" }, 43 | ] 44 | }, 45 | { 46 | "template":"descent", 47 | "elements":[ 48 | { "template":"full", "img":"nasa004.jpg" }, 49 | ] 50 | }, 51 | { 52 | "template":"agenda", 53 | "elements":[ 54 | { "template":"full", "img":"nasa005.jpg" }, 55 | { "template":"caption", "markdown":"+ Hidden Agenda by Kevin MacLeod"}, 56 | ] 57 | }, 58 | { 59 | "template":"agenda", 60 | "elements":[ 61 | { "template":"full", "img":"nasa006.jpg" }, 62 | ] 63 | }, 64 | { 65 | "template":"agenda", 66 | "elements":[ 67 | { "template":"full", "img":"nasa007.jpg" }, 68 | ] 69 | }, 70 | { 71 | "template":"agenda", 72 | "elements":[ 73 | { "template":"full", "img":"nasa008.jpg" }, 74 | ] 75 | }, 76 | { 77 | "template":"agenda", 78 | "elements":[ 79 | { "template":"full", "img":"nasa009.jpg" }, 80 | ] 81 | }, 82 | { 83 | "template":"monkey", 84 | "elements":[ 85 | { "template":"full", "img":"nasa010.jpg" }, 86 | { "template":"caption", "markdown":"+ Monkeys Spinning Monkeys by Kevin MacLeod"}, 87 | ] 88 | }, 89 | { 90 | "template":"monkey", 91 | "elements":[ 92 | { "template":"full", "img":"nasa011.jpg" }, 93 | ] 94 | }, 95 | { 96 | "template":"monkey", 97 | "elements":[ 98 | { "template":"full", "img":"nasa012.jpg" }, 99 | ] 100 | }, 101 | { 102 | "template":"monkey", 103 | "elements":[ 104 | { "template":"full", "img":"nasa013.jpg" }, 105 | ] 106 | }, 107 | ] 108 | } 109 | -------------------------------------------------------------------------------- /sample/script/nasa/nasa000.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swipe-org/swipe/3529d5a1fa808c0885942e521798895bcf6199b7/sample/script/nasa/nasa000.jpg -------------------------------------------------------------------------------- /sample/script/nasa/nasa001.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swipe-org/swipe/3529d5a1fa808c0885942e521798895bcf6199b7/sample/script/nasa/nasa001.jpg -------------------------------------------------------------------------------- /sample/script/nasa/nasa002.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swipe-org/swipe/3529d5a1fa808c0885942e521798895bcf6199b7/sample/script/nasa/nasa002.jpg -------------------------------------------------------------------------------- /sample/script/nasa/nasa003.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swipe-org/swipe/3529d5a1fa808c0885942e521798895bcf6199b7/sample/script/nasa/nasa003.jpg -------------------------------------------------------------------------------- /sample/script/nasa/nasa004.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swipe-org/swipe/3529d5a1fa808c0885942e521798895bcf6199b7/sample/script/nasa/nasa004.jpg -------------------------------------------------------------------------------- /sample/script/nasa/nasa005.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swipe-org/swipe/3529d5a1fa808c0885942e521798895bcf6199b7/sample/script/nasa/nasa005.jpg -------------------------------------------------------------------------------- /sample/script/nasa/nasa006.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swipe-org/swipe/3529d5a1fa808c0885942e521798895bcf6199b7/sample/script/nasa/nasa006.jpg -------------------------------------------------------------------------------- /sample/script/nasa/nasa007.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swipe-org/swipe/3529d5a1fa808c0885942e521798895bcf6199b7/sample/script/nasa/nasa007.jpg -------------------------------------------------------------------------------- /sample/script/nasa/nasa008.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swipe-org/swipe/3529d5a1fa808c0885942e521798895bcf6199b7/sample/script/nasa/nasa008.jpg -------------------------------------------------------------------------------- /sample/script/nasa/nasa009.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swipe-org/swipe/3529d5a1fa808c0885942e521798895bcf6199b7/sample/script/nasa/nasa009.jpg -------------------------------------------------------------------------------- /sample/script/nasa/nasa010.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swipe-org/swipe/3529d5a1fa808c0885942e521798895bcf6199b7/sample/script/nasa/nasa010.jpg -------------------------------------------------------------------------------- /sample/script/nasa/nasa011.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swipe-org/swipe/3529d5a1fa808c0885942e521798895bcf6199b7/sample/script/nasa/nasa011.jpg -------------------------------------------------------------------------------- /sample/script/nasa/nasa012.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swipe-org/swipe/3529d5a1fa808c0885942e521798895bcf6199b7/sample/script/nasa/nasa012.jpg -------------------------------------------------------------------------------- /sample/script/nasa/nasa013.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swipe-org/swipe/3529d5a1fa808c0885942e521798895bcf6199b7/sample/script/nasa/nasa013.jpg -------------------------------------------------------------------------------- /sample/script/network.swipe: -------------------------------------------------------------------------------- 1 | { 2 | "pages": [ 3 | { 4 | "play":"never", 5 | "elements": [ 6 | { 7 | "text":"HTTP GET", "pos":["50%", 80], "w":"80%", "h":50, "borderWidth":1, "borderColor":"black", "cornerRadius":10, "focusable":true, 8 | "events":{ 9 | "tapped":{ 10 | "actions":[ 11 | { "update":{"id":"label", "text":"Loading ...", "textColor":"black" } }, 12 | { "update":{"id":"error", "text":"", "opacity":0 } }, 13 | { 14 | "get":{ 15 | "source": {"url":"http://www.stoppani.net/swipe/simpledata.txt"}, 16 | "events": { 17 | "error": { 18 | "params": {"message":{"type":"string"}}, 19 | "actions": [ 20 | { "update":{"id":"label", "text":"Error. Try Again", "textColor":"red" } }, 21 | { "update":{"id":"error", "text":{"valueOf":{"property":{"params":"message"}}}, "opacity":1, "duration":0.4}} 22 | ] 23 | }, 24 | "completion": { 25 | "params": {"caption":{"type":"string"}, "imageURL":{"type":"string"}}, 26 | "actions": [ 27 | { "update":{"id":"label", "text":{"valueOf":{"property":{"params":"caption"}}}}}, 28 | { "update":{"id":"image", "img":{"valueOf":{"property":{"params":"imgURL"}}}}} 29 | ] 30 | } 31 | } 32 | } 33 | } 34 | ] 35 | } 36 | }}, 37 | {"id":"label", "h":50, "pos":["50%", 130] }, 38 | {"id":"image", "w":150, "h":150, "pos":["50%", 240], "img":"more.png" }, 39 | {"id":"error", "textColor":"red", "w":"98%", "h":200, "pos":["50%", 420], "fontSize":18 }, 40 | ] 41 | } 42 | ] 43 | } 44 | -------------------------------------------------------------------------------- /sample/script/radiostream.swipe: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Internet Radios", 3 | "orientation":"landscape", 4 | "templates": { 5 | "pages":{ 6 | "*":{ 7 | "play":"always", 8 | }, 9 | }, 10 | }, 11 | "pages":[ 12 | { 13 | "elements":[ 14 | { "radio":"http://play.radiopanteon.pl:8000/stream" }, 15 | { "text":"Radio Panteon" }, 16 | ], 17 | }, 18 | { 19 | "elements":[ 20 | { "radio":"http://dir.xiph.org/listen/3754848/listen.m3u" }, 21 | { "text":"Russian Hit - PYCCKUU XUT" }, 22 | ], 23 | }, 24 | { 25 | "elements":[ 26 | { "radio":"http://dir.xiph.org/listen/3618988/listen.m3u" }, 27 | { "text":"MEGA RADIO" }, 28 | ], 29 | }, 30 | { 31 | "elements":[ 32 | { "radio":"http://dir.xiph.org/listen/4061925/listen.m3u" }, 33 | { "text":"DUSHEVNOE RADIO" }, 34 | ], 35 | }, 36 | { 37 | "elements":[ 38 | { "radio":"http://dir.xiph.org/listen/3716076/listen.m3u" }, 39 | { "text":"I Love Radio" }, 40 | ], 41 | }, 42 | { 43 | "elements":[ 44 | { "radio":"http://dir.xiph.org/listen/3956748/listen.m3u" }, 45 | { "text":"ABCLounge" }, 46 | ], 47 | }, 48 | { 49 | "elements":[ 50 | { "radio":"http://dir.xiph.org/listen/3851921/listen.m3u" }, 51 | { "text":"sunshine live" }, 52 | ], 53 | }, 54 | { 55 | "elements":[ 56 | { "radio":"http://dir.xiph.org/listen/3753780/listen.m3u" }, 57 | { "text":"Mundo Livre FM Curitiba" }, 58 | ], 59 | }, 60 | ] 61 | } 62 | -------------------------------------------------------------------------------- /sample/script/speech.swipe: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Speech", 3 | "voices":{ 4 | "*":{ "lang":"en-US" }, 5 | "american":{ "lang":"en-US" }, 6 | "japanese":{ "lang":"ja-JP" }, 7 | "british":{ "lang":"en-GB" }, 8 | "french":{ "lang":"fr-FR" }, 9 | "german":{ "lang":"de-DE" }, 10 | "vader":{ "pitch":2.0, "rate":0.2 }, 11 | }, 12 | "pages": [ 13 | { 14 | "speech":{ "text":"Hello World" }, 15 | "elements": [ 16 | { "text":"Hello World" } 17 | ] 18 | }, 19 | { 20 | "speech":{ "text":"こんにちは", "voice":"japanese" }, 21 | "elements": [ 22 | { "text":"こんにちは" } 23 | ] 24 | }, 25 | { 26 | "speech":{ "text":"Bonjour", "voice":"french" }, 27 | "elements": [ 28 | { "text":"Bonjour" } 29 | ] 30 | }, 31 | { 32 | "speech":{ "text":"Guten Tag", "voice":"german" }, 33 | "elements": [ 34 | { "text":"Guten Tag" } 35 | ] 36 | }, 37 | { 38 | "speech":{ "text":"I'm an American in New York.", "voice":"american" }, 39 | "elements": [ 40 | { "text":"I'm an American in New York." } 41 | ] 42 | }, 43 | { 44 | "speech":{ "text":"I'm an Englishman in New York.", "voice":"british" }, 45 | "elements": [ 46 | { "text":"I'm an Englishman in New York." } 47 | ] 48 | }, 49 | { 50 | "speech":{ "text":"I'm a Japanese in New York", "voice":"japanese" }, 51 | "elements": [ 52 | { "text":"I'm a Japanese in New York." } 53 | ] 54 | }, 55 | { 56 | "speech":{ "text":"May the force be with you.", "voice":"vader" }, 57 | "elements": [ 58 | { "text":"May the force be with you." } 59 | ] 60 | }, 61 | ] 62 | } -------------------------------------------------------------------------------- /sample/script/tutorial/Icon-180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swipe-org/swipe/3529d5a1fa808c0885942e521798895bcf6199b7/sample/script/tutorial/Icon-180.png -------------------------------------------------------------------------------- /sample/script/tutorial/chara_walk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swipe-org/swipe/3529d5a1fa808c0885942e521798895bcf6199b7/sample/script/tutorial/chara_walk.png -------------------------------------------------------------------------------- /sample/script/tutorial/empty.swipe: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Swipe", 3 | "dimension":[1280, 0], 4 | 5 | "pages":[{ 6 | "elements":[{ 7 | "x":100, "y":100, "w":100, "h":100, 8 | "bc":"#00f" 9 | }], 10 | }] 11 | } 12 | -------------------------------------------------------------------------------- /sample/script/tutorial/epsilon.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swipe-org/swipe/3529d5a1fa808c0885942e521798895bcf6199b7/sample/script/tutorial/epsilon.gif -------------------------------------------------------------------------------- /sample/script/tutorial/izumi_06.mov: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swipe-org/swipe/3529d5a1fa808c0885942e521798895bcf6199b7/sample/script/tutorial/izumi_06.mov -------------------------------------------------------------------------------- /sample/script/tutorial/shuttle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swipe-org/swipe/3529d5a1fa808c0885942e521798895bcf6199b7/sample/script/tutorial/shuttle.png -------------------------------------------------------------------------------- /sample/script/tutorial/sound01.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swipe-org/swipe/3529d5a1fa808c0885942e521798895bcf6199b7/sample/script/tutorial/sound01.wav -------------------------------------------------------------------------------- /sample/script/tutorial/swipe.swipe: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Swipe", 3 | "dimension":[1280, 0], 4 | "orientation":"landscape", 5 | "templates": { 6 | "elements":{ 7 | "body": { "x":"center", "w":"66.7%" }, 8 | "code": { "x":"4%", "y":"4%", "w":"44%", "h":"92%", "bc":"#eff" }, 9 | "demo": { "x":"52%", "y":"4%", "w":"44%", "h":"92%" }, 10 | "logo": { 11 | "elements":[ 12 | { 13 | "id":"back", "w":192, "h":192, "x":"center", "y":"center", "bc":"#00ACC1", 14 | }, 15 | { 16 | "id":"frame", "w":192, "h":192, "x":"center", "y":"center", "bc":"#00ACC1", 17 | "clip":true, 18 | "elements": [ 19 | { "w":192, "h":288, "bc":"#00BCD4", 20 | "anchor":[0, 144], "pos":["50%","50%"], "rotate":-45, 21 | "shadow":{ "offset":[-0.7,0], "opacity":0.1, "radius":1 } }, 22 | { "w":288, "h":40, "bc":"#80DEEA", 23 | "anchor":[144, 40], "pos":["50%","50%"], "rotate":-45, 24 | "shadow":{ "offset":[0,1.4], "opacity":0.2, "radius":2 } }, 25 | { "w":40, "h":40, "fillColor":"#E0F7FA", "path":"ellipse", 26 | "anchor":[20, 40], "pos":["50%","50%"], "rotate":-45, 27 | "shadow":{ "offset":[-2,2], "opacity":0.3, "radius":3 } }, 28 | ], 29 | }, 30 | ] 31 | }, 32 | }, 33 | "pages":{ 34 | "*": { "bc":"#ddf" }, 35 | "demo": { "bc":"#fff" }, 36 | }, 37 | }, 38 | "markdown":{ 39 | "styles": { 40 | "#":{ "color":"#616161", "font":{ "size":92, "name":"Helvetica-Bold" }, "alignment":"center" }, 41 | "#2":{ "color":"#616161", "font":{ "size":60, "name":"Helvetica-Bold" }, "alignment":"center" }, 42 | "##":{ "color":"#008", "font":{ "size":60 } }, 43 | "###":{ "color":"#040", "font":{ "size":48, "name":"HelveticaNeue-Italic" } }, 44 | "-":{ "font":{ "size":42 } }, 45 | "*":{ "font":{ "size":42 } }, 46 | "```": { "font":{ "size":24, "name":"Damascus" } }, 47 | } 48 | }, 49 | 50 | "pages":[ 51 | { 52 | "elements":[ 53 | { "template":"body", "markdown":[ 54 | "# Swipe", 55 | ]}, 56 | { "template":"logo", "x":300, "w":192, 57 | "elements":[ 58 | { 59 | "id":"back", "w":128, "h":176, "cornerRadius":12, 60 | "shadow":{ "offset":[0,8], "opacity":0.2, "radius":8 }, 61 | }, 62 | { 63 | "id":"frame", "w":128, "h":176, "cornerRadius":12, 64 | }, 65 | ] 66 | }, 67 | ], 68 | }, 69 | 70 | { 71 | "elements":[ 72 | { "template":"body", "markdown":[ 73 | "## Problems", 74 | "- Native vs. HTML5 dilemma", 75 | "- Not enough developers", 76 | "- Too expensive to develop apps", 77 | "- ePub is not designed for multi-media", 78 | "### Hard to innovate without programming!", 79 | ]}, 80 | ] 81 | }, 82 | 83 | { 84 | "elements":[ 85 | { "template":"body", "markdown":[ 86 | "## Media Format Evolutions", 87 | "- Book & Magazine for Paper", 88 | "- Movie & Animation for TV", 89 | "- HTML for PC", 90 | "- PowerPoint & Keynote for Projector", 91 | "- ePub for eBook", 92 | "### Let's design a new media format specificaly for touch-enabled smart devices!", 93 | ]}, 94 | ] 95 | }, 96 | 97 | { 98 | "elements":[ 99 | { "template":"body", "markdown":[ 100 | "## Target Applications", 101 | "- Interactive Comics", 102 | "- Sound Novels", 103 | "- Graphical Audio Books", 104 | "- Interactive Videos", 105 | "- Media-rich Presentations", 106 | "- Interactive Arts", 107 | ]}, 108 | ] 109 | }, 110 | 111 | { 112 | "elements":[ 113 | { "template":"body", "markdown":[ 114 | "## Design Principles", 115 | "- Optimized for touch-enabled devices", 116 | "- 100% declarative (no programming)", 117 | "- Rich & interactive animations", 118 | "- Re-invent the video experience", 119 | "- Customizable page-transitions", 120 | "- Designer friendly", 121 | "- Lighweight & portable", 122 | ]}, 123 | ] 124 | }, 125 | 126 | { 127 | "elements":[ 128 | { "template":"body", "markdown":[ 129 | "#2 Swipe Engine & Language", 130 | ]}, 131 | ], 132 | }, 133 | 134 | { 135 | "elements":[ 136 | { "template":"body", "markdown":[ 137 | "## Swipe Engine", 138 | "- enables media-rich/animated documents", 139 | "- is optimized for touch-enabled devices", 140 | "- is portable", 141 | "- is lightweight", 142 | "- is open source", 143 | "### github.com/snakajima/swipe", 144 | ]}, 145 | ] 146 | }, 147 | 148 | { 149 | "elements":[ 150 | { "template":"body", "markdown":[ 151 | "## Swipe Engine", 152 | "- is not a general purpose platform", 153 | "- is not a programming environment", 154 | "- is not a game engine", 155 | "- does not replace ePub", 156 | ]}, 157 | ] 158 | }, 159 | { 160 | "elements":[ 161 | { "template":"body", "markdown":[ 162 | "## Swipe Language", 163 | "- is a domain-specific language", 164 | "- is declarative (like HTML)", 165 | "- is based on JSON (not XML)", 166 | "- is designer-friendly", 167 | ]}, 168 | ] 169 | }, 170 | 171 | { 172 | "elements":[ 173 | { "template":"body", "markdown":[ 174 | "## Status", 175 | "### Now (October 2015)", 176 | "- Specification (0.1)", 177 | "- iOS/tvOS Runtime (0.1)", 178 | "### Next Step", 179 | "- Further improvement based on feedback", 180 | "- Android/HTML5/Windows Runtime", 181 | "- Authoring Tool", 182 | "#### https://github.com/snakajima/swipe", 183 | "#### http://www.swipe.net", 184 | ]}, 185 | ] 186 | }, 187 | 188 | ] 189 | } 190 | -------------------------------------------------------------------------------- /sample/script/tv/focus.swipe: -------------------------------------------------------------------------------- 1 | { 2 | "templates":{ 3 | "elements":{ 4 | "button":{ 5 | "data":"one", "h":50, "bc":"#ccc", "borderWidth":1, "borderColor":"#ccc", "cornerRadius":10, "focusable":true, 6 | "events":{ 7 | "load":{ 8 | "actions":[ 9 | { "update": {"text":{"valueOf":{"property":"data"}}} } 10 | ] 11 | }, 12 | "gainedFocus":{ 13 | "actions":[ 14 | { 15 | "update":{ 16 | "id":"focus", 17 | "x":{"valueOf":{"property":"screenX"}}, 18 | "y":{"valueOf":{"property":"screenY"}}, 19 | "w":{"valueOf":{"property":"w"}}, 20 | "h":{"valueOf":{"property":"h"}}, 21 | "opacity":1, 22 | "duration":0.4 23 | } 24 | } 25 | ] 26 | }, 27 | "lostFocus":{ 28 | "actions":[ 29 | { 30 | "update":{ 31 | "id":"focus", 32 | "opacity":0 33 | } 34 | } 35 | ] 36 | }, 37 | "tapped":{ 38 | "actions":[ 39 | { 40 | "update":{ 41 | "text":"tapped", "bc":"#eee", "duration":0.4, 42 | "events":{ 43 | "completion":{ 44 | "actions":[ 45 | { "update": {"text":{"valueOf":{"property":"data"}}, "bc":"#ccc" } } 46 | ] 47 | } 48 | } 49 | } 50 | } 51 | ] 52 | } 53 | } 54 | } 55 | } 56 | }, 57 | "pages":[ 58 | { 59 | "play":"never", 60 | "elements":[ 61 | { "template":"button", "data":"1", "y":50 }, 62 | { 63 | "y":105, "h":"50", 64 | "elements":[ 65 | { "template":"button", "data":"2", "w":"50%" }, 66 | { "template":"button", "data":"3", "x":"50%", "w":"50%" } 67 | ] 68 | }, 69 | { 70 | "y":160, "h":"50", 71 | "elements":[ 72 | { "template":"button", "data":"4", "w":"50%" }, 73 | { "template":"button", "data":"5", "x":"50%", "w":"50%" } 74 | ] 75 | }, 76 | { "template":"button", "data":"6", "y":215, "x":"50%", "w":"50%" }, 77 | { "template":"button", "data":"7", "y":270, "x":"50%", "w":"50%" }, 78 | { "template":"button", "data":"8", "y":270, "w":"50%" }, 79 | { 80 | "y":330, "h":"105", "x":0, "w":"50%", 81 | "elements":[ 82 | { "template":"button", "data":"9" }, 83 | { "template":"button", "data":"10", "y":55 } 84 | ] 85 | }, 86 | { "id":"focus", "borderWidth":2, "borderColor":"red", "opacity":0 } 87 | ] 88 | } 89 | ] 90 | } -------------------------------------------------------------------------------- /sample/script/tv/tv_index.swipe: -------------------------------------------------------------------------------- 1 | { 2 | "type":"net.swipe.list", 3 | "rowHeight":"10%", 4 | "items":[ 5 | { "url":"focus.swipe", "title":"Focus", "icon":"Icon-180" }, 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /sample/script/update.swipe: -------------------------------------------------------------------------------- 1 | { 2 | "templates":{ 3 | "elements":{ 4 | "focus": { 5 | "fontSize":20, 6 | "events": { 7 | "gainedFocus":{ 8 | "actions":[ 9 | { 10 | "update":{ 11 | "id":"focus", 12 | "x":{"valueOf":{"property":"screenX"}}, 13 | "y":{"valueOf":{"property":"screenY"}}, 14 | "w":{"valueOf":{"property":"w"}}, 15 | "h":{"valueOf":{"property":"h"}}, 16 | "opacity":1, 17 | "duration":0.4 18 | } 19 | } 20 | ] 21 | }, 22 | "lostFocus":{ 23 | "actions":[ 24 | { 25 | "update":{ 26 | "id":"focus", 27 | "opacity":0 28 | } 29 | } 30 | ] 31 | } 32 | } 33 | } 34 | } 35 | }, 36 | "pages": [ 37 | { 38 | "play":"never", 39 | "elements": [ 40 | { "id":"caption", "text":"sample", "textColor":"#fff", "fontSize":20, "x":"1%", "w":"98%", "h":40, "y":40, "bc":"#eee" }, 41 | { "text":"Tap a button to display its 'text' property above", "x":"1%", "w":"98%", "y":82, "h":60, "fontSize":18 }, 42 | { 43 | "text":"one", "x":"1%", "w":"48%", "h":40, "y":140, "bc":"#fdd", "borderWidth":1, "borderColor":"#ccc", "cornerRadius":10, "template":"focus", "focusable":true, 44 | "events": { 45 | "tapped": { 46 | "actions":[{ "update":{ "id":"caption", "text":{"valueOf":{ "property":"text"} }, "textColor":"#f00", "bc":"#0f0", "duration":0.4 }}] 47 | } 48 | } 49 | }, 50 | { 51 | "text":"two", "x":"51%", "w":"48%", "h":40, "y":140, "bc":"#dfd", "borderWidth":1, "borderColor":"#ccc", "cornerRadius":10, "template":"focus", "focusable":true, 52 | "events": { 53 | "tapped": { 54 | "actions":[{ "update":{ "id":"caption", "text":{"valueOf":{ "property":"text"} }, "textColor":"#0f0", "bc":"#f00", "duration":0.4 }}] 55 | } 56 | } 57 | }, 58 | { "text":"Tap text field below, enter text, tap on the button to display the text field's 'text' property above", "x":"1%", "w":"98%", "y":200, "h":60, "fontSize":18 }, 59 | { 60 | "id":"input", "textField":{}, "textAlign":"center", "text":"hi", "borderWidth":1, "borderColor":"#ccc", "cornerRadius":10, "template":"focus", "focusable":true, 61 | "x":"1%", "y":260, "w":"82%", "h":50, "bc":"#fff", 62 | "events":{ 63 | "textChanged":{ 64 | "actions":[{ "update":{ "id":"btn", "enabled":{ "valueOf":{ "property":"text.length" }}} }] 65 | } 66 | } 67 | }, 68 | { 69 | "id":"btn", "text":"", "x":"84%", "y":260, "w":50, "h":50, "textColor":"#ccc", "bc":"#fff", "borderWidth":1, "borderColor":"#ccc", "cornerRadius":25, "enabled":false, "template":"focus", "focusable":true, 70 | "events":{ 71 | "enabled":{ 72 | "actions":[ { "update":{ "focusable":true, "text":"^", "textColor":"#1E88E5", "borderColor":"#1E88E5" } } ] 73 | }, 74 | "disabled":{ 75 | "actions":[ { "update":{ "focusable":false, "text":"", "textColor":"#ccc", "borderColor":"#ccc" } } ] 76 | }, 77 | "tapped":{ 78 | "//": "fired only if enabled", 79 | "actions":[ 80 | { "update":{ "id":"caption", "text":{"valueOf":{ "id":"input", "property":"text"} }, "textColor":"#000", "bc":"#ddd", "duration":0.4 }}, 81 | { "update":{ "enabled":false } }, 82 | { "update":{ "id":"input", "text":"" } } 83 | ] 84 | }, 85 | "load":{ 86 | "actions":[{ "update":{ "enabled":{ "valueOf":{ "id":"input", "property":"text.length" }}} }] 87 | } 88 | } 89 | }, 90 | { "id":"focus", "borderWidth":2, "borderColor":"red", "opacity":0 } 91 | ] 92 | } 93 | ] 94 | } 95 | -------------------------------------------------------------------------------- /sample/script/vectors.swipe: -------------------------------------------------------------------------------- 1 | { 2 | "title": "", 3 | "scenes": { 4 | "s0": { 5 | "elements": 6 | [{ 7 | "bc":"#ff000033", 8 | "fillColor": "#ffffffe0", 9 | "y": 459.5, 10 | "h": 324.5, 11 | "w": 226.5, 12 | "id": "id0", 13 | "opacity": 0, 14 | "path": "M54.5,324.5Q106.315006604422,199.846174687046,0.0,214.0 226.5,0.0,202.0,316.0 155.743702216728,211.844523128144,54.5,324.5Z", 15 | "lineWidth": 3, 16 | "scale": [2.129, 2.129], 17 | "_hidden": true, 18 | "strokeColor": "#e40000ff", 19 | "x": 141.5 20 | }, 21 | { 22 | "bc":"#00ff0033", 23 | "fillColor": "#ffffffe0", 24 | "y": 160, 25 | "h": 305.3183288574219, 26 | "w": 218, 27 | "id": "id1", 28 | "opacity": 0, 29 | "path": "M100.0,176.5Q16.0068081793643,250.81683739138,89.5,82.0 0.0,119.0,30.0,59.5 60.0,0.0,109.75,17.0 159.5,34.0,188.75,71.0 218.0,108.0,207.5,139.25 197.0,170.5,159.0,112.5 165.864229617208,305.318314190719,100.0,176.5Z", 30 | "lineWidth": 4, 31 | "scale": [1.829, 1.829], 32 | "_hidden": true, 33 | "strokeColor": "#0b8700ff", 34 | "x": 241.5 35 | }, 36 | { 37 | "bc":"#0000ff33", 38 | "fillColor": "#ffffffe0", 39 | "y": 318, 40 | "h": 261.5, 41 | "w": 241.5, 42 | "id": "id2", 43 | "opacity": 0, 44 | "path": "M140.5,229.5Q105.793442883693,144.207260232528,47.5,261.5 0.0,30.0,87.0,126.5 70.0,0.0,155.75,41.5 241.5,83.0,234.0,137.25 226.5,191.5,177.0,117.5 142.064997296804,86.5737682687859,140.5,229.5Z", 45 | "lineWidth": 4, 46 | "scale": [1.59, 1.59], 47 | "_hidden": true, 48 | "strokeColor": "#004087ff", 49 | "x": 70 50 | }, 51 | { 52 | "shadow": { 53 | "opacity": 1, 54 | "radius": 4 55 | }, 56 | "w": "90%", 57 | "id": "caption", 58 | "text": { 59 | "ref": "caption" 60 | }, 61 | "opacity": 0, 62 | "x": "5%", 63 | "textColor": "#fff", 64 | "to": { 65 | "opacity": 1 66 | }, 67 | "textAlign": "bottom", 68 | "y": "2.5%", 69 | "h": "95%" 70 | } 71 | ], 72 | "eyePosition": 1.5 73 | } 74 | }, 75 | "dimension": [576, 1024], 76 | "languages": [], 77 | 78 | "pages": [ 79 | { 80 | "elements": [{ 81 | "id": "id0", 82 | "opacity": 1, 83 | "_hidden": false 84 | }, 85 | { 86 | "id": "id1", 87 | "opacity": 1, 88 | "_hidden": false 89 | }, 90 | { 91 | "id": "id2", 92 | "opacity": 1, 93 | "_hidden": false 94 | } 95 | ], 96 | "scene": "s0", 97 | "play": "auto" 98 | }, 99 | { 100 | "elements": [{ 101 | "opacity": 1, 102 | "id": "id0", 103 | "_hidden": false, 104 | "to": { 105 | "pos": "M0,0 l-153.5,46.0", 106 | "fillColor": "#ff7272a0", 107 | "translate": [ -153.5, 46], 108 | "rotate": [0, 0, -51.5] 109 | } 110 | }, 111 | { 112 | "opacity": 1, 113 | "id": "id1", 114 | "_hidden": false, 115 | "to": { 116 | "pos": "M0,0 l-97.0,-51.0", 117 | "fillColor": "#a9ffa1a0", 118 | "translate": [ -97, -51], 119 | "rotate": [0, 0, -39.5] 120 | } 121 | }, { 122 | "opacity": 1, 123 | "id": "id2", 124 | "_hidden": false, 125 | "to": { 126 | "pos": "M0,0 l194.5,13.0", 127 | "fillColor": "#43d9ffa0", 128 | "translate": [194.5, 13], 129 | "rotate": [0, 0, 45] 130 | } 131 | } 132 | ], 133 | "scene": "s0", 134 | "play": "scroll" 135 | } 136 | ], 137 | "type": "net.swipe.swipe" 138 | } -------------------------------------------------------------------------------- /sample/script/videostream.swipe: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Video Stream Test", 3 | "orientation":"landscape", 4 | "templates": { 5 | "pages":{ 6 | "*":{ 7 | "bc":"#333", 8 | "play":"always", 9 | }, 10 | }, 11 | }, 12 | "pages":[ 13 | { 14 | "elements":[ 15 | { "video":"http://devimages.apple.com/iphone/samples/bipbop/bipbopall.m3u8", "stream":true }, 16 | ], 17 | }, 18 | { 19 | "elements":[ 20 | { "video":"http://devstreaming.apple.com/videos/wwdc/2015/214dh5q5d0kswh/214/hls_vod_mvp.m3u8", "stream":true }, 21 | ], 22 | }, 23 | { 24 | "elements":[ 25 | { "video":"https://devimages.apple.com.edgekey.net/streaming/examples/bipbop_4x3/bipbop_4x3_variant.m3u8", "stream":true }, 26 | ], 27 | }, 28 | { 29 | "elements":[ 30 | { "video":"https://devimages.apple.com.edgekey.net/streaming/examples/bipbop_16x9/bipbop_16x9_variant.m3u8", "stream":true }, 31 | ], 32 | }, 33 | ] 34 | } 35 | --------------------------------------------------------------------------------