From cf82076540b4d6dd1f2558383581f91fe2fbf700 Mon Sep 17 00:00:00 2001 From: ttsymlov Date: Wed, 18 Sep 2019 00:24:08 +0200 Subject: [PATCH] add "Enable Gradient Color" option for CPU and Memory usage bar --- .gitignore | 2 + iGlance/iGlance.xcodeproj/project.pbxproj | 4 ++ iGlance/iGlance/AppDelegate.swift | 28 ++++++++- iGlance/iGlance/Base.lproj/Main.storyboard | 62 +++++++++++++++++++ .../Components/CpuUsageComponent.swift | 42 +++++++++---- .../Components/MemUsageComponent.swift | 35 ++++++++--- .../iGlance/SettingsWindowViews/CpuView.swift | 47 ++++++++++++++ .../SettingsWindowViews/MemoryView.swift | 48 ++++++++++++++ iGlance/iGlance/Utils/GradientImage.swift | 60 ++++++++++++++++++ 9 files changed, 304 insertions(+), 24 deletions(-) create mode 100644 iGlance/iGlance/Utils/GradientImage.swift diff --git a/.gitignore b/.gitignore index 0374c7e..ce5648c 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,5 @@ iGlance/iGlance\.xcodeproj/project\.xcworkspace/ iGlance/iGlance\.xcodeproj/xcuserdata/ *.DS_Store + +.idea diff --git a/iGlance/iGlance.xcodeproj/project.pbxproj b/iGlance/iGlance.xcodeproj/project.pbxproj index 45a64fb..1845ae7 100644 --- a/iGlance/iGlance.xcodeproj/project.pbxproj +++ b/iGlance/iGlance.xcodeproj/project.pbxproj @@ -40,6 +40,7 @@ B4EF0F3E2184CE3D00A28F5E /* FanView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4EF0F3D2184CE3D00A28F5E /* FanView.swift */; }; B4EF0F402184CEA800A28F5E /* NetworkView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4EF0F3F2184CEA800A28F5E /* NetworkView.swift */; }; B4FF095120D02270006BE560 /* BatteryComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4FF095020D02270006BE560 /* BatteryComponent.swift */; }; + D574CB478F9CB8632C3F5178 /* GradientImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = D574CD590FABD2E739D0B44C /* GradientImage.swift */; }; /* End PBXBuildFile section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -94,6 +95,7 @@ B4EF0F3D2184CE3D00A28F5E /* FanView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FanView.swift; sourceTree = ""; }; B4EF0F3F2184CEA800A28F5E /* NetworkView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkView.swift; sourceTree = ""; }; B4FF095020D02270006BE560 /* BatteryComponent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BatteryComponent.swift; sourceTree = ""; }; + D574CD590FABD2E739D0B44C /* GradientImage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GradientImage.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -179,6 +181,7 @@ isa = PBXGroup; children = ( B4A54A902218723200957E17 /* MenuBarGraph.swift */, + D574CD590FABD2E739D0B44C /* GradientImage.swift */, ); path = Utils; sourceTree = ""; @@ -386,6 +389,7 @@ B4DD136A21F3C59C00652861 /* MemUsageComponent.swift in Sources */, 02B8120920C95D6600C98F0D /* CPUMenuView.swift in Sources */, 025AC38B20C0AFDD000E0837 /* AppDelegate.swift in Sources */, + D574CB478F9CB8632C3F5178 /* GradientImage.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/iGlance/iGlance/AppDelegate.swift b/iGlance/iGlance/AppDelegate.swift index 532e16b..69b28e6 100644 --- a/iGlance/iGlance/AppDelegate.swift +++ b/iGlance/iGlance/AppDelegate.swift @@ -70,10 +70,12 @@ class AppDelegate: NSObject, NSApplicationDelegate { static var userWantsCPUUtil = false static var userWantsCPUTemp = false static var userWantsAutostart = false - static var cpuColor = NSColor.red + static var cpuColor = NSColor.green + static var cpuColor2 = NSColor.red static var cpuUsageVisualization = VisualizationType.Bar static var cpuGraphWidth = 27 static var memColor = NSColor.green + static var memColor2 = NSColor.red static var memUsageVisualization = VisualizationType.Bar static var memGraphWidth = 27 static var updateInterval = 1.0 @@ -84,6 +86,8 @@ class AppDelegate: NSObject, NSApplicationDelegate { static var userWantsBatteryNotification = true static var lowerBatteryNotificationValue = 20 static var upperBatteryNotificationValue = 80 + static var userWantsCPUGradientColor = false + static var userWantsMemGradientColor = false } /** @@ -274,6 +278,14 @@ class AppDelegate: NSObject, NSApplicationDelegate { UserSettings.memColor = NSColor(calibratedRed: colRedMem, green: colGreenMem, blue: colBlueMem, alpha: colAlphaMem) } + if UserDefaults.standard.value(forKey: "colRedMem2") != nil { + let red = UserDefaults.standard.value(forKey: "colRedMem2") as! CGFloat + let green = UserDefaults.standard.value(forKey: "colGreenMem2") as! CGFloat + let blue = UserDefaults.standard.value(forKey: "colBlueMem2") as! CGFloat + let alpha = UserDefaults.standard.value(forKey: "colAlphaMem2") as! CGFloat + UserSettings.memColor2 = NSColor(calibratedRed: red, green: green, blue: blue, alpha: alpha) + } + if UserDefaults.standard.value(forKey: "colRedCPU") != nil { colRedCPU = UserDefaults.standard.value(forKey: "colRedCPU") as! CGFloat colGreenCPU = UserDefaults.standard.value(forKey: "colGreenCPU") as! CGFloat @@ -282,6 +294,14 @@ class AppDelegate: NSObject, NSApplicationDelegate { UserSettings.cpuColor = NSColor(calibratedRed: colRedCPU, green: colGreenCPU, blue: colBlueCPU, alpha: colAlphaCPU) } + if UserDefaults.standard.value(forKey: "colRedCPU2") != nil { + let red = UserDefaults.standard.value(forKey: "colRedCPU2") as! CGFloat + let green = UserDefaults.standard.value(forKey: "colGreenCPU2") as! CGFloat + let blue = UserDefaults.standard.value(forKey: "colBlueCPU2") as! CGFloat + let alpha = UserDefaults.standard.value(forKey: "colAlphaCPU2") as! CGFloat + UserSettings.cpuColor2 = NSColor(calibratedRed: red, green: green, blue: blue, alpha: alpha) + } + if UserDefaults.standard.value(forKey: "userWantsCPUUtil") != nil { UserSettings.userWantsCPUUtil = UserDefaults.standard.value(forKey: "userWantsCPUUtil") as! Bool } @@ -323,9 +343,15 @@ class AppDelegate: NSObject, NSApplicationDelegate { if UserDefaults.standard.value(forKey: "userWantsCPUBorder") != nil { UserSettings.userWantsCPUBorder = UserDefaults.standard.value(forKey: "userWantsCPUBorder") as! Bool } + if UserDefaults.standard.value(forKey: "userWantsCPUGradientColor") != nil { + UserSettings.userWantsCPUGradientColor = UserDefaults.standard.value(forKey: "userWantsCPUGradientColor") as! Bool + } if UserDefaults.standard.value(forKey: "userWantsMemBorder") != nil { UserSettings.userWantsMemBorder = UserDefaults.standard.value(forKey: "userWantsMemBorder") as! Bool } + if UserDefaults.standard.value(forKey: "userWantsMemGradientColor") != nil { + UserSettings.userWantsMemGradientColor = UserDefaults.standard.value(forKey: "userWantsMemGradientColor") as! Bool + } if UserDefaults.standard.value(forKey: "userWantsBatteryUtil") != nil { UserSettings.userWantsBatteryUtil = UserDefaults.standard.value(forKey: "userWantsBatteryUtil") as! Bool } diff --git a/iGlance/iGlance/Base.lproj/Main.storyboard b/iGlance/iGlance/Base.lproj/Main.storyboard index 54929ce..984f26d 100644 --- a/iGlance/iGlance/Base.lproj/Main.storyboard +++ b/iGlance/iGlance/Base.lproj/Main.storyboard @@ -1079,18 +1079,49 @@ + + + + + + @@ -1201,16 +1232,47 @@ + + + + + + diff --git a/iGlance/iGlance/Components/CpuUsageComponent.swift b/iGlance/iGlance/Components/CpuUsageComponent.swift index 4cede88..32091dd 100644 --- a/iGlance/iGlance/Components/CpuUsageComponent.swift +++ b/iGlance/iGlance/Components/CpuUsageComponent.swift @@ -27,14 +27,19 @@ import Cocoa class CpuUsageComponent { - + + static let usageBarMaxHeight: Double = 16.0 // 32*0.5 + static let usageBarWidth: Double = 7.0 // 14*0.5 + // the status item of the cpu utilization static var sItemCpuUtil = NSStatusBar.system.statusItem(withLength: 27.0) // the custom menu view of the cpu utilization let myCpuMenuView = CPUMenuView(frame: NSRect(x: 0, y: 0, width: 170, height: 90)) // the menu item for the custom view let menuItemCpu = NSMenuItem(title: "", action: nil, keyEquivalent: "") - // the buton of the cpu utilization icon + // gradient image helper + let gradientImage = GradientImage(height: usageBarMaxHeight, width: usageBarWidth) + // the button of the cpu utilization icon var btnCpuUtil: NSStatusBarButton? // the menu for the button var menuCpuUtil: NSMenu? @@ -112,17 +117,28 @@ class CpuUsageComponent { img2?.draw(at: NSPoint(x: 11, y: 0), from: NSZeroRect, operation: NSCompositingOperation.sourceOver, fraction: 1.0) } - // define the width and height of the rectangle which is going to be drawn - let rectWidth = 7 // 14*0.5 - let maxRectHeight = 16.0 // 32*0.5 - let pixelHeightCpu = Double((maxRectHeight / 100.0) * totalCpuUsage) - // create the rectangle - let pbFillRectCpu = NSRect(x: 12.0, y: 1.0, width: Double(rectWidth), height: pixelHeightCpu) - // set the fill color according to the user settings and fill the rectangle - AppDelegate.UserSettings.cpuColor.setFill() - pbFillRectCpu.fill() - // clear the fill color - NSColor.clear.setFill() + // define the height of the rectangle which is going to be drawn + let pixelHeightCpu = Double((CpuUsageComponent.usageBarMaxHeight / 100.0) * totalCpuUsage) + let barColor = AppDelegate.UserSettings.cpuColor + if AppDelegate.UserSettings.userWantsCPUGradientColor { // draw two color gradient version of the bar + // create image of a gradient using colors according to the user settings + let gradImg = gradientImage.create(fromColor: barColor, toColor: AppDelegate.UserSettings.cpuColor2) + // draw a part of the gradient image with height capped by `pixelHeightCpu` + gradImg.draw( + at: NSPoint(x: 12.0, y: 1.0), + from: NSRect(x: 0.0, y: 0.0, width: CpuUsageComponent.usageBarWidth, height: pixelHeightCpu), + operation: NSCompositingOperation.sourceOver, + fraction: 1.0 + ) + } else { // draw simple monocolored version of the bar + let pbFillRectCpu = NSRect(x: 12.0, y: 1.0, width: CpuUsageComponent.usageBarWidth, height: pixelHeightCpu) + // set the fill color according to the user settings and fill the rectangle + barColor.setFill() + pbFillRectCpu.fill() + // clear the fill color + NSColor.clear.setFill() + } + imgFinal.unlockFocus() btnCpuUtil?.image = imgFinal diff --git a/iGlance/iGlance/Components/MemUsageComponent.swift b/iGlance/iGlance/Components/MemUsageComponent.swift index 80298c4..1553f9d 100644 --- a/iGlance/iGlance/Components/MemUsageComponent.swift +++ b/iGlance/iGlance/Components/MemUsageComponent.swift @@ -27,12 +27,17 @@ import Cocoa class MemUsageComponent { + static let usageBarMaxHeight: Double = 16.0 + static let usageBarWidth: Double = 7.0 + // the status item of the memory usage static let sItemMemUsage = NSStatusBar.system.statusItem(withLength: 27.0) // the custom view of the memory usage let myMemMenuView = MemMenuView(frame: NSRect(x: 0, y: 0, width: 170, height: 110)) // the menu item for the custom menu view let menuItemMem = NSMenuItem(title: "", action: nil, keyEquivalent: "") + // gradient image helper + let gradientImage = GradientImage(height: usageBarMaxHeight, width: usageBarWidth) // the button of the menu bar icon var btnMemUsage: NSStatusBarButton? // the menu of the icon @@ -41,16 +46,13 @@ class MemUsageComponent { /** * MEM Button Image variables */ - var pbFillRectMEM: NSRect? var memImg: String? /** * Shared variables */ - var pixelWidth: Double? var pbIMG: String? - var pbMax: Double? - + var menuBarGraph = MenuBarGraph() func initialize() { @@ -64,7 +66,6 @@ class MemUsageComponent { menuMemUsage?.addItem(NSMenuItem(title: "Quit iGlance", action: #selector(NSApplication.terminate(_:)), keyEquivalent: "q")) MemUsageComponent.sItemMemUsage.menu = menuMemUsage - pixelWidth = 7 btnMemUsage = MemUsageComponent.sItemMemUsage.button // adjust the length of the status item according to the visualization type @@ -110,7 +111,7 @@ class MemUsageComponent { private func drawUsageBar(totalMemUsage: Double) { - let normalizedMemUsage = Double(totalMemUsage / 100) * 16 + let normalizedMemUsage = Double(totalMemUsage / 100) * MemUsageComponent.usageBarMaxHeight let imgFinal = NSImage(size: NSSize(width: 20, height: 18)) imgFinal.lockFocus() @@ -120,10 +121,24 @@ class MemUsageComponent { let img2 = NSImage(named: NSImage.Name(pbIMG!)) img2?.draw(at: NSPoint(x: 11, y: 0), from: NSZeroRect, operation: NSCompositingOperation.sourceOver, fraction: 1.0) } - pbFillRectMEM = NSRect(x: 12.0, y: 1.0, width: pixelWidth!, height: normalizedMemUsage) - AppDelegate.UserSettings.memColor.setFill() - pbFillRectMEM?.fill() - NSColor.clear.setFill() + let barColor = AppDelegate.UserSettings.memColor + if AppDelegate.UserSettings.userWantsMemGradientColor { // draw two color gradient version of the bar + // create image of a gradient using colors according to the user settings + let gradImg = gradientImage.create(fromColor: barColor, toColor: AppDelegate.UserSettings.memColor2) + // draw a part of the gradient image with height capped by `normalizedMemUsage` + gradImg.draw( + at: NSPoint(x: 12.0, y: 1.0), + from: NSRect(x: 0.0, y: 0.0, width: MemUsageComponent.usageBarWidth, height: normalizedMemUsage), + operation: NSCompositingOperation.sourceOver, + fraction: 1.0 + ) + } else { // draw simple monocolored version of the bar + let pbFillRectMEM = NSRect(x: 12.0, y: 1.0, width: MemUsageComponent.usageBarWidth, height: normalizedMemUsage) + barColor.setFill() + pbFillRectMEM.fill() + NSColor.clear.setFill() + } + imgFinal.unlockFocus() btnMemUsage?.image = imgFinal diff --git a/iGlance/iGlance/SettingsWindowViews/CpuView.swift b/iGlance/iGlance/SettingsWindowViews/CpuView.swift index e236d77..fe546e2 100644 --- a/iGlance/iGlance/SettingsWindowViews/CpuView.swift +++ b/iGlance/iGlance/SettingsWindowViews/CpuView.swift @@ -144,6 +144,10 @@ class CpuView: NSViewController { cpuGraphwidth.isHidden = popUpCPUGraphType.indexOfSelectedItem == 0 graphPixelLabel.isHidden = cpuGraphwidth.isHidden graphWidthLabel.isHidden = cpuGraphwidth.isHidden + // hide/show second color related controls + cbCPUEnableColorGradient.isHidden = (AppDelegate.UserSettings.cpuUsageVisualization == AppDelegate.VisualizationType.Graph) + secondColorLabel.isHidden = !shouldShowSecondColorControls() + cpCPU2.isHidden = !shouldShowSecondColorControls() } // outlets and actions of the labels and textfield to define the width of the menu bar graph @@ -175,4 +179,47 @@ class CpuView: NSViewController { } @IBOutlet weak var graphPixelLabel: NSTextField! @IBOutlet weak var graphWidthLabel: NSTextField! + + // define the outlet and the action of the color well to choose second color + @IBOutlet var cpCPU2: NSColorWell! { + didSet { + cpCPU2.color = AppDelegate.UserSettings.cpuColor2 + cpCPU2.isHidden = !shouldShowSecondColorControls() + } + } + + @IBAction func cpCPU2_clicked(_ sender: NSColorWell) { + AppDelegate.UserSettings.cpuColor2 = sender.color + var red: CGFloat = 0, blue: CGFloat = 0, green: CGFloat = 0, alpha: CGFloat = 0 + sender.color.usingColorSpace(NSColorSpace.genericRGB)?.getRed(&red, green: &green, blue: &blue, alpha: &alpha) + UserDefaults.standard.set(CGFloat(round(red * 10000) / 10000), forKey: "colRedCPU2") + UserDefaults.standard.set(CGFloat(round(green * 10000) / 10000), forKey: "colGreenCPU2") + UserDefaults.standard.set(CGFloat(round(blue * 10000) / 10000), forKey: "colBlueCPU2") + UserDefaults.standard.set(CGFloat(round(alpha * 10000) / 10000), forKey: "colAlphaCPU2") + } + + @IBOutlet var cbCPUEnableColorGradient: NSButton! { + didSet { + cbCPUEnableColorGradient.isHidden = (AppDelegate.UserSettings.cpuUsageVisualization == AppDelegate.VisualizationType.Graph) + cbCPUEnableColorGradient.state = AppDelegate.UserSettings.userWantsCPUGradientColor ? NSButton.StateValue.on : NSButton.StateValue.off + } + } + + @IBAction func cbCPUEnableColorGradient_clicked(_: NSButton) { + AppDelegate.UserSettings.userWantsCPUGradientColor = (cbCPUEnableColorGradient.state == NSButton.StateValue.on) + UserDefaults.standard.set((cbCPUEnableColorGradient.state == NSButton.StateValue.on), forKey: "userWantsCPUGradientColor") + secondColorLabel.isHidden = !shouldShowSecondColorControls() + cpCPU2.isHidden = !shouldShowSecondColorControls() + } + + @IBOutlet weak var secondColorLabel: NSTextField! { + didSet { + secondColorLabel.isHidden = !shouldShowSecondColorControls() + } + } + + private func shouldShowSecondColorControls() -> Bool { + return (AppDelegate.UserSettings.userWantsCPUGradientColor + && AppDelegate.UserSettings.cpuUsageVisualization == AppDelegate.VisualizationType.Bar) + } } diff --git a/iGlance/iGlance/SettingsWindowViews/MemoryView.swift b/iGlance/iGlance/SettingsWindowViews/MemoryView.swift index 1f84dd0..93050ad 100644 --- a/iGlance/iGlance/SettingsWindowViews/MemoryView.swift +++ b/iGlance/iGlance/SettingsWindowViews/MemoryView.swift @@ -105,6 +105,11 @@ class MemoryView: NSViewController { memGraphWidth.isHidden = popUpMemoryVisualization.indexOfSelectedItem == 0 memGraphPixelLabel.isHidden = memGraphWidth.isHidden memGraphWidthLabel.isHidden = memGraphWidth.isHidden + + // hide/show second color related controls + cbMemEnableColorGradient.isHidden = (AppDelegate.UserSettings.memUsageVisualization == AppDelegate.VisualizationType.Graph) + secondColorLabel.isHidden = !shouldShowSecondColorControls() + cpMem2.isHidden = !shouldShowSecondColorControls() } @IBOutlet weak var memGraphWidthLabel: NSTextField! @@ -136,4 +141,47 @@ class MemoryView: NSViewController { } } + + @IBOutlet var cbMemEnableColorGradient: NSButton! { + didSet { + cbMemEnableColorGradient.isHidden = (AppDelegate.UserSettings.memUsageVisualization == AppDelegate.VisualizationType.Graph) + cbMemEnableColorGradient.state = AppDelegate.UserSettings.userWantsMemGradientColor ? NSButton.StateValue.on : NSButton.StateValue.off + } + } + + @IBAction func cbMemEnableColorGradient_clicked(_: NSButton) { + AppDelegate.UserSettings.userWantsMemGradientColor = (cbMemEnableColorGradient.state == NSButton.StateValue.on) + UserDefaults.standard.set(AppDelegate.UserSettings.userWantsMemGradientColor, forKey: "userWantsMemGradientColor") + secondColorLabel.isHidden = !shouldShowSecondColorControls() + cpMem2.isHidden = !shouldShowSecondColorControls() + } + + @IBOutlet weak var secondColorLabel: NSTextField! { + didSet { + secondColorLabel.isHidden = !shouldShowSecondColorControls() + } + } + + // define the outlet and the action of the color well to choose second color + @IBOutlet var cpMem2: NSColorWell! { + didSet { + cpMem2.color = AppDelegate.UserSettings.memColor2 + cpMem2.isHidden = !shouldShowSecondColorControls() + } + } + + @IBAction func cpMem2_clicked(_ sender: NSColorWell) { + AppDelegate.UserSettings.memColor2 = sender.color + var red: CGFloat = 0, blue: CGFloat = 0, green: CGFloat = 0, alpha: CGFloat = 0 + sender.color.usingColorSpace(NSColorSpace.genericRGB)?.getRed(&red, green: &green, blue: &blue, alpha: &alpha) + UserDefaults.standard.set(CGFloat(round(red * 10000) / 10000), forKey: "colRedMem2") + UserDefaults.standard.set(CGFloat(round(green * 10000) / 10000), forKey: "colGreenMem2") + UserDefaults.standard.set(CGFloat(round(blue * 10000) / 10000), forKey: "colBlueMem2") + UserDefaults.standard.set(CGFloat(round(alpha * 10000) / 10000), forKey: "colAlphaMem2") + } + + private func shouldShowSecondColorControls() -> Bool { + return (AppDelegate.UserSettings.userWantsMemGradientColor + && AppDelegate.UserSettings.memUsageVisualization == AppDelegate.VisualizationType.Bar) + } } diff --git a/iGlance/iGlance/Utils/GradientImage.swift b/iGlance/iGlance/Utils/GradientImage.swift new file mode 100644 index 0000000..229db56 --- /dev/null +++ b/iGlance/iGlance/Utils/GradientImage.swift @@ -0,0 +1,60 @@ +// +// GradientImage.swift +// iGlance +// +// MIT License +// +// 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. + +import Cocoa + +final class GradientImage { + + private struct Cache { + let fromColor: NSColor + let toColor: NSColor + let img: NSImage + } + + private let height: Double + private let width: Double + + private var cache: Cache? + + init(height: Double, width: Double) { + self.height = height + self.width = width + } + + func create(fromColor: NSColor, toColor: NSColor) -> NSImage { + if cache?.fromColor == fromColor && cache?.toColor == toColor { + // return cached image if from/to colors didn't change + return cache!.img + } + let img = NSImage(size: NSSize(width: width, height: height)) + let rect = NSRect(x: 0, y: 0, width: width, height: height) + img.lockFocus() + let gradient = NSGradient(starting: fromColor, ending: toColor) + gradient?.draw(in: rect, angle: CGFloat(90)) + img.unlockFocus() + cache = Cache(fromColor: fromColor, toColor: toColor, img: img) + return img + } + +} \ No newline at end of file