Skip to content

Commit

Permalink
Improve scroll in tmux
Browse files Browse the repository at this point in the history
  • Loading branch information
yury committed Apr 17, 2020
1 parent 42c2b43 commit f623195
Show file tree
Hide file tree
Showing 5 changed files with 156 additions and 61 deletions.
4 changes: 1 addition & 3 deletions Blink/Commands/help.m
Original file line number Diff line number Diff line change
Expand Up @@ -69,12 +69,10 @@ int help_main(int argc, char *argv[]) {
@"",
@"Gestures:",
@" ✌️ tap -> New Terminal. ",
@" ✌️ up/down -> Mouse wheel. ",
@" 👆 tap -> Mouse click. ",
@" 👆 drag down -> Dismiss keyboard. ",
@" 👆 swipe left/right -> Switch Terminals. ",
@" pinch -> Change font size.",
@"",
UIDevice.currentDevice.userInterfaceIdiom == UIUserInterfaceIdiomPhone ? @" 👆 drag down -> Dismiss keyboard.\n" : @"",
@"Shortcuts:",
@" Press and hold ⌘ on hardware kb to show a list of shortcuts.",
@" Run config. Go to Keyboard > Shortcuts for configuration.",
Expand Down
1 change: 1 addition & 0 deletions Blink/SpaceController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ class SpaceController: UIViewController {
_viewportsController.dataSource = self
_viewportsController.delegate = self


addChild(_viewportsController)

if let v = _viewportsController.view {
Expand Down
200 changes: 149 additions & 51 deletions Blink/WebKit/WKWebView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ import UIKit
import WebKit

class UIScrollViewWithoutHitTest: UIScrollView {
var isInfinit = false

var reportedScroll: CGPoint? = nil

override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
let scrollBarWidth: CGFloat = 24
if
Expand All @@ -43,6 +47,43 @@ class UIScrollViewWithoutHitTest: UIScrollView {
}
return nil
}

override func layoutSubviews() {
super.layoutSubviews()

guard isInfinit
else {
return
}

let requiredSize = CGSize(width: bounds.width /* * 10 */, height: bounds.height * 10)
if contentSize != requiredSize {
contentSize = requiredSize
}

var currentOffset = contentOffset

let centerOffsetX = (requiredSize.width - bounds.size.width) * 0.5;
let centerOffsetY = (requiredSize.height - bounds.size.height) * 0.5;

let distanceFromCenterY = abs(currentOffset.y - centerOffsetY);
let distanceFromCenterX = abs(currentOffset.x - centerOffsetY);

if distanceFromCenterY > (requiredSize.height / 4.0) {
currentOffset = CGPoint(x: currentOffset.x, y: centerOffsetY)
contentOffset = currentOffset
reportedScroll = nil
}

// if distanceFromCenterX > (requiredSize.width / 4.0) {
// currentOffset = CGPoint(x: centerOffsetX, y: currentOffset.y)
// }

// if currentOffset != contentOffset {
// contentOffset = currentOffset
// }
}

}

/**
Expand All @@ -57,16 +98,31 @@ class UIScrollViewWithoutHitTest: UIScrollView {
var view: UIView? = nil
private weak var _wkWebView: WKWebView? = nil
private let _scrollView = UIScrollViewWithoutHitTest()
private let _termScrollView = UIScrollViewWithoutHitTest()
private let _jsScrollerPath: String
private let _2fPanRecognizer = UIPanGestureRecognizer()
private let _1fTapRecognizer = UITapGestureRecognizer()
private let _2fTapRecognizer = UITapGestureRecognizer()
private let _pinchRecognizer = UIPinchGestureRecognizer()
private let _3fTapRecognizer = UITapGestureRecognizer()
private let _longPressRecognizer = UILongPressGestureRecognizer()
private let _hoverRecognizer = UIHoverGestureRecognizer()
private var _characterSize: CGSize? = nil
private var _scrollPoint: CGPoint? = nil
private var _scrollPointTrackpad: CGPoint? = nil

@objc var focused: Bool = false;
@objc var hasSelection: Bool = false;
@objc var hasSelection: Bool = false {
didSet {
_pinchRecognizer.isEnabled = !hasSelection
_1fTapRecognizer.isEnabled = !hasSelection

if hasSelection {
_scrollView.panGestureRecognizer.dropTouches()
_termScrollView.panGestureRecognizer.dropTouches()
view?.dropSuperViewTouches()
}
}
}

@objc var indicatorStyle: UIScrollView.IndicatorStyle {
get { _scrollView.indicatorStyle }
Expand All @@ -75,13 +131,14 @@ class UIScrollViewWithoutHitTest: UIScrollView {

var allRecognizers:[UIGestureRecognizer] {
let recognizers = [
_2fPanRecognizer,
_1fTapRecognizer,
_2fTapRecognizer,
_3fTapRecognizer,
_pinchRecognizer,
_longPressRecognizer,
_scrollView.panGestureRecognizer
_scrollView.panGestureRecognizer,
_termScrollView.panGestureRecognizer,
_hoverRecognizer
]
return recognizers
}
Expand All @@ -96,6 +153,7 @@ class UIScrollViewWithoutHitTest: UIScrollView {

_scrollView.frame = webView.bounds
webView.addSubview(_scrollView)
webView.addSubview(_termScrollView)
webView.configuration.userContentController.add(self, name: "wkScroller")

for r in allRecognizers {
Expand All @@ -105,6 +163,7 @@ class UIScrollViewWithoutHitTest: UIScrollView {
_wkWebView = webView
} else {
_scrollView.removeFromSuperview()
_termScrollView.removeFromSuperview()
_wkWebView?.configuration.userContentController.removeScriptMessageHandler(forName: "wkScroller")

for r in allRecognizers {
Expand All @@ -123,18 +182,32 @@ class UIScrollViewWithoutHitTest: UIScrollView {
_jsScrollerPath = jsScrollerPath
super.init()
_scrollView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
_scrollView.alwaysBounceVertical = true
_scrollView.alwaysBounceVertical = false
_scrollView.alwaysBounceHorizontal = false
_scrollView.isDirectionalLockEnabled = true
_scrollView.keyboardDismissMode = .interactive
// iPad have dismiss button on keyboard
if UIDevice.current.userInterfaceIdiom != .pad {
_scrollView.keyboardDismissMode = .interactive
}
_scrollView.delaysContentTouches = false
_scrollView.delegate = self

_2fPanRecognizer.minimumNumberOfTouches = 2
_2fPanRecognizer.maximumNumberOfTouches = 2
_2fPanRecognizer.delegate = self
_2fPanRecognizer.addTarget(self, action: #selector(_on2fPan(_:)))

_termScrollView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
_termScrollView.alwaysBounceVertical = false
_termScrollView.alwaysBounceHorizontal = false
_termScrollView.isDirectionalLockEnabled = true
_termScrollView.keyboardDismissMode = .none
_termScrollView.delaysContentTouches = false
_termScrollView.delegate = self
// _termScrollView.isUserInteractionEnabled = true
_termScrollView.showsVerticalScrollIndicator = false
_termScrollView.showsHorizontalScrollIndicator = false
_termScrollView.isInfinit = true
_termScrollView.decelerationRate = .fast
// _termScrollView.isUserInteractionEnabled = false
// _termScrollView.isHidden = true

_3fTapRecognizer.numberOfTapsRequired = 1
_3fTapRecognizer.numberOfTouchesRequired = 3
_3fTapRecognizer.delegate = self
Expand All @@ -152,52 +225,21 @@ class UIScrollViewWithoutHitTest: UIScrollView {
_2fTapRecognizer.numberOfTouchesRequired = 2
_2fTapRecognizer.delegate = self
_2fTapRecognizer.addTarget(self, action: #selector(_on2fTap(_:)))
_2fTapRecognizer.require(toFail: _2fPanRecognizer)

_pinchRecognizer.delegate = self
_pinchRecognizer.addTarget(self, action: #selector(_onPinch(_:)))
_pinchRecognizer.require(toFail: _2fTapRecognizer)

_hoverRecognizer.addTarget(self, action: #selector(_onHover(_:)))
}

private var _reportedY:CGFloat = 0

@objc func _on2fPan(_ recognizer: UIPanGestureRecognizer) {
let point = recognizer.location(in: recognizer.view)

switch recognizer.state {
case .began:
_scrollView.panGestureRecognizer.dropTouches()
recognizer.view?.superview?.dropSuperViewTouches()

_scrollView.isScrollEnabled = false
_scrollView.showsVerticalScrollIndicator = false
_reportedY = point.y
case .changed:
let dY = point.y - _reportedY;
if abs(dY) < 5 {
return
}
_scrollView.panGestureRecognizer.dropTouches()
_1fTapRecognizer.dropTouches()
_pinchRecognizer.dropTouches()
_reportedY = point.y
let deltaY = dY > 0 ? -1 : 1
let deltaX = 0 // hterm supports only deltaY for now
_wkWebView?.evaluateJavaScript("term_reportWheelEvent(\"wheel\", \(point.x), \(point.y), \(deltaX), \(deltaY));", completionHandler: nil)
case .ended: fallthrough
case .cancelled:
_scrollView.isScrollEnabled = true
_scrollView.showsVerticalScrollIndicator = true
default: break
}
}

@objc func _on1fTap(_ recognizer: UITapGestureRecognizer) {
let point = recognizer.location(in: recognizer.view)
switch recognizer.state {
case .recognized:
if hasSelection {
return
}

if focused {
_wkWebView?.evaluateJavaScript("term_reportMouseClick(\(point.x), \(point.y), 1, \(BKDefaults.isKeyCastsOn() ? "true" : "false"));", completionHandler: nil)
}
Expand Down Expand Up @@ -237,23 +279,27 @@ class UIScrollViewWithoutHitTest: UIScrollView {
}

@objc func _onPinch(_ recognizer: UIPinchGestureRecognizer) {
if recognizer.state == .possible {
return
}

let dScale = 1.0 - recognizer.scale;
if abs(dScale) > 0.06 {
recognizer.view?.superview?.dropSuperViewTouches()
_scrollView.panGestureRecognizer.dropTouches()
_termScrollView.panGestureRecognizer.dropTouches()
_2fTapRecognizer.dropTouches()
_2fPanRecognizer.dropTouches()

if let target = _wkWebView?.target(forAction: #selector(scaleWithPich(_:)), withSender: recognizer) as? UIResponder {
target.perform(#selector(scaleWithPich(_:)), with: recognizer)
}
}
}

@objc func _onHover(_ recognizer: UIHoverGestureRecognizer) {
switch recognizer.state {
case .began, .changed:
_scrollPointTrackpad = recognizer.location(in: view)
default: break
}
}

@objc func scaleWithPich(_ pinch: UIPinchGestureRecognizer) {

}
Expand All @@ -277,7 +323,43 @@ extension WKWebViewGesturesInteraction: UIGestureRecognizerDelegate {
extension WKWebViewGesturesInteraction: UIScrollViewDelegate {
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let offset = scrollView.contentOffset
_wkWebView?.evaluateJavaScript("\(_jsScrollerPath).reportScroll(\(offset.x), \(offset.y));", completionHandler: nil)

if scrollView === _scrollView {
_wkWebView?.evaluateJavaScript("\(_jsScrollerPath).reportScroll(\(offset.x), \(offset.y));", completionHandler: nil)
return
}

if scrollView === _termScrollView {
var charHeight: CGFloat = _characterSize?.height ?? 20
if (charHeight <= 0) {
charHeight = 20
}
if var reportedScroll = _termScrollView.reportedScroll {
let deltaY = offset.y - reportedScroll.y
if abs(deltaY) < charHeight {
return
}
let dY = CGFloat(Int(deltaY) / Int(charHeight)) * charHeight
reportedScroll.y = reportedScroll.y + dY
_termScrollView.reportedScroll = reportedScroll

let point = _scrollPoint ?? _scrollPointTrackpad ?? CGPoint.zero

_wkWebView?.evaluateJavaScript("term_reportWheelEvent(\"wheel\", \(point.x), \(point.y), \(0), \(dY));", completionHandler: nil)
}

}
}

func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
if scrollView == _termScrollView {
if _termScrollView.panGestureRecognizer.numberOfTouches > 0 {
_scrollPoint = _termScrollView.panGestureRecognizer.location(in: view)
} else {
_scrollPoint = nil
}
_termScrollView.reportedScroll = _termScrollView.contentOffset
}
}
}

Expand All @@ -293,20 +375,36 @@ extension WKWebViewGesturesInteraction: WKScriptMessageHandler {
switch op {
case "resize":
let contentSize = NSCoder.cgSize(for: msg["contentSize"] as? String ?? "")
let characterSize = NSCoder.cgSize(for: msg["characterSize"] as? String ?? "")
let isPrimary = msg["isPrimary"] as? Bool ?? true

_characterSize = characterSize
_scrollView.contentSize = contentSize
let offset = CGPoint(x: 0, y: max(contentSize.height - _scrollView.bounds.height, 0));
_scrollView.contentOffset = offset

_scrollView.panGestureRecognizer.isEnabled = isPrimary
_termScrollView.panGestureRecognizer.isEnabled = !isPrimary


case "scrollTo":
let animated = msg["animated"] as? Bool == true
let isPrimary = msg["isPrimary"] as? Bool ?? true

let x: CGFloat = msg["x"] as? CGFloat ?? 0
let y: CGFloat = msg["y"] as? CGFloat ?? 0
let offset = CGPoint(x: x, y: y)

_scrollView.panGestureRecognizer.isEnabled = isPrimary
_termScrollView.panGestureRecognizer.isEnabled = !isPrimary

if (offset == _scrollView.contentOffset) {
return
}
// TODO: debounce?
_scrollView.setContentOffset(offset, animated: animated)


default: break
}
}
Expand Down
4 changes: 2 additions & 2 deletions Resources/hterm_all.min.js

Large diffs are not rendered by default.

8 changes: 3 additions & 5 deletions Resources/term.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ function term_setupDefaults() {
term_set('audible-bell-sound', '');
term_set('receive-encoding', 'raw'); // we are UTF8
term_set('allow-images-inline', true); // need to make it work
term_set('scroll-wheel-may-send-arrow-keys', true)
}

function term_processKB(str) {
Expand Down Expand Up @@ -279,11 +280,8 @@ function term_reportWheelEvent(name, x, y, deltaX, deltaY) {
return;
}

var event = new WheelEvent(name, {deltaX, deltaY});
_setTermCoordinates(event, x, y);
if (!t.prompt.processMouseScroll(event)) {
t.onMouse(event);
}
var event = new WheelEvent(name, {clientX: x, clientY: y, deltaX, deltaY});
t.onMouse_Blink(event);
}

function term_setWidth(cols) {
Expand Down

0 comments on commit f623195

Please sign in to comment.