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.
[](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.