Repository: yuhua-chen/LayerX Branch: master Commit: b96bfd477e2c Files: 13 Total size: 56.4 KB Directory structure: gitextract_ov5xqwub/ ├── .gitignore ├── LayerX/ │ ├── AppDelegate.swift │ ├── Assets.xcassets/ │ │ ├── AppIcon.appiconset/ │ │ │ └── Contents.json │ │ ├── Contents.json │ │ └── lock.imageset/ │ │ └── Contents.json │ ├── Base.lproj/ │ │ └── Main.storyboard │ ├── Info.plist │ ├── MCDragAndDropImageView.swift │ ├── MCWindow.swift │ └── ViewController.swift ├── LayerX.xcodeproj/ │ ├── project.pbxproj │ └── project.xcworkspace/ │ └── contents.xcworkspacedata └── README.md ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ # Created by https://www.gitignore.io/api/xcode,osx ### Xcode ### # Xcode # # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore ## Build generated build/ DerivedData ## Various settings *.pbxuser !default.pbxuser *.mode1v3 !default.mode1v3 *.mode2v3 !default.mode2v3 *.perspectivev3 !default.perspectivev3 xcuserdata ## Other *.xccheckout *.moved-aside *.xcuserstate ### OSX ### .DS_Store .AppleDouble .LSOverride # Icon must end with two \r Icon # Thumbnails ._* # Files that might appear in the root of a volume .DocumentRevisions-V100 .fseventsd .Spotlight-V100 .TemporaryItems .Trashes .VolumeIcon.icns # Directories potentially created on remote AFP share .AppleDB .AppleDesktop Network Trash Folder Temporary Items .apdisk ================================================ FILE: LayerX/AppDelegate.swift ================================================ // // AppDelegate.swift // LayerX // // Created by Michael Chen on 2015/10/26. // Copyright © 2015年 Michael Chen. All rights reserved. // import Cocoa @NSApplicationMain class AppDelegate: NSObject, NSApplicationDelegate { private let defaultSize = NSMakeSize(480, 320) private let resizeStep: CGFloat = 0.1 var allSpaces = false var locked = false var onTop = false weak var window: MCWIndow! weak var viewController: ViewController! var isLockIconHiddenWhileLocked = false { didSet { viewController.lockIconImageView.isHidden = window.isMovable || isLockIconHiddenWhileLocked } } var isSizeHidden = false { didSet { viewController.sizeTextField.isHidden = isSizeHidden } } func applicationDidFinishLaunching(_ aNotification: Notification) { if let window = NSApp.windows.first as? MCWIndow { window.fitsWithSize(defaultSize) window.collectionBehavior = [.managed, .moveToActiveSpace] self.window = window } } } fileprivate enum ArrowTag: Int { case up = 20 case left = 21 case right = 22 case down = 23 } // MARK: - Hotkeys extension AppDelegate { private var originalSize: NSSize { viewController.imageView.image?.size ?? defaultSize } private func resizeAspectFit(calculator: (_ original: CGFloat, _ current: CGFloat) -> CGFloat) { let originalSize = self.originalSize let width = calculator(originalSize.width, window.frame.size.width) let height = width / originalSize.width * originalSize.height if width > 0 { window.resizeTo(NSSize(width: width, height: height), animated: true) } } @IBAction func actualSize(_ sender: AnyObject?) { window.resizeTo(originalSize, animated: true) } @IBAction func makeLarger(_ sender: AnyObject) { resizeAspectFit { $0 * ($1 / $0 + resizeStep) } } @IBAction func makeSmaller(_ sender: AnyObject) { resizeAspectFit { $0 * ($1 / $0 - resizeStep) } } @IBAction func makeLargerOnePixel(_ sender: AnyObject) { resizeAspectFit { $1 + 1 } } @IBAction func makeSmallerOnePixel(_ sender: AnyObject) { resizeAspectFit { $1 - 1 } } @IBAction func increaseTransparency(_ sender: AnyObject) { var alpha = viewController.imageView.alphaValue alpha -= 0.1 viewController.imageView.alphaValue = max(alpha, 0.05) } @IBAction func reduceTransparency(_ sender: AnyObject) { var alpha = viewController.imageView.alphaValue alpha += 0.1 viewController.imageView.alphaValue = min(alpha, 1.0) } func getPasteboardImage() -> NSImage? { let pasteboard = NSPasteboard.general; if let file = pasteboard.data(forType: NSPasteboard.PasteboardType.fileURL), let str = String(data: file, encoding: .utf8), let url = URL(string: str) { return NSImage(contentsOf: url) } if let tiff = pasteboard.data(forType: NSPasteboard.PasteboardType.tiff) { return NSImage(data: tiff) } if let png = pasteboard.data(forType: NSPasteboard.PasteboardType.png) { return NSImage(data: png) } return nil } @IBAction func paste(_ sender: AnyObject) { guard let image = getPasteboardImage() else { return } let rep = image.representations[0] viewController.imageView.image = image let size = NSMakeSize(CGFloat(rep.pixelsWide), CGFloat(rep.pixelsHigh)) window.resizeTo(size, animated: true) viewController.sizeTextField.isHidden = false viewController.placeholderTextField.isHidden = true } @IBAction func toggleLockWindow(_ sender: AnyObject) { let menuItem = sender as! NSMenuItem locked = !locked onTop = locked if locked { menuItem.title = "Unlock" window.isMovable = false window.ignoresMouseEvents = true window.level = NSWindow.Level(rawValue: Int(CGWindowLevelForKey(.maximumWindow))) } else { menuItem.title = "Lock" window.isMovable = true window.ignoresMouseEvents = false window.level = NSWindow.Level(rawValue: Int(CGWindowLevelForKey(.normalWindow))) } viewController.lockIconImageView.isHidden = window.isMovable || isLockIconHiddenWhileLocked } @IBAction func toggleOnTop(_ sender: AnyObject) { let menuItem = sender as! NSMenuItem onTop = !onTop if onTop { menuItem.title = "Don't keep on top" window.level = NSWindow.Level(rawValue: Int(CGWindowLevelForKey(.maximumWindow))) } else if !locked { menuItem.title = "Keep on top" window.level = NSWindow.Level(rawValue: Int(CGWindowLevelForKey(.normalWindow))) } } @IBAction func toggleLockIconVisibility(_ sender: AnyObject) { let menuItem = sender as! NSMenuItem menuItem.state = menuItem.state == .on ? .off : .on isLockIconHiddenWhileLocked = menuItem.state == .on } @IBAction func toggleSizeVisibility(_ sender: AnyObject) { let menuItem = sender as! NSMenuItem menuItem.state = menuItem.state == .on ? .off : .on isSizeHidden = menuItem.state == .on } @IBAction func moveAround(_ sender: AnyObject) { let menuItem = sender as! NSMenuItem guard let arrow = ArrowTag(rawValue: menuItem.tag) else { return } switch arrow { case .up: window.moveBy(CGPoint(x: 0, y: 1)) case .left: window.moveBy(CGPoint(x: -1, y: 0)) case .right: window.moveBy(CGPoint(x: 1, y: 0)) case .down: window.moveBy(CGPoint(x: 0, y: -1)) } } @IBAction func toggleAllSpaces(_ sender: AnyObject) { let menuItem = sender as! NSMenuItem allSpaces = !allSpaces if allSpaces { menuItem.title = "Keep on this space" window.collectionBehavior = [.canJoinAllSpaces] } else { menuItem.title = "Keep on all spaces" window.collectionBehavior = [.managed, .moveToActiveSpace] } } func validateMenuItem(_ menuItem: NSMenuItem) -> Bool { return viewController.imageView.image != nil } } // MARK: - Helper func appDelegate() -> AppDelegate { return NSApp.delegate as! AppDelegate } ================================================ FILE: LayerX/Assets.xcassets/AppIcon.appiconset/Contents.json ================================================ { "images" : [ { "size" : "16x16", "idiom" : "mac", "filename" : "appIcon-16.png", "scale" : "1x" }, { "size" : "16x16", "idiom" : "mac", "filename" : "appIcon-32.png", "scale" : "2x" }, { "size" : "32x32", "idiom" : "mac", "filename" : "appIcon-34.png", "scale" : "1x" }, { "size" : "32x32", "idiom" : "mac", "filename" : "appIcon-64.png", "scale" : "2x" }, { "size" : "128x128", "idiom" : "mac", "filename" : "appIcon-128.png", "scale" : "1x" }, { "size" : "128x128", "idiom" : "mac", "filename" : "appIcon-257.png", "scale" : "2x" }, { "size" : "256x256", "idiom" : "mac", "filename" : "appIcon-256.png", "scale" : "1x" }, { "size" : "256x256", "idiom" : "mac", "filename" : "appIcon.png", "scale" : "2x" }, { "size" : "512x512", "idiom" : "mac", "filename" : "appIcon-1.png", "scale" : "1x" }, { "size" : "512x512", "idiom" : "mac", "filename" : "appIcon-2.png", "scale" : "2x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: LayerX/Assets.xcassets/Contents.json ================================================ { "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: LayerX/Assets.xcassets/lock.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "filename" : "Lock-16.png", "scale" : "1x" }, { "idiom" : "universal", "filename" : "lock-32.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "lock.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: LayerX/Base.lproj/Main.storyboard ================================================ ================================================ FILE: LayerX/Info.plist ================================================ CFBundleDevelopmentRegion en CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIconFile CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName $(PRODUCT_NAME) CFBundlePackageType APPL CFBundleShortVersionString 1.2.0 CFBundleSignature ???? CFBundleVersion 1 LSMinimumSystemVersion $(MACOSX_DEPLOYMENT_TARGET) NSHumanReadableCopyright Copyright © 2015年 Michael Chen. All rights reserved. NSMainStoryboardFile Main NSPrincipalClass NSApplication ================================================ FILE: LayerX/MCDragAndDropImageView.swift ================================================ // // MCDragAndDropImageView.swift // LayerX // // Created by Michael Chen on 2015/10/26. // Copyright © 2015年 Michael Chen. All rights reserved. // import Cocoa protocol MCDragAndDropImageViewDelegate: class { func dragAndDropImageViewDidDrop(_ imageView: MCDragAndDropImageView) } class MCDragAndDropImageView: NSImageView { weak var delegate: MCDragAndDropImageViewDelegate? required init?(coder: NSCoder) { super.init(coder: coder) registerForDraggedTypes(NSImage.imageTypes.map(NSPasteboard.PasteboardType.init(rawValue:))) wantsLayer = true } override func setNeedsDisplay() { alphaValue = image == nil ? 1.0 : 0.6 super.setNeedsDisplay() } override func draw(_ dirtyRect: NSRect) { super.draw(dirtyRect) if let _ = image { layer?.backgroundColor = NSColor.clear.cgColor return } layer?.backgroundColor = NSColor(white: isHighlighted ? 0.5 : 0.8, alpha: 1.0).cgColor } override var mouseDownCanMoveWindow:Bool { return true } } // MARK: - NSDraggingSource extension MCDragAndDropImageView: NSDraggingSource { override func draggingEntered(_ sender: NSDraggingInfo) -> NSDragOperation { if (NSImage.canInit(with: sender.draggingPasteboard)) { isHighlighted = true setNeedsDisplay() let sourceDragMask = sender.draggingSourceOperationMask let pboard = sender.draggingPasteboard if pboard.availableType(from: [.fileURL]) == .fileURL { if sourceDragMask.rawValue & NSDragOperation.copy.rawValue != 0 { return NSDragOperation.copy } } } return NSDragOperation() } override func draggingExited(_ sender: NSDraggingInfo?) { isHighlighted = false setNeedsDisplay() } override func prepareForDragOperation(_ sender: NSDraggingInfo) -> Bool { isHighlighted = false setNeedsDisplay() return NSImage.canInit(with: sender.draggingPasteboard) } override func performDragOperation(_ sender: NSDraggingInfo) -> Bool { if (NSImage.canInit(with: sender.draggingPasteboard)) { image = NSImage(pasteboard: sender.draggingPasteboard) delegate?.dragAndDropImageViewDidDrop(self) setNeedsDisplay() } return true } func draggingSession(_ session: NSDraggingSession, sourceOperationMaskFor context: NSDraggingContext) -> NSDragOperation { switch context { case .outsideApplication: return NSDragOperation() case .withinApplication: return .copy @unknown default: return NSDragOperation() } } } ================================================ FILE: LayerX/MCWindow.swift ================================================ // // MCWindow.swift // LayerX // // Created by Michael Chen on 2015/10/27. // Copyright © 2015年 Michael Chen. All rights reserved. // import Cocoa class MCWIndow: NSWindow { override func awakeFromNib() { styleMask = [.borderless, .resizable] isOpaque = false backgroundColor = NSColor.clear isMovableByWindowBackground = true hasShadow = false } func moveBy(_ offset: CGPoint) { var frame = self.frame frame.origin.x += offset.x frame.origin.y += offset.y setFrame(frame, display: true) } func fitsWithSize(_ size: NSSize) { var frame = self.frame if frame.size.width < size.width || frame.size.height < size.height { frame.size = size setFrame(frame, display: true) } } func resizeTo(_ size: NSSize, animated: Bool) { var frame = self.frame frame.size = size if !animated { setFrame(frame, display: true) return } let resizeAnimation = [NSViewAnimation.Key.target: self, NSViewAnimation.Key.endFrame: NSValue(rect: frame)] let animations = NSViewAnimation(viewAnimations: [resizeAnimation]) animations.animationBlockingMode = .blocking animations.animationCurve = .easeInOut animations.duration = 0.15 animations.start() } override func constrainFrameRect(_ frameRect: NSRect, to screen: NSScreen?) -> NSRect { return frameRect } } ================================================ FILE: LayerX/ViewController.swift ================================================ // // ViewController.swift // LayerX // // Created by Michael Chen on 2015/10/26. // Copyright © 2015年 Michael Chen. All rights reserved. // import Cocoa class ViewController: NSViewController { @IBOutlet weak var imageView: MCDragAndDropImageView! @IBOutlet weak var sizeTextField: NSTextField! @IBOutlet weak var placeholderTextField: NSTextField! @IBOutlet weak var lockIconImageView: NSImageView! override var acceptsFirstResponder: Bool { return true } lazy var trackingArea: NSTrackingArea = { let options: NSTrackingArea.Options = [.activeAlways, .mouseEnteredAndExited] return NSTrackingArea(rect: self.view.bounds, options: options, owner: self, userInfo: nil) }() deinit { NotificationCenter.default.removeObserver(self) } required init?(coder: NSCoder) { super.init(coder: coder) appDelegate().viewController = self } override func viewDidLoad() { super.viewDidLoad() imageView.delegate = self sizeTextField.layer?.cornerRadius = 3 sizeTextField.layer?.masksToBounds = true lockIconImageView.wantsLayer = true lockIconImageView.layer?.backgroundColor = NSColor(white: 0.0, alpha: 0.5).cgColor lockIconImageView.layer?.cornerRadius = 5 lockIconImageView.layer?.masksToBounds = true NotificationCenter.default.addObserver(self, selector: #selector(windowDidResize(_:)), name: NSWindow.didResizeNotification, object: appDelegate().window) view.addTrackingArea(trackingArea) } @objc func fadeOutSizeTextField() { let transition = CATransition() sizeTextField.layer?.add(transition, forKey: "fadeOut") sizeTextField.layer?.opacity = 0 } @objc func windowDidResize(_ notification: Notification) { let window = notification.object as! NSWindow let size = window.frame.size sizeTextField.stringValue = "\(Int(size.width))x\(Int(size.height))" sizeTextField.layer?.opacity = 1 NSObject.cancelPreviousPerformRequests(withTarget: self, selector: #selector(ViewController.fadeOutSizeTextField), object: nil) perform(#selector(ViewController.fadeOutSizeTextField), with: nil, afterDelay: 2) } // MARK: Mouse events override func scrollWheel(with theEvent: NSEvent) { guard let _ = imageView.image else { return } let delta = theEvent.deltaY * 0.005; var alpha = imageView.alphaValue - delta alpha = min(alpha, 1) alpha = max(alpha, 0.05) imageView.alphaValue = alpha } override func mouseEntered(with theEvent: NSEvent) { sizeTextField.layer?.opacity = 1 } override func mouseExited(with theEvent: NSEvent) { fadeOutSizeTextField() } } // MARK: - MCDragAndDropImageViewDelegate extension ViewController: MCDragAndDropImageViewDelegate { func dragAndDropImageViewDidDrop(_ imageView: MCDragAndDropImageView) { sizeTextField.isHidden = false placeholderTextField.isHidden = true appDelegate().actualSize(nil) } } // MARK: - Movable NSView class MCMovableView: NSView{ override var mouseDownCanMoveWindow:Bool { return true } } ================================================ FILE: LayerX.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 46; objects = { /* Begin PBXBuildFile section */ 8C09F9F51BDE2E5C00DD457E /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C09F9F41BDE2E5C00DD457E /* AppDelegate.swift */; }; 8C09F9F71BDE2E5C00DD457E /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C09F9F61BDE2E5C00DD457E /* ViewController.swift */; }; 8C09F9F91BDE2E5C00DD457E /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 8C09F9F81BDE2E5C00DD457E /* Assets.xcassets */; }; 8C09F9FC1BDE2E5C00DD457E /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 8C09F9FA1BDE2E5C00DD457E /* Main.storyboard */; }; 8C09FA041BDE324500DD457E /* MCDragAndDropImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C09FA031BDE324500DD457E /* MCDragAndDropImageView.swift */; }; 8C09FA0C1BDF5F7300DD457E /* MCWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C09FA0B1BDF5F7300DD457E /* MCWindow.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ 8C09F9F11BDE2E5C00DD457E /* LayerX.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = LayerX.app; sourceTree = BUILT_PRODUCTS_DIR; }; 8C09F9F41BDE2E5C00DD457E /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 8C09F9F61BDE2E5C00DD457E /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 8C09F9F81BDE2E5C00DD457E /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 8C09F9FB1BDE2E5C00DD457E /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 8C09F9FD1BDE2E5C00DD457E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 8C09FA031BDE324500DD457E /* MCDragAndDropImageView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MCDragAndDropImageView.swift; sourceTree = ""; }; 8C09FA0B1BDF5F7300DD457E /* MCWindow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MCWindow.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 8C09F9EE1BDE2E5C00DD457E /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 8C09F9E81BDE2E5C00DD457E = { isa = PBXGroup; children = ( 8C09F9F31BDE2E5C00DD457E /* LayerX */, 8C09F9F21BDE2E5C00DD457E /* Products */, ); sourceTree = ""; }; 8C09F9F21BDE2E5C00DD457E /* Products */ = { isa = PBXGroup; children = ( 8C09F9F11BDE2E5C00DD457E /* LayerX.app */, ); name = Products; sourceTree = ""; }; 8C09F9F31BDE2E5C00DD457E /* LayerX */ = { isa = PBXGroup; children = ( 8C09F9F41BDE2E5C00DD457E /* AppDelegate.swift */, 8C09FA0B1BDF5F7300DD457E /* MCWindow.swift */, 8C09F9F61BDE2E5C00DD457E /* ViewController.swift */, 8C09FA031BDE324500DD457E /* MCDragAndDropImageView.swift */, 8C09F9F81BDE2E5C00DD457E /* Assets.xcassets */, 8C09F9FA1BDE2E5C00DD457E /* Main.storyboard */, 8C09F9FD1BDE2E5C00DD457E /* Info.plist */, ); path = LayerX; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ 8C09F9F01BDE2E5C00DD457E /* LayerX */ = { isa = PBXNativeTarget; buildConfigurationList = 8C09FA001BDE2E5C00DD457E /* Build configuration list for PBXNativeTarget "LayerX" */; buildPhases = ( 8C09F9ED1BDE2E5C00DD457E /* Sources */, 8C09F9EE1BDE2E5C00DD457E /* Frameworks */, 8C09F9EF1BDE2E5C00DD457E /* Resources */, ); buildRules = ( ); dependencies = ( ); name = LayerX; productName = LayerX; productReference = 8C09F9F11BDE2E5C00DD457E /* LayerX.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 8C09F9E91BDE2E5C00DD457E /* Project object */ = { isa = PBXProject; attributes = { LastUpgradeCheck = 0700; ORGANIZATIONNAME = "Michael Chen"; TargetAttributes = { 8C09F9F01BDE2E5C00DD457E = { CreatedOnToolsVersion = 7.0; LastSwiftMigration = 0800; }; }; }; buildConfigurationList = 8C09F9EC1BDE2E5C00DD457E /* Build configuration list for PBXProject "LayerX" */; compatibilityVersion = "Xcode 3.2"; developmentRegion = English; hasScannedForEncodings = 0; knownRegions = ( English, en, Base, ); mainGroup = 8C09F9E81BDE2E5C00DD457E; productRefGroup = 8C09F9F21BDE2E5C00DD457E /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 8C09F9F01BDE2E5C00DD457E /* LayerX */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 8C09F9EF1BDE2E5C00DD457E /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 8C09F9F91BDE2E5C00DD457E /* Assets.xcassets in Resources */, 8C09F9FC1BDE2E5C00DD457E /* Main.storyboard in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 8C09F9ED1BDE2E5C00DD457E /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 8C09F9F71BDE2E5C00DD457E /* ViewController.swift in Sources */, 8C09FA0C1BDF5F7300DD457E /* MCWindow.swift in Sources */, 8C09F9F51BDE2E5C00DD457E /* AppDelegate.swift in Sources */, 8C09FA041BDE324500DD457E /* MCDragAndDropImageView.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXVariantGroup section */ 8C09F9FA1BDE2E5C00DD457E /* Main.storyboard */ = { isa = PBXVariantGroup; children = ( 8C09F9FB1BDE2E5C00DD457E /* Base */, ); name = Main.storyboard; sourceTree = ""; }; /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ 8C09F9FE1BDE2E5C00DD457E /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_IDENTITY = "-"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; MACOSX_DEPLOYMENT_TARGET = 10.13; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; }; name = Debug; }; 8C09F9FF1BDE2E5C00DD457E /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_IDENTITY = "-"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; MACOSX_DEPLOYMENT_TARGET = 10.13; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; SWIFT_VERSION = 5.0; }; name = Release; }; 8C09FA011BDE2E5C00DD457E /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; COMBINE_HIDPI_IMAGES = YES; INFOPLIST_FILE = LayerX/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = io.michaelchen.LayerX; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Debug; }; 8C09FA021BDE2E5C00DD457E /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; COMBINE_HIDPI_IMAGES = YES; INFOPLIST_FILE = LayerX/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = io.michaelchen.LayerX; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 8C09F9EC1BDE2E5C00DD457E /* Build configuration list for PBXProject "LayerX" */ = { isa = XCConfigurationList; buildConfigurations = ( 8C09F9FE1BDE2E5C00DD457E /* Debug */, 8C09F9FF1BDE2E5C00DD457E /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 8C09FA001BDE2E5C00DD457E /* Build configuration list for PBXNativeTarget "LayerX" */ = { isa = XCConfigurationList; buildConfigurations = ( 8C09FA011BDE2E5C00DD457E /* Debug */, 8C09FA021BDE2E5C00DD457E /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = 8C09F9E91BDE2E5C00DD457E /* Project object */; } ================================================ FILE: LayerX.xcodeproj/project.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: README.md ================================================ # LayerX An intuitive app to display transparent images on screen. [![Demo](http://img.youtube.com/vi/35KixjZBDjY/0.jpg)](http://www.youtube.com/watch?v=35KixjZBDjY) # System requirements Support Mac OS X 10.10 or later. # Features - [x] Drag and drop images - [x] Keyboard Shortcuts - [x] Adjust transparency - [x] Lock images - [X] Aspect ratio scaling # Keyboard Shortcuts ### Set image | Key | Action | |:--- |:--- | |`⌘ V`| Paste image or file from clipboard. | ### Scale | Key | Action | |:---|:---| |`⌘ 0`| Actual image size.| |`⌘ +`| Scale up 10%.| |`⌘ -`| Scale down 10%.| |`^ ⌘ +`| Scale up 1px.| |`^ ⌘ -`| Scale down 1px.| ### Move (Arrow keys) | Key | Action | |:---|:---| |`↑`| Move up 1px.| |`←`| Move left 1px.| |`→`| Move right 1px.| |`↓`| Move down 1px.| ### Alpha |Key|Action| |:---|:---| |`⌘ J`| Increase transparency.| |`⌘ K`| Reduce transparency.| ### Window |Key|Action| |:---|:---| |`⌘ L`| Lock images and make window always on top.| |`⌘ T`| Make window always on top.| |`⌘ A`| Keep window on all spaces.| # Mouse events | Event | Action | |:---|:---| | Scroll up on image | Reduce transparency. | | Scroll down on image | Increase transparency. | # Contact Any suggestions are welcome. Find me at [@yuhua_twit](https://twitter.com/yuhua_twit) # License Copyright (c) 2017 Michael Chen (a.k.a. Yuhua Chen) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.