Mac OSX Launcher: (feature disabled, WIP) new pretty userinterface.

This commit is contained in:
meeh
2019-05-02 22:45:41 +00:00
parent 8453c5cce0
commit d81f993f81
10 changed files with 1189 additions and 2 deletions

View File

@@ -0,0 +1,83 @@
//
// AppearanceObserver.swift
// I2PLauncher
//
// Created by Mikal Villa on 15/04/2019.
// Copyright © 2019 The I2P Project. All rights reserved.
//
import Cocoa
protocol AppearanceObserver: AnyObject {
func changeAppearance(to newAppearance: NSAppearance)
}
@available(OSX 10.14, *)
class Appearance {
class Weak {
fileprivate weak var object: AnyObject?
init(_ object: AnyObject) {
self.object = object
}
}
private static var effectiveAppearanceObserver: Any? = {
return NSApplication.shared.observe(
\NSApplication.effectiveAppearance,
options: [.new, .initial]
) { _, change in
guard let newValue = change.newValue else { return }
Appearance.fire(newAppearance: newValue)
}
}()
private static var observers = [Weak]()
private static func fire(newAppearance: NSAppearance) {
observers = observers.filter {
guard let object = $0.object else { return false }
(object as? AppearanceObserver)?.changeAppearance(to: newAppearance)
return true
}
}
static func addObserver(_ observer: AppearanceObserver) {
observers = observers.filter { $0.object != nil }
observers.append(Weak(observer))
if effectiveAppearanceObserver == nil {
fatalError("Did not setup appearance observer.")
}
}
static func removeObserver(_ observer: AppearanceObserver) {
observers = observers.filter { $0.object != nil && $0.object !== observer }
}
}
enum InterfaceStyle : String {
case Dark, Light
init() {
let type = UserDefaults.standard.string(forKey: "AppleInterfaceStyle") ?? "Light"
self = InterfaceStyle(rawValue: type)!
}
}
extension NSAppearance {
var isDarkMode: Bool {
let currentStyle = InterfaceStyle()
if #available(OSX 10.14, *) {
return currentStyle == .Dark
} else {
return false
}
}
}

View File

@@ -0,0 +1,237 @@
//
// BottomBar.swift
// I2PLauncher
//
// Created by Mikal Villa on 17/04/2019.
// Copyright © 2019 The I2P Project. All rights reserved.
//
import Cocoa
import SnapKit
import SwiftDate
enum BottomBarStatus {
case undetermined
case updating
case updated(Date)
}
class BottomBar: NSView {
let settingsButton = NSButton()
let reloadButton = NSButton()
let doneButton = NSButton()
let aboutButton = NSButton()
let quitButton = NSButton()
let statusField = NSTextField()
let separator = ServiceTableRowView()
var status: BottomBarStatus = .undetermined {
didSet {
updateStatusText()
}
}
var reloadServicesCallback: () -> Void = {}
var openSettingsCallback: () -> Void = {}
var closeSettingsCallback: () -> Void = {}
override init(frame frameRect: NSRect) {
super.init(frame: frameRect)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
private func commonInit() {
addSubview(separator)
addSubview(settingsButton)
addSubview(reloadButton)
addSubview(statusField)
addSubview(doneButton)
addSubview(aboutButton)
addSubview(quitButton)
let gearIcon = GearIcon()
settingsButton.addSubview(gearIcon)
let refreshIcon = RefreshIcon()
reloadButton.addSubview(refreshIcon)
separator.snp.makeConstraints { make in
make.height.equalTo(1)
make.left.top.right.equalTo(0)
}
settingsButton.snp.makeConstraints { make in
make.height.width.equalTo(30)
make.bottom.left.equalTo(0)
}
gearIcon.snp.makeConstraints { make in
make.centerX.centerY.equalToSuperview()
make.width.height.equalTo(22)
}
reloadButton.snp.makeConstraints { make in
make.height.width.equalTo(30)
make.bottom.right.equalTo(0)
}
refreshIcon.snp.makeConstraints { make in
make.centerX.centerY.equalToSuperview()
make.width.height.equalTo(18)
}
statusField.snp.makeConstraints { make in
make.left.equalTo(settingsButton.snp.right)
make.right.equalTo(reloadButton.snp.left)
make.centerY.equalToSuperview()
}
doneButton.snp.makeConstraints { make in
make.width.equalTo(60)
make.centerY.equalToSuperview()
make.right.equalTo(-3)
}
aboutButton.snp.makeConstraints { make in
make.width.equalTo(56)
make.centerY.equalToSuperview()
make.left.equalTo(quitButton.snp.right).offset(6)
}
quitButton.snp.makeConstraints { make in
make.width.equalTo(46)
make.centerY.equalToSuperview()
make.left.equalTo(3)
}
settingsButton.isBordered = false
settingsButton.bezelStyle = .regularSquare
settingsButton.title = ""
settingsButton.target = self
settingsButton.action = #selector(BottomBar.openSettings)
gearIcon.scaleUnitSquare(to: NSSize(width: 0.46, height: 0.46))
reloadButton.isBordered = false
reloadButton.bezelStyle = .regularSquare
reloadButton.title = ""
reloadButton.target = self
reloadButton.action = #selector(BottomBar.reloadServices)
refreshIcon.scaleUnitSquare(to: NSSize(width: 0.38, height: 0.38))
statusField.isEditable = false
statusField.isBordered = false
statusField.isSelectable = false
let font = NSFont.systemFont(ofSize: 12)
let italicFont = NSFontManager.shared.font(
withFamily: font.fontName,
traits: NSFontTraitMask.italicFontMask,
weight: 5,
size: 10
)
statusField.font = italicFont
statusField.textColor = NSColor.secondaryLabelColor
statusField.maximumNumberOfLines = 1
statusField.backgroundColor = NSColor.clear
statusField.alignment = .center
statusField.cell?.truncatesLastVisibleLine = true
doneButton.title = "Done"
doneButton.bezelStyle = .regularSquare
doneButton.controlSize = .regular
doneButton.isHidden = true
doneButton.target = self
doneButton.action = #selector(BottomBar.closeSettings)
aboutButton.title = "About"
aboutButton.bezelStyle = .regularSquare
aboutButton.controlSize = .regular
aboutButton.isHidden = true
aboutButton.target = self
aboutButton.action = #selector(BottomBar.openAbout)
quitButton.title = "Quit"
quitButton.bezelStyle = .regularSquare
quitButton.controlSize = .regular
quitButton.isHidden = true
quitButton.target = NSApp
quitButton.action = #selector(NSApplication.terminate(_:))
}
func updateStatusText() {
switch status {
case .undetermined: statusField.stringValue = ""
case .updating: statusField.stringValue = "Updating…"
case .updated(let date):
let colloquial = date.toRelative(style: RelativeFormatter.defaultStyle())
statusField.stringValue = "Updated \(colloquial)"
}
}
@objc func reloadServices() {
reloadServicesCallback()
}
@objc func openSettings() {
settingsButton.isHidden = true
statusField.isHidden = true
reloadButton.isHidden = true
doneButton.isHidden = false
aboutButton.isHidden = false
quitButton.isHidden = false
openSettingsCallback()
}
@objc func closeSettings() {
settingsButton.isHidden = false
statusField.isHidden = false
reloadButton.isHidden = false
doneButton.isHidden = true
aboutButton.isHidden = true
quitButton.isHidden = true
closeSettingsCallback()
}
@objc func openAbout() {
let githubLink = ""
let contributorsLink = ""
let openSourceNotice = "I2PLauncher is an open-source project\n\(githubLink)"
let iconGlyphCredit = "Activity glyph (app icon)\nCreated by Gregor Črešnar from the Noun Project"
let contributors = "Contributors\n\(contributorsLink)"
let credits = NSMutableAttributedString(string: "\n\(openSourceNotice)\n\n\(iconGlyphCredit)\n\n\(contributors)\n\n")
let normalFont = NSFont.systemFont(ofSize: 11)
let boldFont = NSFont.boldSystemFont(ofSize: 11)
credits.addAttribute(.font, value: normalFont, range: NSRange(location: 0, length: credits.length))
for word in ["stts", "Activity", "Contributors"] {
credits.addAttribute(.font, value: boldFont, range: (credits.string as NSString).range(of: word))
}
credits.addAttribute(
.link,
value: "https://\(githubLink)",
range: (credits.string as NSString).range(of: githubLink)
)
credits.addAttribute(
.link,
value: "https://\(contributorsLink)",
range: (credits.string as NSString).range(of: contributorsLink)
)
NSApp.orderFrontStandardAboutPanel(options: [NSApplication.AboutPanelOptionKey(rawValue: "Credits"): credits])
NSApp.activate(ignoringOtherApps: true)
}
}

View File

@@ -0,0 +1,18 @@
//
// CustomScrollView.swift
// I2PLauncher
//
// Created by Mikal Villa on 07/04/2019.
// Copyright © 2019 The I2P Project. All rights reserved.
//
import Cocoa
import SnapKit
class CustomScrollView: NSScrollView {
var topConstraint: Constraint?
override var isOpaque: Bool {
return false
}
}

View File

@@ -101,9 +101,9 @@ class EditorTableViewController: NSObject, SwitchableTableViewController {
scrollView.frame.size.height = 400
(NSApp.delegate as? SwiftApplicationDelegate)?.popupController.resizePopup(
/*(NSApp.delegate as? SwiftApplicationDelegate)?.popupController.resizePopup(
height: scrollView.frame.size.height + 30 // bottomBar.frame.size.height
)
)*/
}
func willOpenPopup() {

View File

@@ -0,0 +1,375 @@
//
// Icons.swift
// I2PLauncher
//
// Created by Mikal Villa on 11/04/2019.
// Copyright © 2019 The I2P Project. All rights reserved.
//
import Cocoa
class CheckmarkIcon: NSView {
var color: NSColor = NSColor(calibratedRed: 0.46, green: 0.78, blue: 0.56, alpha: 1) {
didSet {
self.needsDisplay = true
}
}
override func draw(_ dirtyRect: NSRect) {
super.draw(dirtyRect)
color.setStroke()
let checkmarkPath = NSBezierPath()
checkmarkPath.lineWidth = 3
checkmarkPath.move(to: NSPoint(x: 17.01, y: 9.15))
checkmarkPath.curve(to: NSPoint(x: 16.3, y: 9.45),
controlPoint1: NSPoint(x: 16.75, y: 9.15),
controlPoint2: NSPoint(x: 16.5, y: 9.25))
checkmarkPath.line(to: NSPoint(x: 2.3, y: 23.45))
checkmarkPath.line(to: NSPoint(x: 3.71, y: 24.86))
checkmarkPath.line(to: NSPoint(x: 17.01, y: 11.57))
checkmarkPath.line(to: NSPoint(x: 42.3, y: 36.86))
checkmarkPath.line(to: NSPoint(x: 43.71, y: 35.45))
checkmarkPath.line(to: NSPoint(x: 17.71, y: 9.45))
checkmarkPath.curve(to: NSPoint(x: 17.01, y: 9.15),
controlPoint1: NSPoint(x: 17.52, y: 9.25),
controlPoint2: NSPoint(x: 17.26, y: 9.15))
checkmarkPath.close()
checkmarkPath.stroke()
}
}
class CrossIcon: NSView {
var color: NSColor = NSColor(calibratedRed: 0.9, green: 0.78, blue: 0.56, alpha: 1) {
didSet {
self.needsDisplay = true
}
}
override func draw(_ dirtyRect: NSRect) {
super.draw(dirtyRect)
color.setFill()
let context = NSGraphicsContext.current!.cgContext
NSGraphicsContext.saveGraphicsState()
context.translateBy(x: 23, y: 23)
context.rotate(by: -45 * CGFloat.pi / 180)
NSBezierPath(rect: NSRect(x: -26.88, y: -1, width: 53.75, height: 4)).fill()
NSGraphicsContext.restoreGraphicsState()
NSGraphicsContext.saveGraphicsState()
context.translateBy(x: 23, y: 23)
context.rotate(by: -45 * CGFloat.pi / 180)
NSBezierPath(rect: NSRect(x: -1, y: -26.88, width: 4, height: 53.75)).fill()
NSGraphicsContext.restoreGraphicsState()
}
}
class RefreshIcon: NSView {
var color = NSColor.secondaryLabelColor {
didSet {
self.needsDisplay = true
}
}
override func draw(_ dirtyRect: NSRect) {
super.draw(dirtyRect)
color.setFill()
let circle = NSBezierPath()
circle.move(to: NSPoint(x: 23, y: 3))
circle.line(to: NSPoint(x: 23, y: 5))
circle.curve(to: NSPoint(x: 41, y: 23),
controlPoint1: NSPoint(x: 32.93, y: 5),
controlPoint2: NSPoint(x: 41, y: 13.07))
circle.curve(to: NSPoint(x: 23, y: 41),
controlPoint1: NSPoint(x: 41, y: 32.93),
controlPoint2: NSPoint(x: 32.93, y: 41))
circle.curve(to: NSPoint(x: 5, y: 23),
controlPoint1: NSPoint(x: 13.07, y: 41),
controlPoint2: NSPoint(x: 5, y: 32.93))
circle.curve(to: NSPoint(x: 14.47, y: 7.14),
controlPoint1: NSPoint(x: 5, y: 16.37),
controlPoint2: NSPoint(x: 8.63, y: 10.29))
circle.line(to: NSPoint(x: 13.53, y: 5.38))
circle.curve(to: NSPoint(x: 3, y: 23),
controlPoint1: NSPoint(x: 7.03, y: 8.88),
controlPoint2: NSPoint(x: 3, y: 15.63))
circle.curve(to: NSPoint(x: 23, y: 43),
controlPoint1: NSPoint(x: 3, y: 34.03),
controlPoint2: NSPoint(x: 11.97, y: 43))
circle.curve(to: NSPoint(x: 43, y: 23),
controlPoint1: NSPoint(x: 34.03, y: 43),
controlPoint2: NSPoint(x: 43, y: 34.03))
circle.curve(to: NSPoint(x: 23, y: 3),
controlPoint1: NSPoint(x: 43, y: 11.97),
controlPoint2: NSPoint(x: 34.03, y: 3))
circle.close()
circle.fill()
let arrowHead = NSBezierPath()
arrowHead.move(to: NSPoint(x: 4.2, y: 3.02))
arrowHead.line(to: NSPoint(x: 3.8, y: 4.98))
arrowHead.line(to: NSPoint(x: 13, y: 6.82))
arrowHead.line(to: NSPoint(x: 13, y: 16))
arrowHead.line(to: NSPoint(x: 15, y: 16))
arrowHead.line(to: NSPoint(x: 15, y: 6))
arrowHead.curve(to: NSPoint(x: 14.2, y: 5.02),
controlPoint1: NSPoint(x: 15, y: 5.52),
controlPoint2: NSPoint(x: 14.66, y: 5.11))
arrowHead.line(to: NSPoint(x: 4.2, y: 3.02))
arrowHead.close()
arrowHead.fill()
}
}
class GearIcon: NSView {
var color = NSColor.secondaryLabelColor {
didSet {
self.needsDisplay = true
}
}
override func draw(_ dirtyRect: NSRect) {
super.draw(dirtyRect)
color.setFill()
let outerCog = NSBezierPath()
outerCog.move(to: NSPoint(x: 26.76, y: 9.49))
outerCog.curve(to: NSPoint(x: 27.62, y: 8.99),
controlPoint1: NSPoint(x: 27.11, y: 9.49),
controlPoint2: NSPoint(x: 27.44, y: 9.3))
outerCog.line(to: NSPoint(x: 28.4, y: 7.64))
outerCog.curve(to: NSPoint(x: 31.13, y: 6.9),
controlPoint1: NSPoint(x: 28.93, y: 6.72),
controlPoint2: NSPoint(x: 30.21, y: 6.37))
outerCog.line(to: NSPoint(x: 32.87, y: 7.9))
outerCog.curve(to: NSPoint(x: 33.6, y: 10.64),
controlPoint1: NSPoint(x: 33.82, y: 8.46),
controlPoint2: NSPoint(x: 34.15, y: 9.68))
outerCog.line(to: NSPoint(x: 32.82, y: 11.98))
outerCog.curve(to: NSPoint(x: 32.98, y: 13.18),
controlPoint1: NSPoint(x: 32.6, y: 12.37),
controlPoint2: NSPoint(x: 32.66, y: 12.86))
outerCog.curve(to: NSPoint(x: 36.48, y: 19.26),
controlPoint1: NSPoint(x: 34.63, y: 14.87),
controlPoint2: NSPoint(x: 35.85, y: 16.97))
outerCog.curve(to: NSPoint(x: 37.44, y: 19.99),
controlPoint1: NSPoint(x: 36.6, y: 19.69),
controlPoint2: NSPoint(x: 36.99, y: 19.99))
outerCog.line(to: NSPoint(x: 39, y: 19.99))
outerCog.curve(to: NSPoint(x: 41, y: 21.99),
controlPoint1: NSPoint(x: 40.1, y: 19.99),
controlPoint2: NSPoint(x: 41, y: 20.89))
outerCog.line(to: NSPoint(x: 41, y: 23.99))
outerCog.curve(to: NSPoint(x: 39, y: 25.99),
controlPoint1: NSPoint(x: 41, y: 25.1),
controlPoint2: NSPoint(x: 40.1, y: 25.99))
outerCog.line(to: NSPoint(x: 37.44, y: 25.99))
outerCog.curve(to: NSPoint(x: 36.48, y: 26.73),
controlPoint1: NSPoint(x: 36.99, y: 25.99),
controlPoint2: NSPoint(x: 36.6, y: 26.29))
outerCog.curve(to: NSPoint(x: 32.98, y: 32.81),
controlPoint1: NSPoint(x: 35.85, y: 29.02),
controlPoint2: NSPoint(x: 34.63, y: 31.12))
outerCog.curve(to: NSPoint(x: 32.82, y: 34.01),
controlPoint1: NSPoint(x: 32.66, y: 33.13),
controlPoint2: NSPoint(x: 32.6, y: 33.62))
outerCog.line(to: NSPoint(x: 33.6, y: 35.35))
outerCog.curve(to: NSPoint(x: 32.87, y: 38.08),
controlPoint1: NSPoint(x: 34.15, y: 36.3),
controlPoint2: NSPoint(x: 33.82, y: 37.53))
outerCog.line(to: NSPoint(x: 31.13, y: 39.08))
outerCog.curve(to: NSPoint(x: 28.4, y: 38.35),
controlPoint1: NSPoint(x: 30.21, y: 39.62),
controlPoint2: NSPoint(x: 28.93, y: 39.26))
outerCog.line(to: NSPoint(x: 27.62, y: 37))
outerCog.curve(to: NSPoint(x: 26.51, y: 36.53),
controlPoint1: NSPoint(x: 27.4, y: 36.61),
controlPoint2: NSPoint(x: 26.94, y: 36.42))
outerCog.curve(to: NSPoint(x: 19.49, y: 36.53),
controlPoint1: NSPoint(x: 24.14, y: 37.14),
controlPoint2: NSPoint(x: 21.86, y: 37.14))
outerCog.curve(to: NSPoint(x: 18.38, y: 37),
controlPoint1: NSPoint(x: 19.06, y: 36.42),
controlPoint2: NSPoint(x: 18.6, y: 36.61))
outerCog.line(to: NSPoint(x: 17.6, y: 38.35))
outerCog.curve(to: NSPoint(x: 14.87, y: 39.08),
controlPoint1: NSPoint(x: 17.07, y: 39.26),
controlPoint2: NSPoint(x: 15.79, y: 39.62))
outerCog.line(to: NSPoint(x: 13.13, y: 38.08))
outerCog.curve(to: NSPoint(x: 12.4, y: 35.35),
controlPoint1: NSPoint(x: 12.18, y: 37.53),
controlPoint2: NSPoint(x: 11.85, y: 36.3))
outerCog.line(to: NSPoint(x: 13.18, y: 34.01))
outerCog.curve(to: NSPoint(x: 13.02, y: 32.81),
controlPoint1: NSPoint(x: 13.4, y: 33.62),
controlPoint2: NSPoint(x: 13.34, y: 33.13))
outerCog.curve(to: NSPoint(x: 9.52, y: 26.73),
controlPoint1: NSPoint(x: 11.36, y: 31.12),
controlPoint2: NSPoint(x: 10.15, y: 29.02))
outerCog.curve(to: NSPoint(x: 8.56, y: 25.99),
controlPoint1: NSPoint(x: 9.4, y: 26.29),
controlPoint2: NSPoint(x: 9.01, y: 25.99))
outerCog.line(to: NSPoint(x: 7, y: 25.99))
outerCog.curve(to: NSPoint(x: 5, y: 23.99),
controlPoint1: NSPoint(x: 5.9, y: 25.99),
controlPoint2: NSPoint(x: 5, y: 25.1))
outerCog.line(to: NSPoint(x: 5, y: 21.99))
outerCog.curve(to: NSPoint(x: 7, y: 19.99),
controlPoint1: NSPoint(x: 5, y: 20.89),
controlPoint2: NSPoint(x: 5.9, y: 19.99))
outerCog.line(to: NSPoint(x: 8.56, y: 19.99))
outerCog.curve(to: NSPoint(x: 9.52, y: 19.26),
controlPoint1: NSPoint(x: 9.01, y: 19.99),
controlPoint2: NSPoint(x: 9.4, y: 19.69))
outerCog.curve(to: NSPoint(x: 13.02, y: 13.18),
controlPoint1: NSPoint(x: 10.15, y: 16.97),
controlPoint2: NSPoint(x: 11.36, y: 14.87))
outerCog.curve(to: NSPoint(x: 13.18, y: 11.98),
controlPoint1: NSPoint(x: 13.34, y: 12.86),
controlPoint2: NSPoint(x: 13.4, y: 12.37))
outerCog.line(to: NSPoint(x: 12.4, y: 10.64))
outerCog.curve(to: NSPoint(x: 13.13, y: 7.9),
controlPoint1: NSPoint(x: 11.85, y: 9.68),
controlPoint2: NSPoint(x: 12.18, y: 8.46))
outerCog.line(to: NSPoint(x: 14.87, y: 6.9))
outerCog.curve(to: NSPoint(x: 17.6, y: 7.64),
controlPoint1: NSPoint(x: 15.79, y: 6.37),
controlPoint2: NSPoint(x: 17.07, y: 6.73))
outerCog.line(to: NSPoint(x: 18.38, y: 8.99))
outerCog.curve(to: NSPoint(x: 19.49, y: 9.46),
controlPoint1: NSPoint(x: 18.6, y: 9.38),
controlPoint2: NSPoint(x: 19.06, y: 9.57))
outerCog.curve(to: NSPoint(x: 26.51, y: 9.45),
controlPoint1: NSPoint(x: 21.86, y: 8.84),
controlPoint2: NSPoint(x: 24.14, y: 8.84))
outerCog.curve(to: NSPoint(x: 26.76, y: 9.49),
controlPoint1: NSPoint(x: 26.59, y: 9.48),
controlPoint2: NSPoint(x: 26.67, y: 9.49))
outerCog.close()
outerCog.move(to: NSPoint(x: 30.14, y: 4.64))
outerCog.curve(to: NSPoint(x: 26.67, y: 6.64),
controlPoint1: NSPoint(x: 28.71, y: 4.64),
controlPoint2: NSPoint(x: 27.38, y: 5.4))
outerCog.line(to: NSPoint(x: 26.26, y: 7.34))
outerCog.curve(to: NSPoint(x: 19.74, y: 7.34),
controlPoint1: NSPoint(x: 24.06, y: 6.88),
controlPoint2: NSPoint(x: 21.94, y: 6.88))
outerCog.line(to: NSPoint(x: 19.33, y: 6.64))
outerCog.curve(to: NSPoint(x: 15.86, y: 4.64),
controlPoint1: NSPoint(x: 18.62, y: 5.4),
controlPoint2: NSPoint(x: 17.29, y: 4.64))
outerCog.curve(to: NSPoint(x: 13.87, y: 5.17),
controlPoint1: NSPoint(x: 15.16, y: 4.64),
controlPoint2: NSPoint(x: 14.47, y: 4.82))
outerCog.line(to: NSPoint(x: 12.13, y: 6.17))
outerCog.curve(to: NSPoint(x: 10.67, y: 11.64),
controlPoint1: NSPoint(x: 10.22, y: 7.28),
controlPoint2: NSPoint(x: 9.57, y: 9.73))
outerCog.line(to: NSPoint(x: 11.07, y: 12.34))
outerCog.curve(to: NSPoint(x: 7.81, y: 17.99),
controlPoint1: NSPoint(x: 9.61, y: 13.97),
controlPoint2: NSPoint(x: 8.5, y: 15.9))
outerCog.line(to: NSPoint(x: 7, y: 17.99))
outerCog.curve(to: NSPoint(x: 3, y: 21.99),
controlPoint1: NSPoint(x: 4.79, y: 17.99),
controlPoint2: NSPoint(x: 3, y: 19.79))
outerCog.line(to: NSPoint(x: 3, y: 23.99))
outerCog.curve(to: NSPoint(x: 7, y: 27.99),
controlPoint1: NSPoint(x: 3, y: 26.2),
controlPoint2: NSPoint(x: 4.79, y: 27.99))
outerCog.line(to: NSPoint(x: 7.81, y: 27.99))
outerCog.curve(to: NSPoint(x: 11.07, y: 33.65),
controlPoint1: NSPoint(x: 8.5, y: 30.08),
controlPoint2: NSPoint(x: 9.61, y: 32.02))
outerCog.line(to: NSPoint(x: 10.67, y: 34.35))
outerCog.curve(to: NSPoint(x: 12.13, y: 39.81),
controlPoint1: NSPoint(x: 9.57, y: 36.26),
controlPoint2: NSPoint(x: 10.22, y: 38.71))
outerCog.line(to: NSPoint(x: 13.87, y: 40.81))
outerCog.curve(to: NSPoint(x: 15.86, y: 41.35),
controlPoint1: NSPoint(x: 14.47, y: 41.16),
controlPoint2: NSPoint(x: 15.16, y: 41.35))
outerCog.curve(to: NSPoint(x: 19.33, y: 39.35),
controlPoint1: NSPoint(x: 17.29, y: 41.35),
controlPoint2: NSPoint(x: 18.62, y: 40.58))
outerCog.line(to: NSPoint(x: 19.74, y: 38.64))
outerCog.curve(to: NSPoint(x: 26.26, y: 38.64),
controlPoint1: NSPoint(x: 21.94, y: 39.11),
controlPoint2: NSPoint(x: 24.06, y: 39.11))
outerCog.line(to: NSPoint(x: 26.67, y: 39.35))
outerCog.curve(to: NSPoint(x: 30.14, y: 41.35),
controlPoint1: NSPoint(x: 27.38, y: 40.58),
controlPoint2: NSPoint(x: 28.71, y: 41.35))
outerCog.curve(to: NSPoint(x: 32.13, y: 40.81),
controlPoint1: NSPoint(x: 30.84, y: 41.35),
controlPoint2: NSPoint(x: 31.53, y: 41.16))
outerCog.line(to: NSPoint(x: 33.87, y: 39.81))
outerCog.curve(to: NSPoint(x: 35.33, y: 34.35),
controlPoint1: NSPoint(x: 35.78, y: 38.71),
controlPoint2: NSPoint(x: 36.43, y: 36.26))
outerCog.line(to: NSPoint(x: 34.93, y: 33.65))
outerCog.curve(to: NSPoint(x: 38.19, y: 27.99),
controlPoint1: NSPoint(x: 36.39, y: 32.02),
controlPoint2: NSPoint(x: 37.5, y: 30.08))
outerCog.line(to: NSPoint(x: 39, y: 27.99))
outerCog.curve(to: NSPoint(x: 43, y: 23.99),
controlPoint1: NSPoint(x: 41.21, y: 27.99),
controlPoint2: NSPoint(x: 43, y: 26.2))
outerCog.line(to: NSPoint(x: 43, y: 21.99))
outerCog.curve(to: NSPoint(x: 39, y: 17.99),
controlPoint1: NSPoint(x: 43, y: 19.79),
controlPoint2: NSPoint(x: 41.21, y: 17.99))
outerCog.line(to: NSPoint(x: 38.19, y: 17.99))
outerCog.curve(to: NSPoint(x: 34.93, y: 12.34),
controlPoint1: NSPoint(x: 37.5, y: 15.9),
controlPoint2: NSPoint(x: 36.39, y: 13.97))
outerCog.line(to: NSPoint(x: 35.33, y: 11.64))
outerCog.curve(to: NSPoint(x: 33.87, y: 6.17),
controlPoint1: NSPoint(x: 36.43, y: 9.73),
controlPoint2: NSPoint(x: 35.78, y: 7.28))
outerCog.line(to: NSPoint(x: 32.13, y: 5.17))
outerCog.curve(to: NSPoint(x: 30.14, y: 4.64),
controlPoint1: NSPoint(x: 31.53, y: 4.82),
controlPoint2: NSPoint(x: 30.84, y: 4.64))
outerCog.close()
outerCog.fill()
let innerCircle = NSBezierPath()
innerCircle.move(to: NSPoint(x: 23, y: 29.99))
innerCircle.curve(to: NSPoint(x: 16, y: 22.99),
controlPoint1: NSPoint(x: 19.14, y: 29.99),
controlPoint2: NSPoint(x: 16, y: 26.85))
innerCircle.curve(to: NSPoint(x: 23, y: 15.99),
controlPoint1: NSPoint(x: 16, y: 19.13),
controlPoint2: NSPoint(x: 19.14, y: 15.99))
innerCircle.curve(to: NSPoint(x: 30, y: 22.99),
controlPoint1: NSPoint(x: 26.86, y: 15.99),
controlPoint2: NSPoint(x: 30, y: 19.13))
innerCircle.curve(to: NSPoint(x: 23, y: 29.99),
controlPoint1: NSPoint(x: 30, y: 26.85),
controlPoint2: NSPoint(x: 26.86, y: 29.99))
innerCircle.close()
innerCircle.move(to: NSPoint(x: 23, y: 13.99))
innerCircle.curve(to: NSPoint(x: 14, y: 22.99),
controlPoint1: NSPoint(x: 18.04, y: 13.99),
controlPoint2: NSPoint(x: 14, y: 18.03))
innerCircle.curve(to: NSPoint(x: 23, y: 31.99),
controlPoint1: NSPoint(x: 14, y: 27.96),
controlPoint2: NSPoint(x: 18.04, y: 31.99))
innerCircle.curve(to: NSPoint(x: 32, y: 22.99),
controlPoint1: NSPoint(x: 27.96, y: 31.99),
controlPoint2: NSPoint(x: 32, y: 27.96))
innerCircle.curve(to: NSPoint(x: 23, y: 13.99),
controlPoint1: NSPoint(x: 32, y: 18.03),
controlPoint2: NSPoint(x: 27.96, y: 13.99))
innerCircle.close()
innerCircle.fill()
}
}

View File

@@ -0,0 +1,37 @@
//
// ServiceTableRowView.swift
// I2PLauncher
//
// Created by Mikal Villa on 19/04/2019.
// Copyright © 2019 The I2P Project. All rights reserved.
//
import Cocoa
class ServiceTableRowView: NSTableRowView {
var showSeparator = true
var gradient: CAGradientLayer?
override func layout() {
super.layout()
let width = frame.size.width
let height = frame.size.height
let gradient = self.gradient ?? CAGradientLayer()
gradient.isHidden = !showSeparator
self.wantsLayer = true
self.layer?.insertSublayer(gradient, at: 0)
self.gradient = gradient
let separatorColor = NSColor.quaternaryLabelColor.cgColor
gradient.colors = [NSColor.clear.cgColor, separatorColor, separatorColor, separatorColor, NSColor.clear.cgColor]
gradient.locations = [0, 0.3, 0.5, 0.70, 1]
gradient.startPoint = CGPoint(x: 0, y: 0.5)
gradient.endPoint = CGPoint(x: 1, y: 0.5)
gradient.frame = CGRect(x: 0, y: height - 1, width: width, height: 1)
}
}

View File

@@ -0,0 +1,266 @@
//
// ServiceTableViewController.swift
// I2PLauncher
//
// Created by Mikal Villa on 20/04/2019.
// Copyright © 2019 The I2P Project. All rights reserved.
//
import Cocoa
import SnapKit
import MBPopup
class ServiceTableViewController: NSObject, SwitchableTableViewController {
let contentView = NSStackView(frame: CGRect(x: 0, y: 0, width: 220, height: 400))
let scrollView = CustomScrollView()
let tableView = NSTableView()
let bottomBar = BottomBar()
let addServicesNoticeField = NSTextField()
var editorTableViewController: EditorTableViewController
var services: [BaseService] = [I2PRouterService(),HttpTunnelService(),IrcTunnelService()] {
didSet {
addServicesNoticeField.isHidden = services.count > 0
}
}
var servicesBeingUpdated = [BaseService]()
var generalStatus: ServiceStatus {
let hasBadServices = services.first { $0.status > .stopped } != nil
return hasBadServices ? .killed : .started
}
var hidden: Bool = false
var updateCallback: (() -> Void)?
override init() {
self.editorTableViewController = EditorTableViewController(contentView: contentView, scrollView: scrollView)
super.init()
}
func setup() {
//bottomBar.reloadServicesCallback = (NSApp.delegate as? SwiftApplicationDelegate)!.updateServices
bottomBar.openSettingsCallback = { [weak self] in
self?.hide()
self?.editorTableViewController.show()
}
bottomBar.closeSettingsCallback = { [weak self] in
self?.editorTableViewController.hide()
self?.show()
}
contentView.snp.makeConstraints { make in
make.left.right.bottom.equalTo(0)
make.width.greaterThanOrEqualTo(220)
make.height.greaterThanOrEqualTo(40 + 30 + 2) // tableView.rowHeight + bottomBar.frame.size.height + 2
}
contentView.addSubview(scrollView)
contentView.addSubview(bottomBar)
scrollView.snp.makeConstraints { make in
scrollView.topConstraint = make.top.equalToSuperview().constraint
make.left.right.equalTo(0)
}
bottomBar.snp.makeConstraints { make in
make.width.equalToSuperview()
make.top.equalTo(scrollView.snp.bottom)
make.height.equalTo(30)
make.left.right.equalTo(0)
make.bottom.equalTo(0)
}
contentView.addSubview(addServicesNoticeField)
addServicesNoticeField.snp.makeConstraints { make in
make.height.equalTo(22)
make.left.right.equalTo(0)
make.centerY.equalToSuperview().offset(-14)
}
scrollView.borderType = .noBorder
scrollView.hasVerticalScroller = true
scrollView.hasHorizontalScroller = false
scrollView.autoresizesSubviews = true
scrollView.documentView = tableView
scrollView.drawsBackground = false
scrollView.wantsLayer = true
scrollView.layer?.cornerRadius = 6
tableView.frame = scrollView.bounds
let column = NSTableColumn(identifier: NSUserInterfaceItemIdentifier(rawValue: "serviceColumnIdentifier"))
column.width = tableView.frame.size.width
tableView.addTableColumn(column)
tableView.autoresizesSubviews = true
tableView.wantsLayer = true
tableView.layer?.cornerRadius = 6
tableView.headerView = nil
tableView.rowHeight = 40
tableView.gridStyleMask = NSTableView.GridLineStyle.init(rawValue: 0)
tableView.dataSource = self
tableView.delegate = self
tableView.selectionHighlightStyle = .none
tableView.backgroundColor = NSColor.clear
addServicesNoticeField.isEditable = false
addServicesNoticeField.isBordered = false
addServicesNoticeField.isSelectable = false
let italicFont = NSFontManager.shared.font(
withFamily: NSFont.systemFont(ofSize: 13).fontName,
traits: NSFontTraitMask.italicFontMask,
weight: 5,
size: 13
)
addServicesNoticeField.font = italicFont
addServicesNoticeField.textColor = NSColor.textColor
addServicesNoticeField.maximumNumberOfLines = 1
addServicesNoticeField.cell!.truncatesLastVisibleLine = true
addServicesNoticeField.alignment = .center
addServicesNoticeField.stringValue = "Oh, maybe too empty? :)"
addServicesNoticeField.backgroundColor = .clear
}
func willOpenPopup() {
resizeViews()
reloadData()
if case let .updated(date) = bottomBar.status {
if Date().timeIntervalSince1970 - date.timeIntervalSince1970 > 60 {
//(NSApp.delegate as? SwiftApplicationDelegate)?.updateServices()
}
}
}
func willShow() {
scrollView.topConstraint?.update(offset: 0)
scrollView.documentView = tableView
if editorTableViewController.selectionChanged {
//self.services = ["I2PRouter","HttpTunnel"]//Preferences.shared.selectedServices
reloadData()
//(NSApp.delegate as? SwiftApplicationDelegate)?.updateServices()
} else {
addServicesNoticeField.isHidden = services.count > 0
}
resizeViews()
}
func willHide() {
addServicesNoticeField.isHidden = true
}
func resizeViews() {
var frame = scrollView.frame
frame.size.height = min(tableView.intrinsicContentSize.height, 490)
scrollView.frame = frame
//(NSApp.delegate as? SwiftApplicationDelegate)?.popupController.resizePopup(height: scrollView.frame.size.height + bottomBar.frame.size.height)
}
func reloadData(at index: Int? = nil) {
services.sort()
bottomBar.updateStatusText()
guard index != nil else {
tableView.reloadData()
return
}
tableView.reloadData(forRowIndexes: IndexSet(integer: index!), columnIndexes: IndexSet(integer: 0))
}
func updateServices(updateCallback: @escaping () -> Void) {
self.servicesBeingUpdated = [Service]()
guard services.count > 0 else {
reloadData()
bottomBar.status = .updated(Date())
self.updateCallback?()
self.updateCallback = nil
return
}
self.updateCallback = updateCallback
let serviceCallback: ((BaseService) -> Void) = { [weak self] service in self?.updatedStatus(for: service) }
bottomBar.status = .updating
services.forEach {
servicesBeingUpdated.append($0)
$0.updateStatus(callback: serviceCallback)
}
}
func updatedStatus(for service: BaseService) {
if let index = servicesBeingUpdated.index(of: service) {
servicesBeingUpdated.remove(at: index)
}
DispatchQueue.main.async { [weak self] in
self?.reloadData()
if self?.servicesBeingUpdated.count == 0 {
self?.bottomBar.status = .updated(Date())
self?.updateCallback?()
self?.updateCallback = nil
}
}
}
}
extension ServiceTableViewController: NSTableViewDataSource {
func numberOfRows(in tableView: NSTableView) -> Int {
return services.count
}
func tableView(_ tableView: NSTableView, objectValueFor tableColumn: NSTableColumn?, row: Int) -> Any? {
return nil
}
}
extension ServiceTableViewController: NSTableViewDelegate {
func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
let identifier = tableColumn?.identifier ?? NSUserInterfaceItemIdentifier(rawValue: "identifier")
let cell = tableView.makeView(withIdentifier: identifier, owner: self) ?? StatusTableCell()
guard let view = cell as? StatusTableCell else { return nil }
guard let service = services[row] as? Service else { return nil }
view.textField?.stringValue = service.name
view.statusField.stringValue = service.message
view.statusIndicator.status = service.status
return view
}
func tableView(_ tableView: NSTableView, rowViewForRow row: Int) -> NSTableRowView? {
let cellIdentifier = NSUserInterfaceItemIdentifier(rawValue: "rowView")
let cell = tableView.makeView(withIdentifier: cellIdentifier, owner: self) ?? ServiceTableRowView()
guard let view = cell as? ServiceTableRowView else { return nil }
view.showSeparator = row + 1 < services.count
return view
}
func tableView(_ tableView: NSTableView, shouldSelectRow row: Int) -> Bool {
guard let _ = services[row] as? Service else { return false }
//NSWorkspace.shared.open(service.url)
return false
}
}

View File

@@ -0,0 +1,62 @@
//
// StatusIndicator.swift
// I2PLauncher
//
// Created by Mikal Villa on 09/04/2019.
// Copyright © 2019 The I2P Project. All rights reserved.
//
import Cocoa
class StatusIndicator: NSView {
var checkmarkIcon = CheckmarkIcon()
var crossIcon = CrossIcon()
var status: ServiceStatus = .started {
didSet {
checkmarkIcon.isHidden = status > .stopped
crossIcon.isHidden = status <= .stopped
switch status {
case .undetermined: crossIcon.color = StatusColor.gray
case .waiting: checkmarkIcon.color = StatusColor.gray
case .started: checkmarkIcon.color = StatusColor.green
case .notice: checkmarkIcon.color = StatusColor.green
case .killed: checkmarkIcon.color = StatusColor.red
case .crashed: checkmarkIcon.color = StatusColor.red
case .stopped: crossIcon.color = StatusColor.blue
case .restarting: crossIcon.color = StatusColor.orange
}
}
}
init() {
super.init(frame: NSRect.zero)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
private func commonInit() {
addSubview(checkmarkIcon)
addSubview(crossIcon)
}
override func setFrameSize(_ newSize: NSSize) {
super.setFrameSize(newSize)
checkmarkIcon.frame = bounds
crossIcon.frame = bounds
}
}
class StatusColor {
static var green = NSColor(calibratedRed: 0.36, green: 0.68, blue: 0.46, alpha: 1)
static var blue = NSColor(calibratedRed: 0.24, green: 0.54, blue: 1, alpha: 0.8)
static var orange = NSColor.orange
static var red = NSColor(calibratedRed: 0.9, green: 0.4, blue: 0.23, alpha: 1)
static var gray = NSColor.tertiaryLabelColor
}

View File

@@ -0,0 +1,78 @@
//
// StatusTableCell.swift
// I2PLauncher
//
// Created by Mikal Villa on 18/04/2019.
// Copyright © 2019 The I2P Project. All rights reserved.
//
import Cocoa
import SnapKit
class StatusTableCell: NSTableCellView {
let statusIndicator = StatusIndicator()
let statusField = NSTextField()
override init(frame frameRect: NSRect) {
super.init(frame: frameRect)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
private func commonInit() {
statusIndicator.scaleUnitSquare(to: NSSize(width: 0.3, height: 0.3))
addSubview(statusIndicator)
statusIndicator.snp.makeConstraints { make in
make.height.width.equalTo(14)
make.left.equalTo(8)
make.centerY.equalToSuperview()
}
let textField = NSTextField()
textField.isEditable = false
textField.isBordered = false
textField.isSelectable = false
self.textField = textField
let font = NSFont.systemFont(ofSize: 12)
textField.font = font
textField.textColor = NSColor.labelColor
textField.backgroundColor = NSColor.clear
addSubview(textField)
textField.snp.makeConstraints { make in
make.height.equalTo(18)
make.leading.equalTo(statusIndicator.snp.trailing).offset(4)
make.trailing.equalTo(8)
make.centerY.equalToSuperview().offset(-8)
}
statusField.isEditable = false
statusField.isBordered = false
statusField.isSelectable = false
let italicFont = NSFontManager.shared.font(
withFamily: font.fontName,
traits: NSFontTraitMask.italicFontMask,
weight: 5,
size: 10
)
statusField.font = italicFont
statusField.textColor = NSColor.secondaryLabelColor
statusField.maximumNumberOfLines = 1
statusField.cell!.truncatesLastVisibleLine = true
statusField.backgroundColor = NSColor.clear
addSubview(statusField)
statusField.snp.makeConstraints { make in
make.height.equalTo(18)
make.leading.equalTo(statusIndicator.snp.trailing).offset(4)
make.trailing.equalToSuperview().offset(-8)
make.centerY.equalToSuperview().offset(10)
}
}
}

View File

@@ -0,0 +1,31 @@
//
// SwitchableTableViewController.swift
// I2PLauncher
//
// Created by Mikal Villa on 30/04/2019.
// Copyright © 2019 The I2P Project. All rights reserved.
//
import Cocoa
protocol SwitchableTableViewController {
var hidden: Bool { get set }
mutating func show()
mutating func hide()
func willShow()
func willHide()
}
extension SwitchableTableViewController {
mutating func show() {
self.hidden = false
self.willShow()
}
mutating func hide() {
self.hidden = true
self.willHide()
}
}