Files

486 lines
16 KiB
Swift

//
// SplitView.swift
// TGUIKit
//
// Created by keepcoder on 06/09/16.
// Copyright © 2016 Telegram. All rights reserved.
//
import Foundation
import SwiftSignalKit
import AppKit
fileprivate class SplitMinimisizeView : Control {
private var startPoint:NSPoint = NSZeroPoint
private var startDragging: NSPoint = .zero
private var acceptAllDrags: Bool = false
weak var splitView:SplitView?
override init() {
super.init()
userInteractionEnabled = false
}
required public init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
required public init(frame frameRect: NSRect) {
fatalError("init(frame:) has not been implemented")
}
fileprivate override func mouseMoved(with event: NSEvent) {
super.mouseMoved(with: event)
checkCursor()
}
func checkCursor() {
if let splitView = splitView {
if let minimisize = splitView.delegate?.splitViewIsCanMinimisize(), minimisize {
if mouseInside() || (NSEvent.pressedMouseButtons & (1 << 0)) != 0 {
if let cursor = splitView.delegate?.splitResizeCursor(at: self.frame.origin.offsetBy(dx: 5, dy: 0)) {
cursor.set()
} else {
if splitView.state == .minimisize {
NSCursor.resizeRight.set()
} else {
NSCursor.resizeLeft.set()
}
}
} else {
NSCursor.arrow.set()
}
}
}
}
fileprivate override func mouseEntered(with event: NSEvent) {
super.mouseEntered(with: event)
checkCursor()
}
override func cursorUpdate(with event: NSEvent) {
super.cursorUpdate(with: event)
checkCursor()
}
fileprivate override func mouseExited(with event: NSEvent) {
super.mouseExited(with: event)
checkCursor()
}
fileprivate override func mouseDragged(with event: NSEvent) {
super.mouseDragged(with: event)
if let splitView = splitView {
if let minimisize = splitView.delegate?.splitViewIsCanMinimisize(), minimisize {
checkCursor()
let current = splitView.convert(event.locationInWindow, from: nil)
if startDragging == .zero {
startDragging = current
acceptAllDrags = false
}
if abs(startDragging.x - current.x) > frame.width / 2 || abs(startDragging.y - current.y) > frame.width / 2 || acceptAllDrags {
acceptAllDrags = true
splitView.delegate?.splitViewShouldResize(at: current)
if current.x <= 100, splitView.state != .minimisize {
splitView.needMinimisize()
startPoint = current
} else if current.x >= 100, splitView.state == .minimisize {
splitView.needFullsize()
startPoint = current
} else {
splitView.resize(to: current)
}
}
}
}
}
private func notifyTableEndResize(in view: NSView) {
for view in view.subviews {
if let view = view as? TableView {
view.layoutItems()
} else {
notifyTableEndResize(in: view)
}
}
}
override func rightMouseUp(with event: NSEvent) {
super.rightMouseUp(with: event)
startPoint = .zero
startDragging = .zero
acceptAllDrags = false
}
fileprivate override func mouseUp(with event: NSEvent) {
startPoint = .zero
startDragging = .zero
acceptAllDrags = false
if let splitView = splitView {
notifyTableEndResize(in: splitView)
}
}
fileprivate override func mouseDown(with event: NSEvent) {
if let splitView = splitView {
startPoint = splitView.convert(event.locationInWindow, from: nil)
}
}
override func draw(_ layer: CALayer, in ctx: CGContext) {
super.draw(layer, in: ctx)
if let splitView = splitView {
if let drawBorder = splitView.delegate?.splitViewDrawBorder(), drawBorder {
ctx.setFillColor(presentation.colors.border.cgColor)
ctx.fill(NSMakeRect(floorToScreenPixels(backingScaleFactor, frame.width / 2), 0, .borderSize, frame.height))
}
}
}
}
public struct SplitProportion {
var min:CGFloat = 0;
var max:CGFloat = 0;
public init(min:CGFloat, max:CGFloat) {
self.min = min;
self.max = max;
}
}
public enum SplitViewState : Int {
case none = -1;
case single = 0;
case dual = 1;
case triple = 2;
case minimisize = 3
}
public protocol SplitViewDelegate : class {
func splitViewDidNeedSwapToLayout(state:SplitViewState) -> Void
func splitViewDidNeedMinimisize(controller:ViewController) -> Void
func splitViewDidNeedFullsize(controller:ViewController) -> Void
func splitViewIsCanMinimisize() -> Bool
func splitViewDrawBorder() -> Bool
func splitViewShouldResize(at point: NSPoint) -> Void
func splitResizeCursor(at point: NSPoint) -> NSCursor?
}
public extension SplitViewDelegate {
func splitViewShouldResize(at point: NSPoint) -> Void {
}
func splitResizeCursor(at point: NSPoint) -> NSCursor? {
return nil
}
}
public class SplitView : View {
private let minimisizeOverlay:SplitMinimisizeView = SplitMinimisizeView()
private let container:View
private var forceNotice:Bool = false
public var state: SplitViewState = .none {
didSet {
let notify:Bool = state != oldValue;
// assert(notify);
if(notify) {
self.delegate?.splitViewDidNeedSwapToLayout(state: state);
}
if state != .none {
if state == .dual || state == .minimisize {
if let _ = container.subviews.first {
if minimisizeOverlay.superview == nil {
addSubview(minimisizeOverlay)
}
}
} else {
minimisizeOverlay.removeFromSuperview()
}
} else {
minimisizeOverlay.removeFromSuperview()
}
}
}
public var mustMinimisize: Bool = false
public var canChangeState:Bool = true;
public weak var delegate:SplitViewDelegate?
private var _proportions:[Int:SplitProportion] = [:]
private var _startSize:[Int:NSSize] = [Int:NSSize]()
fileprivate var _controllers:[WeakReference<ViewController>] = []
private var _issingle:Bool?
private var _layoutProportions:[SplitViewState:SplitProportion] = [:]
private var _splitIdx:Int?
public override func draw(_ dirtyRect: NSRect) {
super.draw(dirtyRect)
}
required public init(frame frameRect: NSRect) {
container = View(frame: NSMakeRect(0,0,frameRect.width, frameRect.height))
super.init(frame: frameRect);
self.autoresizesSubviews = true
self.autoresizingMask = [.width, .height]
container.autoresizesSubviews = false
container.autoresizingMask = [.width, .height]
addSubview(container)
minimisizeOverlay.splitView = self
}
public override var backgroundColor: NSColor {
didSet {
container.backgroundColor = backgroundColor
minimisizeOverlay.needsDisplay = true
}
}
deinit {
var bp:Int = 0
bp += 1
}
required public init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
public func addController(controller:ViewController, proportion:SplitProportion) ->Void {
controller._frameRect = NSMakeRect(0, 0, proportion.min, frame.height)
container.addSubview(controller.view);
controller.viewWillAppear(false)
_controllers.append(WeakReference(value: controller));
_startSize.updateValue(controller.view.frame.size, forKey: controller.internalId);
_proportions.updateValue(proportion, forKey: controller.internalId)
controller.viewDidAppear(false)
}
func removeController(controller:ViewController) -> Void {
controller.viewWillDisappear(false)
let idx = _controllers.firstIndex(where: { $0.value == controller });
if let idx = idx {
container.subviews[idx].removeFromSuperview();
_controllers.remove(at: idx);
_startSize.removeValue(forKey: controller.internalId);
_proportions.removeValue(forKey: controller.internalId);
}
controller.viewDidDisappear(false)
}
public func removeAllControllers() -> Void {
var copy:[ViewController] = []
for controller in _controllers {
if let value = controller.value {
copy.append(value)
}
}
for controller in copy {
controller.viewWillDisappear(false)
}
container.removeAllSubviews();
_controllers.removeAll();
_startSize.removeAll();
_proportions.removeAll();
for controller in copy {
controller.viewDidDisappear(false)
}
}
public func setProportion(proportion:SplitProportion, state:SplitViewState) -> Void {
_layoutProportions[state] = proportion;
}
public func removeProportion(state:SplitViewState) -> Void {
_layoutProportions.removeValue(forKey: state);
if(_controllers.count > state.rawValue) {
_controllers.remove(at: state.rawValue)
}
}
public func updateStartSize(size:NSSize, controller:ViewController) -> Void {
_startSize[controller.internalId] = size;
_proportions[controller.internalId] = SplitProportion(min:size.width, max:size.height);
needsLayout = true
}
public func update(_ forceNotice:Bool = false) -> Void {
Queue.mainQueue().justDispatch {
self.forceNotice = forceNotice
self.needsLayout = true
}
}
public var nextLayout: SplitViewState {
let single:SplitProportion! = _layoutProportions[.single]
let dual:SplitProportion! = _layoutProportions[.dual]
let triple:SplitProportion! = _layoutProportions[.triple]
if acceptLayout(prop: single) && canChangeState && !mustMinimisize {
if frame.width < single.max {
if self.state != .single {
return .single;
}
} else if acceptLayout(prop: dual) {
if acceptLayout(prop: triple) {
if frame.width >= dual.min && frame.width <= dual.max {
if state != .dual {
return .dual;
}
} else if state != .triple {
return .triple;
}
} else {
if state != .dual && frame.width >= dual.min {
return .dual;
}
}
}
} else if mustMinimisize, self.state != .minimisize {
return .minimisize
}
return self.state
}
public override func layout() {
super.layout()
//assert(!_controllers.isEmpty)
let single:SplitProportion! = _layoutProportions[.single]
let dual:SplitProportion! = _layoutProportions[.dual]
let triple:SplitProportion! = _layoutProportions[.triple]
if acceptLayout(prop: single) && canChangeState && !mustMinimisize {
if frame.width < single.max {
if self.state != .single {
self.state = .single;
}
} else if acceptLayout(prop: dual) {
if acceptLayout(prop: triple) {
if frame.width >= dual.min && frame.width <= dual.max {
if state != .dual {
state = .dual;
}
} else if state != .triple {
self.state = .triple;
}
} else {
if state != .dual && frame.width >= dual.min {
self.state = .dual;
}
}
}
} else if mustMinimisize, self.state != .minimisize {
self.state = .minimisize
}
if forceNotice {
forceNotice = false
self.delegate?.splitViewDidNeedSwapToLayout(state: state)
}
var x:CGFloat = 0;
for (index, obj) in _controllers.enumerated() {
if let obj = obj.value {
let proportion:SplitProportion = _proportions[obj.internalId]!;
let startSize:NSSize = _startSize[obj.internalId]!;
var size:NSSize = NSMakeSize(x, frame.height);
var _min:CGFloat = startSize.width;
_min = proportion.min;
if(proportion.max == CGFloat.greatestFiniteMagnitude && index != _controllers.count-1) {
var m2:CGFloat = 0;
for i:Int in index + 1 ..< _controllers.count - index {
let split:ViewController? = _controllers[i].value;
if let split = split {
let proportion:SplitProportion = _proportions[split.internalId]!;
m2+=proportion.min;
}
}
_min = frame.width - x - m2;
}
if index < _controllers.count - 1, state != .minimisize {
_min = min(_min, frame.width - 350)
}
if(index == _controllers.count - 1) {
_min = frame.width - x;
}
size = NSMakeSize(x + _min > frame.width ? (frame.width - x) : _min, frame.height);
let rect:NSRect = NSMakeRect(x, 0, size.width, size.height);
if(!NSEqualRects(rect, obj.view.frame)) {
obj.view.frame = rect;
}
x+=size.width;
}
}
//assert(state != .none)
if state != .none {
if state == .dual || state == .minimisize {
if let first = container.subviews.first {
minimisizeOverlay.frame = NSMakeRect(first.frame.maxX - 5, 0, 10, frame.height)
}
}
}
}
public func needFullsize() {
self.state = .none
self.needsLayout = true
}
public func needMinimisize() {
self.state = .minimisize
self.needsLayout = true
}
func resize(to point: NSPoint) {
//NSLog("\(point)")
}
func acceptLayout(prop:SplitProportion!) -> Bool {
return prop != nil ? (prop!.min > 0 && prop!.max > 0) : false;
}
}