Files
2024-07-01 17:50:51 -03:00

1399 lines
44 KiB
Swift

//
// ViewController.swift
// TGUIKit
//
// Created by keepcoder on 06/09/16.
// Copyright © 2016 Telegram. All rights reserved.
//
import Foundation
import SwiftSignalKit
import AppKit
import ColorPalette
public final class BackgroundGradientView : View {
public var values:(top: NSColor?, bottom: NSColor?, rotation: Int32?)? {
didSet {
needsDisplay = true
}
}
public required init(frame frameRect: NSRect) {
super.init(frame: frameRect)
noWayToRemoveFromSuperview = true
}
required public init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override public var isFlipped: Bool {
return false
}
override public func layout() {
super.layout()
let values = self.values
self.values = values
}
override public func draw(_ layer: CALayer, in ctx: CGContext) {
super.draw(layer, in: ctx)
if let values = self.values {
let colors = Array([values.top, values.bottom].compactMap { $0?.cgColor }.reversed())
guard !colors.isEmpty else {
return
}
let gradientColors = colors as CFArray
let delta: CGFloat = 1.0 / (CGFloat(colors.count) - 1.0)
var locations: [CGFloat] = []
for i in 0 ..< colors.count {
locations.append(delta * CGFloat(i))
}
let colorSpace = CGColorSpaceCreateDeviceRGB()
let gradient = CGGradient(colorsSpace: colorSpace, colors: gradientColors, locations: &locations)!
ctx.saveGState()
ctx.translateBy(x: frame.width / 2.0, y: frame.height / 2.0)
ctx.rotate(by: CGFloat(values.rotation ?? 0) * CGFloat.pi / -180.0)
ctx.translateBy(x: -frame.width / 2.0, y: -frame.height / 2.0)
ctx.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: 0.0, y: frame.height), options: [.drawsBeforeStartLocation, .drawsAfterEndLocation])
ctx.restoreGState()
}
}
}
open class BackgroundView: View {
private final class TileControl {
private var superlayer: CALayer?
private let main: CALayer
private var tile: NSImage? = nil
private var shouldTile: Bool = false
private var tileLayers:[CALayer] = []
init(main: CALayer) {
self.main = main
tileLayers.append(main)
}
func set(superlayer: CALayer?, tile: NSImage?, shouldTile: Bool) {
self.superlayer = superlayer
self.shouldTile = shouldTile
self.tile = tile
}
var validLayout: CGRect? = nil
func update(frame: NSRect, transition: ContainedViewLayoutTransition) {
var rects:[CGRect] = []
var frame = frame
frame.size.height = max(500, frame.height)
if let superlayer = superlayer {
if let tile = tile {
let size = tile.size
let tileSize = size.aspectFitted(frame.size)
if shouldTile {
if tileSize.width < frame.width {
while rects.reduce(CGFloat(0), { $0 + $1.width }) < frame.width {
let width = rects.reduce(CGFloat(0), { $0 + $1.width })
rects.append(CGRect(origin: .init(x: width, y: (frame.height - tileSize.height) / 2), size: tileSize))
}
} else {
rects.append(frame.focus(size.aspectFilled(frame.size)))
}
} else {
rects.append(frame.focus(size.aspectFilled(frame.size)))
}
while self.tileLayers.count > rects.count {
self.tileLayers.removeLast().removeFromSuperlayer()
}
while self.tileLayers.count < rects.count {
let layer = SimpleLayer()
// layer.disableActions()
self.tileLayers.append(layer)
}
for (i, layer) in tileLayers.enumerated() {
layer.compositingFilter = main.compositingFilter
layer.backgroundColor = main.backgroundColor
layer.contents = main.contents
layer.opacity = main.opacity
// layer.contentsGravity = .resize
transition.updateFrame(layer: layer, frame: rects[i])
superlayer.addSublayer(layer)
}
} else {
while tileLayers.count > 1 {
tileLayers.removeLast().removeFromSuperlayer()
}
superlayer.addSublayer(main)
transition.updateFrame(layer: main, frame: frame)
}
} else {
while tileLayers.count > 1 {
tileLayers.removeLast().removeFromSuperlayer()
}
main.removeFromSuperlayer()
}
}
}
deinit {
}
private let imageView: SimpleLayer = SimpleLayer()
private var backgroundView: NSView?
public var useSharedAnimationPhase: Bool = true
public var checkDarkPattern: Bool = true
private let container: View = View()
private var tileControl: TileControl
public required init(frame frameRect: NSRect) {
tileControl = TileControl(main: imageView)
super.init(frame: frameRect)
// imageView.disableActions()
imageView.frame = frameRect.size.bounds
// container.addSubview(imageView)
autoresizesSubviews = false
imageView.contentsGravity = .resize
self.addSubview(container)
}
var contentViews: [NSView] {
return self.subviews
}
open override func change(size: NSSize, animated: Bool, _ save: Bool = true, removeOnCompletion: Bool = true, duration: Double = 0.2, timingFunction: CAMediaTimingFunctionName = CAMediaTimingFunctionName.easeOut, completion: ((Bool) -> Void)? = nil) {
super.change(size: size, animated: animated, save, removeOnCompletion: removeOnCompletion, duration: duration, timingFunction: timingFunction, completion: completion)
}
override init() {
fatalError("not supported")
}
open override func layout() {
super.layout()
_customHandler?.layout?(self)
self.updateLayout(size: frame.size, transition: .immediate)
}
public func doAction() {
if let backgroundView = backgroundView as? AnimatedGradientBackgroundView {
backgroundView.animateEvent(transition: .animated(duration: 0.5, curve: .spring))
}
}
open override func viewDidChangeBackingProperties() {
super.viewDidChangeBackingProperties()
}
open override var isFlipped: Bool {
return true
}
required public init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
open var backgroundMode:TableBackgroundMode = .plain {
didSet {
if oldValue != backgroundMode {
// CATransaction.begin()
// CATransaction.setDisableActions(true)
tileControl.validLayout = nil
var backgroundView: NSView? = nil
switch backgroundMode {
case let .background(image, intensity, colors, rotation):
imageView.backgroundColor = .clear
imageView.contents = image
let colors = colors?.map { $0.withAlphaComponent(1) }
var shouldTile = false
if let colors = colors, !colors.isEmpty {
shouldTile = true
let intense = Float((abs(intensity ?? 50))) / 100.0
let invertPattern = presentation.colors.isDark && checkDarkPattern
if invertPattern {
self.imageView.compositingFilter = nil
imageView.opacity = 1.0
} else {
self.imageView.compositingFilter = "softLightBlendMode"
imageView.opacity = intense
}
if colors.count > 2 {
if let bg = self.backgroundView as? AnimatedGradientBackgroundView {
backgroundView = bg
bg.updateColors(colors: colors)
} else {
backgroundView = AnimatedGradientBackgroundView(colors: colors, useSharedAnimationPhase: useSharedAnimationPhase)
backgroundView?.frame = bounds
}
} else {
if let bg = self.backgroundView as? BackgroundGradientView {
backgroundView = bg
} else {
let bg = BackgroundGradientView(frame: bounds)
backgroundView = bg
}
(backgroundView as? BackgroundGradientView)?.values = (top: colors.first, bottom: colors.last, rotation: rotation)
}
} else {
imageView.opacity = 1
imageView.compositingFilter = nil
}
if let bg = backgroundView as? AnimatedGradientBackgroundView {
tileControl.set(superlayer: bg.contentView.layer, tile: image, shouldTile: shouldTile)
} else if let bg = backgroundView as? BackgroundGradientView {
tileControl.set(superlayer: bg.layer, tile: image, shouldTile: shouldTile)
} else {
tileControl.set(superlayer: container.layer, tile: image, shouldTile: shouldTile)
}
case let .color(color):
imageView.backgroundColor = color.withAlphaComponent(1.0).cgColor
imageView.compositingFilter = nil
imageView.contents = nil
imageView.opacity = 1
tileControl.set(superlayer: container.layer!, tile: nil, shouldTile: false)
case let .gradient(colors, rotation):
let colors = colors.map { $0.withAlphaComponent(1) }
imageView.contents = nil
imageView.backgroundColor = .clear
imageView.opacity = 1
imageView.compositingFilter = nil
imageView.removeFromSuperlayer()
tileControl.set(superlayer: nil, tile: nil, shouldTile: false)
if colors.count > 2 {
if let bg = self.backgroundView as? AnimatedGradientBackgroundView {
backgroundView = bg
bg.updateColors(colors: colors)
} else {
backgroundView = AnimatedGradientBackgroundView(colors: colors, useSharedAnimationPhase: useSharedAnimationPhase)
backgroundView?.frame = bounds
}
} else {
if let bg = self.backgroundView as? BackgroundGradientView {
backgroundView = bg
} else {
let bg = BackgroundGradientView(frame: bounds)
backgroundView = bg
}
(backgroundView as? BackgroundGradientView)?.values = (top: colors.first, bottom: colors.last, rotation: rotation)
}
default:
imageView.backgroundColor = presentation.colors.background.cgColor
imageView.contents = nil
imageView.compositingFilter = nil
imageView.opacity = 1
imageView.removeFromSuperlayer()
tileControl.set(superlayer: nil, tile: nil, shouldTile: false)
}
tileControl.update(frame: bounds, transition: .immediate)
if let backgroundView = backgroundView {
self.backgroundView?.removeFromSuperview()
self.backgroundView = backgroundView
container.addSubview(backgroundView)
} else {
self.backgroundView?.removeFromSuperview()
self.backgroundView = nil
}
}
// CATransaction.commit()
}
}
open func updateLayout(size: NSSize, transition: ContainedViewLayoutTransition) {
transition.updateFrame(layer: imageView, frame: size.bounds)
transition.updateFrame(view: container, frame: size.bounds)
tileControl.update(frame: size.bounds, transition: transition)
if let backgroundView = backgroundView {
transition.updateFrame(view: backgroundView, frame: size.bounds)
if let backgroundView = backgroundView as? AnimatedGradientBackgroundView {
backgroundView.updateLayout(size: size, transition: transition)
}
}
}
private(set) var isCopy: Bool = false
override open func copy() -> Any {
let view = BackgroundView(frame: self.frame)
view.isCopy = true
view.backgroundMode = self.backgroundMode
view.useSharedAnimationPhase = true
view.updateLayout(size: view.frame.size, transition: .immediate)
return view
}
open func copy(_ backgroundMode: TableBackgroundMode?) -> BackgroundView {
let view = BackgroundView(frame: self.frame)
view.isCopy = true
view.backgroundMode = backgroundMode ?? self.backgroundMode
view.useSharedAnimationPhase = true
view.updateLayout(size: view.frame.size, transition: .immediate)
return view
}
}
class ControllerToasterView : Control {
private weak var toaster:ControllerToaster?
private let textView:TextView = TextView()
required init(frame frameRect: NSRect) {
super.init(frame: frameRect)
addSubview(textView)
textView.isSelectable = false
textView.userInteractionEnabled = false
self.autoresizingMask = [.width]
self.border = [.Bottom]
updateLocalizationAndTheme(theme: presentation)
}
override func updateLocalizationAndTheme(theme: PresentationTheme) {
super.updateLocalizationAndTheme(theme: theme)
self.backgroundColor = theme.colors.background
self.textView.backgroundColor = theme.colors.background
}
func update(with toaster:ControllerToaster) {
self.toaster = toaster
}
override func layout() {
super.layout()
if let toaster = toaster {
toaster.text.measure(width: frame.width - 40)
textView.update(toaster.text)
textView.center()
}
self.setNeedsDisplayLayer()
}
required public init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
public class ControllerToaster {
let text:TextViewLayout
var view:ControllerToasterView?
let disposable:MetaDisposable = MetaDisposable()
private let action:(()->Void)?
private var height:CGFloat {
return max(30, self.text.layoutSize.height + 10)
}
public init(text:NSAttributedString, action:(()->Void)? = nil) {
self.action = action
self.text = TextViewLayout(text, maximumNumberOfLines: 3, truncationType: .middle, alignment: .center)
}
public init(text:String, action:(()->Void)? = nil) {
self.action = action
self.text = TextViewLayout(NSAttributedString.initialize(string: text, color: presentation.colors.text, font: .medium(.text)), maximumNumberOfLines: 3, truncationType: .middle, alignment: .center)
}
func show(for controller:ViewController, timeout:Double, animated:Bool) {
assert(view == nil)
text.measure(width: controller.frame.width - 40)
view = ControllerToasterView(frame: NSMakeRect(0, 0, controller.frame.width, height))
view?.update(with: self)
if let action = self.action {
view?.set(handler: { [weak self] _ in
action()
self?.hide(true)
}, for: .Click)
}
controller.addSubview(view!)
if animated {
view?.layer?.animatePosition(from: NSMakePoint(0, -height - controller.bar.height), to: NSZeroPoint, duration: 0.2)
}
let signal:Signal<Void,Void> = .single(Void()) |> delay(timeout, queue: Queue.mainQueue())
disposable.set(signal.start(next:{ [weak self] in
self?.hide(true)
}))
}
func hide(_ animated:Bool) {
if animated {
view?.layer?.animatePosition(from: NSZeroPoint, to: NSMakePoint(0, -height), duration: 0.2, removeOnCompletion:false, completion:{ [weak self] (completed) in
self?.view?.removeFromSuperview()
self?.view = nil
})
} else {
view?.removeFromSuperview()
view = nil
disposable.dispose()
}
}
deinit {
let view = self.view
view?.removeFromSuperview()
disposable.dispose()
}
}
open class ViewController : NSObject {
public struct StakeSettings {
public let keepLeft: CGFloat
public let straightMove: Bool
public let keepIn: Bool
public let keepTop: ()->CGFloat
public var isCustom: Bool {
if keepLeft > 0 {
return true
}
if straightMove {
return true
}
if keepIn {
return true
}
if keepTop() > 0 {
return true
}
return false
}
public init(keepLeft: CGFloat, keepTop: @escaping()->CGFloat, straightMove: Bool, keepIn: Bool) {
self.keepLeft = keepLeft
self.straightMove = straightMove
self.keepIn = keepIn
self.keepTop = keepTop
}
public static var `default`: StakeSettings {
return .init(keepLeft: 0, keepTop: { 0 }, straightMove: false, keepIn: false)
}
}
fileprivate var _view:NSView?
public var _frameRect:NSRect
private var toaster:ControllerToaster?
public var atomicSize:Atomic<NSSize> = Atomic(value:NSZeroSize)
public var onDeinit: (()->Void)? = nil
weak open var navigationController:NavigationViewController? {
didSet {
if navigationController != oldValue {
updateNavigation(navigationController)
}
}
}
open func swapNavigationBar(leftView: BarView?, centerView: BarView?, rightView: BarView?, animation: NavigationBarSwapAnimation) {
self.navigationController?.swapNavigationBar(leftView: leftView, centerView: centerView, rightView: rightView, animation: animation)
}
public var noticeResizeWhenLoaded: Bool = true
public var animationStyle:AnimationStyle = AnimationStyle(duration: 0.4, function:CAMediaTimingFunctionName.spring)
public var bar:NavigationBarStyle = NavigationBarStyle(height:50)
public var leftBarView:BarView!
public var centerBarView:TitledBarView!
public var rightBarView:BarView!
public var popover:Popover?
open var modal:Modal?
open var barHeight: CGFloat {
return bar.height
}
open var barPresentation: ControlStyle {
return navigationButtonStyle
}
var widthOnDisappear: CGFloat? = nil
public var ableToNextController:(ViewController, @escaping(ViewController, Bool)->Void)->Void = { controller, f in
f(controller, true)
}
private let _ready = Promise<Bool>()
open var ready: Promise<Bool> {
return self._ready
}
public var didSetReady:Bool = false
public let isKeyWindow:Promise<Bool> = Promise(false)
open var view:NSView {
get {
if(_view == nil) {
loadView();
}
return _view!;
}
}
open var redirectUserInterfaceCalls: Bool {
return false
}
public var backgroundColor: NSColor {
set {
self.view.background = newValue
}
get {
return self.view.background
}
}
open var enableBack:Bool {
return false
}
open var isAutoclosePopover: Bool {
return true
}
open var isOnScreen: Bool {
return self.navigationController?.controller == self && !hasModals()
}
open func executeReturn() -> Void {
self.navigationController?.back()
}
open func updateNavigation(_ navigation:NavigationViewController?) {
}
open var rightSwipeController: ViewController? {
return nil
}
open func navigationWillChangeController() {
}
open var sidebar:ViewController? {
return nil
}
open var sidebarWidth:CGFloat {
return 350
}
open var supportSwipes: Bool {
return true
}
public let internalId:Int = Int(arc4random());
public override init() {
_frameRect = NSZeroRect
super.init()
}
public init(frame frameRect:NSRect) {
_frameRect = frameRect;
}
open func readyOnce() -> Void {
if !didSetReady {
didSetReady = true
ready.set(.single(true))
}
}
open func updateLocalizationAndTheme(theme: PresentationTheme) {
(view as? AppearanceViewProtocol)?.updateLocalizationAndTheme(theme: theme)
// self.navigationController?.updateLocalizationAndTheme(theme: theme)
}
open func loadView() -> Void {
if(_view == nil) {
leftBarView = getLeftBarViewOnce()
centerBarView = getCenterBarViewOnce()
rightBarView = getRightBarViewOnce()
let vz = viewClass() as! NSView.Type
_view = vz.init(frame: _frameRect);
_view?.autoresizingMask = [.width,.height]
NotificationCenter.default.addObserver(self, selector: #selector(viewFrameChanged(_:)), name: NSView.frameDidChangeNotification, object: _view!)
_ = atomicSize.swap(_view!.frame.size)
viewDidLoad()
}
}
open func navigationHeaderDidNoticeAnimation(_ current: CGFloat, _ previous: CGFloat, _ animated: Bool) -> ()->Void {
return {}
}
open func requestUpdateBackBar() {
if isLoaded(), let leftBarView = leftBarView as? BackNavigationBar {
leftBarView.requestUpdate()
}
self.leftBarView.style = barPresentation
}
open func requestUpdateCenterBar() {
setCenterTitle(defaultBarTitle)
setCenterStatus(defaultBarStatus)
self.centerBarView.style = barPresentation
}
open func requestUpdateRightBar() {
self.rightBarView.style = barPresentation
}
open func dismiss() {
if navigationController?.controller == self {
navigationController?.back()
}
}
@objc func viewFrameChanged(_ notification:Notification) {
if atomicSize.with({ $0 != frame.size}) {
viewDidResized(frame.size)
}
}
public private(set) var bgMode: TableBackgroundMode?
open func updateBackgroundColor(_ backgroundMode: TableBackgroundMode) {
self.bgMode = backgroundMode
switch backgroundMode {
case .background, .gradient:
backgroundColor = .clear
case let .color(color):
backgroundColor = color
default:
backgroundColor = presentation.colors.background
}
}
open func updateController() {
}
open func viewDidResized(_ size:NSSize) {
_ = atomicSize.swap(size)
}
open func draggingExited() {
}
open func draggingEntered() {
}
open func focusSearch(animated: Bool, text: String? = nil) {
}
open func invokeNavigationBack() -> Bool {
return true
}
open func updateFrame(_ frame: NSRect, transition: ContainedViewLayoutTransition) {
if isLoaded() {
transition.updateFrame(view: self.view, frame: frame)
}
}
open func getLeftBarViewOnce() -> BarView {
return enableBack ? BackNavigationBar(self) : BarView(controller: self)
}
open var defaultBarTitle:String {
return localizedString(self.className)
}
open var defaultBarStatus:String? {
return nil
}
open func getCenterBarViewOnce() -> TitledBarView {
return TitledBarView(controller: self, .initialize(string: defaultBarTitle, color: barPresentation.textColor, font: .medium(.title)))
}
open func setCenterTitle(_ text:String) {
self.centerBarView.text = .initialize(string: text, color: barPresentation.textColor, font: .medium(.title))
}
open func setCenterStatus(_ text: String?) {
if let text = text {
self.centerBarView.status = .initialize(string: text, color: barPresentation.grayTextColor, font: .normal(.text))
} else {
self.centerBarView.status = nil
}
}
open func getRightBarViewOnce() -> BarView {
return BarView(controller: self)
}
open var abolishWhenNavigationSame: Bool {
return false
}
open func viewClass() ->AnyClass {
return View.self
}
open func draggingItems(for pasteboard:NSPasteboard) -> [DragItem] {
return []
}
public func loadViewIfNeeded(_ frame:NSRect = NSZeroRect) -> Void {
guard _view != nil else {
if !NSIsEmptyRect(frame) {
_frameRect = frame
}
self.loadView()
return
}
}
open func viewDidLoad() -> Void {
if noticeResizeWhenLoaded {
viewDidResized(view.frame.size)
}
}
open func viewWillAppear(_ animated:Bool) -> Void {
}
open func viewDidChangedNavigationLayout(_ state: SplitViewState) -> Void {
}
open func setToNextController(_ controller: ViewController, style: ViewControllerStyle) {
}
open func setToPreviousController(_ controller: ViewController, style: ViewControllerStyle) {
}
open var stake: StakeSettings {
return .default
}
open weak var tied: ViewController?
open func updateSwipingState(_ state: SwipeState, controller: ViewController, isPrevious: Bool) -> Void {
}
deinit {
self.window?.removeObserver(for: self)
self.window?.removeAllHandlers(for: self)
NotificationCenter.default.removeObserver(self)
assertOnMainThread()
self.onDeinit?()
}
open func viewWillDisappear(_ animated:Bool) -> Void {
if #available(OSX 10.12.2, *) {
window?.touchBar = nil
}
widthOnDisappear = frame.width
//assert(self.window != nil)
if canBecomeResponder {
self.window?.removeObserver(for: self)
}
if hasNextResponder {
self.window?.remove(object: self, for: .Tab)
}
NotificationCenter.default.removeObserver(self, name: NSWindow.didBecomeKeyNotification, object: window)
NotificationCenter.default.removeObserver(self, name: NSWindow.didResignKeyNotification, object: window)
isKeyWindow.set(.single(false))
}
public func isLoaded() -> Bool {
return _view != nil
}
open func viewDidAppear(_ animated:Bool) -> Void {
//assert(self.window != nil)
if #available(OSX 10.12.2, *) {
// DispatchQueue.main.async { [weak self] in
self.window?.touchBar = self.window?.makeTouchBar()
// }
}
if hasNextResponder {
self.window?.set(handler: { [weak self] _ -> KeyHandlerResult in
guard let `self` = self else {return .rejected}
_ = self.window?.makeFirstResponder(self.nextResponder())
return .invoked
}, with: self, for: .Tab, priority: responderPriority)
}
if canBecomeResponder {
self.window?.set(responder: {[weak self] () -> NSResponder? in
return self?.firstResponder()
}, with: self, priority: responderPriority)
if let become = becomeFirstResponder(), become == true {
self.window?.applyResponderIfNeeded()
} else {
_ = self.window?.makeFirstResponder(self.window?.firstResponder)
}
}
NotificationCenter.default.addObserver(self, selector: #selector(windowDidBecomeKey), name: NSWindow.didBecomeKeyNotification, object: window)
NotificationCenter.default.addObserver(self, selector: #selector(windowDidResignKey), name: NSWindow.didResignKeyNotification, object: window)
if let window = window {
isKeyWindow.set(.single(window.isKeyWindow))
}
func findTableView(in view: NSView) -> Void {
for subview in view.subviews {
if subview is NSTableView {
if !subview.inLiveResize {
subview.viewDidEndLiveResize()
}
} else if !subview.subviews.isEmpty {
findTableView(in: subview)
}
}
}
if let widthOnDisappear = widthOnDisappear, frame.width != widthOnDisappear {
findTableView(in: view)
}
}
@objc open func windowDidBecomeKey() {
isKeyWindow.set(.single(true))
}
@objc open func windowDidResignKey() {
isKeyWindow.set(.single(false))
}
open var canBecomeResponder: Bool {
return true
}
open var removeAfterDisapper:Bool {
return false
}
open func escapeKeyAction() -> KeyHandlerResult {
return .rejected
}
open func backKeyAction() -> KeyHandlerResult {
if let event = NSApp.currentEvent, event.modifierFlags.contains(.shift), let textView = window?.firstResponder as? TextView, let layout = textView.textLayout, layout.selectedRange.range.max != 0 {
_ = layout.selectPrevChar()
textView.needsDisplay = true
return .invoked
}
return .rejected
}
open func nextKeyAction() -> KeyHandlerResult {
if let event = NSApp.currentEvent, event.modifierFlags.contains(.shift), let textView = window?.firstResponder as? TextView, let layout = textView.textLayout, layout.selectedRange.range.max != 0 {
_ = layout.selectNextChar()
textView.needsDisplay = true
return .invoked
}
return .invokeNext
}
open func returnKeyAction() -> KeyHandlerResult {
return .rejected
}
open func didRemovedFromStack() -> Void {
}
open func viewDidDisappear(_ animated:Bool) -> Void {
}
open func scrollup(force: Bool = false) {
}
open func becomeFirstResponder() -> Bool? {
return false
}
open var window:Window? {
return _view?.window as? Window
}
open func firstResponder() -> NSResponder? {
return nil
}
open func nextResponder() -> NSResponder? {
return nil
}
open var hasNextResponder: Bool {
return false
}
open var responderPriority:HandlerPriority {
return .low
}
public var frame:NSRect {
get {
return isLoaded() ? self.view.frame : _frameRect
}
set {
self.view.frame = newValue
}
}
public var bounds:NSRect {
return isLoaded() ? self.view.bounds : NSMakeRect(0, 0, _frameRect.width, _frameRect.height - bar.height)
}
open var isOpaque: Bool {
return true
}
func removeBackgroundCap() {
for subview in view.subviews.reversed() {
if let subview = subview as? BackgroundView, subview.isCopy {
subview.removeFromSuperview()
}
}
}
public func addSubview(_ subview:NSView) -> Void {
self.view.addSubview(subview)
}
public func removeFromSuperview() ->Void {
if isLoaded() {
self.view.removeFromSuperview()
}
}
open func backSettings() -> (String,CGImage?) {
return (localizedString("Navigation.back"),#imageLiteral(resourceName: "Icon_NavigationBack").precomposed(barPresentation.foregroundColor))
}
open var popoverClass:AnyClass {
return Popover.self
}
open func show(for control:Control, edge:NSRectEdge? = nil, inset:NSPoint = NSZeroPoint, static: Bool = false) -> Void {
if popover == nil {
self.popover = (self.popoverClass as! Popover.Type).init(controller: self, static: `static`, animationMode: .classic)
}
if let popover = popover {
popover.show(for: control, edge: edge, inset: inset)
}
}
open func closePopover() -> Void {
self.popover?.hide()
}
open func invokeNavigation(action:NavigationModalAction) {
_ = (self.ready.get() |> take(1) |> deliverOnMainQueue).start(next: { (ready) in
action.close()
})
}
public func removeToaster() {
if let toaster = self.toaster {
toaster.hide(true)
}
}
public func show(toaster:ControllerToaster, for delay:Double = 3.0, animated:Bool = true) {
assert(isLoaded())
if let toaster = self.toaster {
toaster.hide(true)
}
self.toaster = toaster
toaster.show(for: self, timeout: delay, animated: animated)
}
}
open class GenericViewController<T> : ViewController where T:NSView {
public var genericView:T {
return super.view as! T
}
open override func updateLocalizationAndTheme(theme: PresentationTheme) {
super.updateLocalizationAndTheme(theme: theme)
genericView.background = presentation.colors.background
}
deinit {
var bp:Int = 0
bp += 1
}
override open func loadView() -> Void {
if(_view == nil) {
leftBarView = getLeftBarViewOnce()
centerBarView = getCenterBarViewOnce()
rightBarView = getRightBarViewOnce()
_view = initializer()
_view?.wantsLayer = true
_view?.autoresizingMask = [.width,.height]
NotificationCenter.default.addObserver(self, selector: #selector(viewFrameChanged(_:)), name: NSView.frameDidChangeNotification, object: _view!)
_ = atomicSize.swap(_view!.frame.size)
}
viewDidLoad()
}
public var initializationRect: NSRect {
return NSMakeRect(_frameRect.minX, _frameRect.minY, _frameRect.width - self.stake.keepLeft, _frameRect.height - bar.height)
}
open func initializer() -> T {
let vz = T.self as NSView.Type
//controller.bar.height
return vz.init(frame: initializationRect) as! T
}
}
public struct ModalHeaderData {
public let title: String?
public let subtitle: String?
public let image: CGImage?
public let handler: (()-> Void)?
public let contextMenu:(()->[ContextMenuItem])?
public init(title: String? = nil, subtitle: String? = nil, image: CGImage? = nil, handler: (()->Void)? = nil, contextMenu:(()->[ContextMenuItem])? = nil) {
self.title = title
self.image = image
self.subtitle = subtitle
self.handler = handler
self.contextMenu = contextMenu
}
}
public protocol ModalControllerHelper {
var modalInteractions:ModalInteractions? { get }
}
open class ModalViewController : ViewController, ModalControllerHelper {
public struct Theme {
let text: NSColor
let grayText: NSColor
let background: NSColor
let border: NSColor
let accent: NSColor
let grayForeground: NSColor
let activeBackground: NSColor
let activeBorder: NSColor
let listBackground: NSColor
public init(text: NSColor = presentation.colors.text, grayText: NSColor = presentation.colors.grayText, background: NSColor = .clear, border: NSColor = presentation.colors.border, accent: NSColor = presentation.colors.accent, grayForeground: NSColor = presentation.colors.grayForeground, activeBackground: NSColor = presentation.colors.background, activeBorder: NSColor = presentation.colors.border, listBackground: NSColor = presentation.colors.listBackground) {
self.text = text
self.grayText = grayText
self.background = background
self.border = border
self.accent = accent
self.grayForeground = grayForeground
self.activeBackground = activeBackground
self.activeBorder = activeBorder
self.listBackground = listBackground
}
public init(presentation: PresentationTheme) {
self.text = presentation.colors.text
self.grayText = presentation.colors.grayText
self.background = presentation.colors.background
self.border = presentation.colors.border
self.accent = presentation.colors.accent
self.grayForeground = presentation.colors.grayForeground
self.activeBackground = presentation.colors.background
self.activeBorder = presentation.colors.border
self.listBackground = presentation.colors.listBackground
}
}
open var modalTheme:Theme {
return Theme()
}
open var hasBorder: Bool {
return true
}
open var closable:Bool {
return true
}
// use this only for modal progress. This is made specially for nsvisualeffect support.
open var contentBelowBackground: Bool {
return false
}
open var shouldCloseAllTheSameModals: Bool {
return true
}
open var hasOwnTouchbar: Bool {
return true
}
open var background:NSColor {
return NSColor(0x000000, 0.6)
}
open func didResizeView(_ size: NSSize, animated: Bool) -> Void {
}
open var isVisualEffectBackground: Bool {
return false
}
open var isVisualEffectContainer: Bool {
return false
}
open var isFullScreen:Bool {
return false
}
open var cornerRadius: CGFloat {
return 10
}
open var redirectMouseAfterClosing: Bool {
return false
}
open var containerBackground: NSColor {
return presentation.colors.background
}
open var headerBackground: NSColor {
return presentation.colors.background
}
open var headerBorderColor: NSColor {
return presentation.colors.border
}
open var dynamicSize:Bool {
return false
}
open override func becomeFirstResponder() -> Bool? {
return true
}
open func measure(size:NSSize) {
}
open var modalInteractions:ModalInteractions? {
return nil
}
open var modalHeader: (left:ModalHeaderData?, center: ModalHeaderData?, right: ModalHeaderData?)? {
return nil
}
open override var responderPriority: HandlerPriority {
return .modal
}
open override func firstResponder() -> NSResponder? {
return self.view
}
open func close(animationType: ModalAnimationCloseBehaviour = .common) {
modal?.close(animationType: animationType)
}
open var handleEvents:Bool {
return true
}
open var handleAllEvents: Bool {
return true
}
override open func loadView() -> Void {
if(_view == nil) {
_view = initializer()
_view?.autoresizingMask = [.width,.height]
NotificationCenter.default.addObserver(self, selector: #selector(viewFrameChanged(_:)), name: NSView.frameDidChangeNotification, object: _view!)
_ = atomicSize.swap(_view!.frame.size)
}
viewDidLoad()
}
open func initializer() -> NSView {
let vz = viewClass() as! NSView.Type
return vz.init(frame: NSMakeRect(_frameRect.minX, _frameRect.minY, _frameRect.width, _frameRect.height - bar.height));
}
}
open class ModalController : ModalViewController {
public let controller: NavigationViewController
public init(_ controller: NavigationViewController) {
self.controller = controller
super.init(frame: controller._frameRect)
}
open override var handleEvents: Bool {
return true
}
open override var modalInteractions: ModalInteractions? {
return (self.controller.controller as? ModalControllerHelper)?.modalInteractions
}
open override func viewWillAppear(_ animated: Bool) {
self.controller.viewWillAppear(animated)
}
open override func viewWillDisappear(_ animated: Bool) {
self.controller.viewWillDisappear(animated)
}
open override func viewDidAppear(_ animated: Bool) {
self.controller.viewDidAppear(animated)
}
open override func viewDidDisappear(_ animated: Bool) {
self.controller.viewDidDisappear(animated)
}
open override func firstResponder() -> NSResponder? {
return controller.controller.firstResponder()
}
open override func returnKeyAction() -> KeyHandlerResult {
return controller.controller.returnKeyAction()
}
open override func escapeKeyAction() -> KeyHandlerResult {
return controller.controller.escapeKeyAction()
}
open override var hasBorder: Bool {
return false
}
open override var hasNextResponder: Bool {
return true
}
open override func nextResponder() -> NSResponder? {
return controller.controller.nextResponder()
}
open override func viewDidLoad() {
super.viewDidLoad()
ready.set(controller.controller.ready.get())
}
open override func becomeFirstResponder() -> Bool? {
return nil
}
open override func loadView() {
self._view = controller.view
NotificationCenter.default.addObserver(self, selector: #selector(viewFrameChanged(_:)), name: NSView.frameDidChangeNotification, object: _view!)
_ = atomicSize.swap(_view!.frame.size)
viewDidLoad()
}
}
open class TableModalViewController : ModalViewController {
override open var dynamicSize: Bool {
return true
}
override open func measure(size: NSSize) {
self.modal?.resize(with:NSMakeSize(genericView.frame.width, min(size.height - 200, genericView.listHeight)), animated: false)
}
public func updateSize(_ animated: Bool) {
if let contentSize = self.modal?.window.contentView?.frame.size {
self.modal?.resize(with:NSMakeSize(genericView.frame.width, min(contentSize.height - 200, genericView.listHeight)), animated: animated)
}
}
override open func viewClass() -> AnyClass {
return TableView.self
}
public var genericView:TableView {
return self.view as! TableView
}
}