forked from I2P_Developers/i2p.i2p
Mac OSX Launcher: (feature disabled, WIP) new pretty userinterface.
This commit is contained in:
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@@ -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)
|
||||
|
||||
}
|
||||
}
|
||||
|
@@ -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
|
||||
}
|
||||
}
|
@@ -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() {
|
||||
|
375
launchers/macosx/I2PLauncher/userinterface/Style2019/Icons.swift
Normal file
375
launchers/macosx/I2PLauncher/userinterface/Style2019/Icons.swift
Normal 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()
|
||||
}
|
||||
}
|
@@ -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)
|
||||
}
|
||||
}
|
||||
|
@@ -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
|
||||
}
|
||||
}
|
@@ -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
|
||||
}
|
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
@@ -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()
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user