From 3c8310c5765e2890c3bf850440194d7e0f00d7c9 Mon Sep 17 00:00:00 2001 From: Miguel de Icaza Date: Mon, 17 Apr 2023 23:38:09 -0400 Subject: [PATCH 1/2] This change only queues the exposed region to be repainted on UIKit. This works, but it exposes a problem: scrolling of text (ls -l on a large directory) will redraw the text on top of the old text - this is caused by the clear background that I allow using. So I need to account for that. This sadly also brings back the ugly bar at the bottom, because the UIScrollView allows for over-scroll, and I do not seem to have a way of painting there. The solutions on StackOverflow include "Append a view underneat with the color that you want" which is some ugly stuff. Will investigate later. --- .../SwiftTerm/Apple/AppleTerminalView.swift | 22 ++++++++++--------- Sources/SwiftTerm/iOS/iOSTerminalView.swift | 22 +++++++++++++++++-- TerminalApp/MacTerminal/ViewController.swift | 1 + TerminalApp/iOSTerminal/ViewController.swift | 1 + 4 files changed, 34 insertions(+), 12 deletions(-) diff --git a/Sources/SwiftTerm/Apple/AppleTerminalView.swift b/Sources/SwiftTerm/Apple/AppleTerminalView.swift index 046a089f..17b6985f 100644 --- a/Sources/SwiftTerm/Apple/AppleTerminalView.swift +++ b/Sources/SwiftTerm/Apple/AppleTerminalView.swift @@ -100,11 +100,11 @@ extension TerminalView { search = SearchService (terminal: terminal) - #if os(macOS) +#if os(macOS) needsDisplay = true - #else +#else setNeedsDisplay(frame) - #endif +#endif } /// Returns the underlying terminal emulator that the `TerminalView` is a view for @@ -730,7 +730,7 @@ extension TerminalView { nativeBackgroundColor.setFill() context.fill ([box]) } -#elseif false +#else // Currently the caller on iOS is clearing the entire dirty region due to the ordering of // font change sizes, but once we fix that, we should remove the clearing of the dirty // region in the calling code, and enable this code instead. @@ -811,7 +811,7 @@ extension TerminalView { terminal.clearUpdateRange () - #if os(macOS) +#if os(macOS) let baseLine = frame.height var region = CGRect (x: 0, y: baseLine - (cellDimension.height + CGFloat(rowEnd) * cellDimension.height), @@ -826,11 +826,13 @@ extension TerminalView { region = CGRect (x: 0, y: 0, width: frame.width, height: oh + oy) } setNeedsDisplay(region) - #else - // TODO iOS: need to update the code above, but will do that when I get some real - // life data being fed into it. - setNeedsDisplay(bounds) - #endif +#else + let region = CGRect (x: 0, + y: CGFloat (rowStart)*cellDimension.height+contentOffset.y, + width: frame.width, + height: CGFloat (rowEnd-rowStart+1) * cellDimension.height) + setNeedsDisplay(region) +#endif pendingDisplay = false updateDebugDisplay () diff --git a/Sources/SwiftTerm/iOS/iOSTerminalView.swift b/Sources/SwiftTerm/iOS/iOSTerminalView.swift index 56ac63d6..fddefa7f 100644 --- a/Sources/SwiftTerm/iOS/iOSTerminalView.swift +++ b/Sources/SwiftTerm/iOS/iOSTerminalView.swift @@ -961,18 +961,36 @@ open class TerminalView: UIScrollView, UITextInputTraits, UIKeyInput, UIScrollVi guard let context = getCurrentGraphicsContext() else { return } - + + //print (dirtyRect) // Without these two lines, on font changes, some junk is being displayed // Once we test the font change, we could disable these two lines, and // enable the #if false in drawterminalContents that should be coping with this now nativeBackgroundColor.set () - context.fill ([dirtyRect]) + //print (nativeBackgroundColor) + //UIColor.black.set() + context.clear (dirtyRect) + //context.fill ([dirtyRect]) + //context.saveGState() // drawTerminalContents and CoreText expect the AppKit coordinate system context.scaleBy (x: 1, y: -1) context.translateBy(x: 0, y: -frame.height) drawTerminalContents (dirtyRect: dirtyRect, context: context, bufferOffset: 0) +// context.restoreGState() +// +// switch Int.random(in: 0..<3) { +// case 0: +// context.setFillColor(red: 1.0, green: 0, blue: 0, alpha: 0.3) +// case 1: +// context.setFillColor(red: 0, green: 1.0, blue: 0, alpha: 0.3) +// case 2: +// context.setFillColor(red: 0, green: 0, blue: 1.0, alpha: 0.3) +// default: +// context.setFillColor(red: 0.4, green: 0, blue: 1.0, alpha: 0.3) +// } +// context.fill ([dirtyRect]) } open override var bounds: CGRect { diff --git a/TerminalApp/MacTerminal/ViewController.swift b/TerminalApp/MacTerminal/ViewController.swift index d532e5b4..3464afdb 100644 --- a/TerminalApp/MacTerminal/ViewController.swift +++ b/TerminalApp/MacTerminal/ViewController.swift @@ -124,6 +124,7 @@ class ViewController: NSViewController, LocalProcessTerminalViewDelegate, NSUser terminal = LocalProcessTerminalView(frame: view.frame) zoomGesture = NSMagnificationGestureRecognizer(target: self, action: #selector(zoomGestureHandler)) terminal.addGestureRecognizer(zoomGesture!) + terminal.getTerminal().backgroundColor = SwiftTerm.Color(red: 0, green: 0x8000, blue: 0) ViewController.lastTerminal = terminal terminal.processDelegate = self terminal.feed(text: "Welcome to SwiftTerm") diff --git a/TerminalApp/iOSTerminal/ViewController.swift b/TerminalApp/iOSTerminal/ViewController.swift index d61278a4..907dc129 100644 --- a/TerminalApp/iOSTerminal/ViewController.swift +++ b/TerminalApp/iOSTerminal/ViewController.swift @@ -92,6 +92,7 @@ class ViewController: UIViewController { } view.addSubview(tv) + tv.getTerminal().backgroundColor = SwiftTerm.Color.init(red: 0x8080, green: 0, blue: 0) setupKeyboardMonitor() tv.becomeFirstResponder() self.tv.feed(text: "Welcome to SwiftTerm - connecting to my localhost\r\n\n") From 668d01f81abd3eff2f8f7e7ffacadcb3f90b2cde Mon Sep 17 00:00:00 2001 From: Miguel de Icaza Date: Wed, 19 Apr 2023 15:57:05 -0400 Subject: [PATCH 2/2] I figured out we need both drawing bits --- .../SwiftTerm/Apple/AppleTerminalView.swift | 81 ++++++++++--------- Sources/SwiftTerm/iOS/iOSTerminalView.swift | 32 +++----- 2 files changed, 54 insertions(+), 59 deletions(-) diff --git a/Sources/SwiftTerm/Apple/AppleTerminalView.swift b/Sources/SwiftTerm/Apple/AppleTerminalView.swift index 17b6985f..6618252a 100644 --- a/Sources/SwiftTerm/Apple/AppleTerminalView.swift +++ b/Sources/SwiftTerm/Apple/AppleTerminalView.swift @@ -534,25 +534,25 @@ extension TerminalView { let lineDescent = CTFontGetDescent(fontSet.normal) let lineLeading = CTFontGetLeading(fontSet.normal) let yOffset = ceil(lineDescent+lineLeading) - + func calcLineOffset (forRow: Int) -> CGFloat { cellDimension.height * CGFloat (forRow-bufferOffset+1) } // draw lines - #if os(iOS) +#if os(iOS) // On iOS, we are drawing the exposed region let cellHeight = cellDimension.height let firstRow = Int (dirtyRect.minY/cellHeight) let lastRow = Int(dirtyRect.maxY/cellHeight) - #else +#else // On Mac, we are drawing the terminal buffer let cellHeight = cellDimension.height let boundsMaxY = bounds.maxY let firstRow = terminal.buffer.yDisp+Int ((boundsMaxY-dirtyRect.maxY)/cellHeight) let lastRow = terminal.buffer.yDisp+Int((boundsMaxY-dirtyRect.minY)/cellHeight) - #endif - +#endif + for row in firstRow...lastRow { if row < 0 { continue @@ -575,22 +575,22 @@ extension TerminalView { // Debug aid // context.setFillColor(CGColor(red: 0, green: Double (row)/25.0, blue: 0, alpha: 1)) // context.fill([lineRect]) - + context.translateBy(x: 0, y: pivot) context.scaleBy (x: 2, y: 2) context.translateBy(x: 0, y: -pivot) - + case .doubledTop: context.saveGState() let pivot = lineOrigin.y + cellDimension.height let lineRect = CGRect (origin: CGPoint (x: 0, y: lineOrigin.y), size: CGSize (width: dirtyRect.width, height: cellDimension.height)) - + context.clip(to: [lineRect]) // Debug Aid //context.setFillColor(CGColor(red: Double (row)/25.0, green: 0, blue: 0, alpha: 1)) //context.fill([lineRect]) - + context.translateBy(x: 0, y: pivot) context.scaleBy (x: 2, y: 2) context.translateBy(x: 0, y: -pivot) @@ -599,60 +599,60 @@ extension TerminalView { context.saveGState() context.scaleBy (x: 2, y: 1) } - #if false +#if false // This optimization is useful, but only if we can get proper exposed regions // and while it works most of the time with the BigSur change, there is still // a case where we just get full exposes despite requesting only a line // repro: fill 300 lines, then clear screen then repeatedly output commands // that produce 3-5 lines of text: while we send AppKit the right boundary, - // AppKit still send everything. + // AppKit still send everything. let lineRect = CGRect (origin: lineOrigin, size: CGSize (width: dirtyRect.width, height: cellDimension.height)) if !lineRect.intersects(dirtyRect) { //print ("Skipping row \(row) because it does nto intersect") continue - } - #endif + } +#endif let line = terminal.buffer.lines [row] let lineInfo = buildAttributedString(row: row, line: line, cols: terminal.cols) let ctline = CTLineCreateWithAttributedString(lineInfo.attrStr) - + var col = 0 for run in CTLineGetGlyphRuns(ctline) as? [CTRun] ?? [] { let runGlyphsCount = CTRunGetGlyphCount(run) let runAttributes = CTRunGetAttributes(run) as? [NSAttributedString.Key: Any] ?? [:] let runFont = runAttributes[.font] as! TTFont - + let runGlyphs = [CGGlyph](unsafeUninitializedCapacity: runGlyphsCount) { (bufferPointer, count) in CTRunGetGlyphs(run, CFRange(), bufferPointer.baseAddress!) count = runGlyphsCount } - + var positions = runGlyphs.enumerated().map { (i: Int, glyph: CGGlyph) -> CGPoint in CGPoint(x: lineOrigin.x + (cellDimension.width * CGFloat(col + i)), y: lineOrigin.y + yOffset) } - + var backgroundColor: TTColor? if runAttributes.keys.contains(.selectionBackgroundColor) { backgroundColor = runAttributes[.selectionBackgroundColor] as? TTColor } else if runAttributes.keys.contains(.backgroundColor) { backgroundColor = runAttributes[.backgroundColor] as? TTColor } - + if let backgroundColor = backgroundColor { context.saveGState () - + context.setShouldAntialias (false) context.setLineCap (.square) context.setLineWidth(0) context.setFillColor(backgroundColor.cgColor) - + let transform = CGAffineTransform (translationX: positions[0].x, y: 0) - + var size = CGSize (width: CGFloat (cellDimension.width * CGFloat(runGlyphsCount)), height: cellDimension.height) var origin: CGPoint = lineOrigin - - #if (lastLineExtends) + +#if (lastLineExtends) // Stretch last col/row to full frame size. // TODO: need apply this kind of fixup to selection too if (row-terminal.buffer.yDisp) >= terminal.rows - 1 { @@ -660,24 +660,24 @@ extension TerminalView { size.height += missing origin.y -= missing } - #endif - +#endif + if col + runGlyphsCount >= terminal.cols { size.width += frame.width - size.width } - + let rect = CGRect (origin: origin, size: size) - - #if os(macOS) + +#if os(macOS) rect.applying(transform).fill(using: .destinationOver) - #else +#else context.fill(rect.applying(transform)) - #endif +#endif context.restoreGState() } - + nativeForegroundColor.set() - + if runAttributes.keys.contains(.foregroundColor) { let color = runAttributes[.foregroundColor] as! TTColor let cgColor = color.cgColor @@ -688,13 +688,13 @@ extension TerminalView { } CTFontDrawGlyphs(runFont, runGlyphs, &positions, positions.count, context) - + // Draw other attributes drawRunAttributes(runAttributes, glyphPositions: positions, in: context) - + col += runGlyphsCount } - + // Render any sixel content last if let images = lineInfo.images { let rowBase = frame.height - (CGFloat(row) * cellDimension.height) @@ -731,17 +731,26 @@ extension TerminalView { context.fill ([box]) } #else + if false { // Currently the caller on iOS is clearing the entire dirty region due to the ordering of // font change sizes, but once we fix that, we should remove the clearing of the dirty // region in the calling code, and enable this code instead. + // + // NOTE: there is another reason, the caller needs to clear the region + // to avoid situations where the previous text is not painted over + // when we have a transparent background that does not clear the + // previous color before. + + // Perhaps cast let lineOffset = calcLineOffset(forRow: lastRow) let lineOrigin = CGPoint(x: 0, y: frame.height - lineOffset) - + let inter = dirtyRect.intersection(CGRect (x: 0, y: lineOrigin.y, width: bounds.width, height: cellHeight)) if !inter.isEmpty { nativeBackgroundColor.setFill() context.fill ([inter]) } + } #endif #if os(iOS) diff --git a/Sources/SwiftTerm/iOS/iOSTerminalView.swift b/Sources/SwiftTerm/iOS/iOSTerminalView.swift index fddefa7f..8b64d6f9 100644 --- a/Sources/SwiftTerm/iOS/iOSTerminalView.swift +++ b/Sources/SwiftTerm/iOS/iOSTerminalView.swift @@ -961,36 +961,22 @@ open class TerminalView: UIScrollView, UITextInputTraits, UIKeyInput, UIScrollVi guard let context = getCurrentGraphicsContext() else { return } - - //print (dirtyRect) - // Without these two lines, on font changes, some junk is being displayed - // Once we test the font change, we could disable these two lines, and - // enable the #if false in drawterminalContents that should be coping with this now + + // This is necessary because we might be re-rendering a + // part of the screen, and we might have a transparent + // background, so it is necessary to clear first. nativeBackgroundColor.set () - //print (nativeBackgroundColor) - //UIColor.black.set() - context.clear (dirtyRect) - //context.fill ([dirtyRect]) + //context.clear (dirtyRect) + + // If this works with the transparent background, that should work + // and it avoids the extra code in ApplTerminalView in the iOS case + context.fill ([dirtyRect]) - //context.saveGState() // drawTerminalContents and CoreText expect the AppKit coordinate system context.scaleBy (x: 1, y: -1) context.translateBy(x: 0, y: -frame.height) drawTerminalContents (dirtyRect: dirtyRect, context: context, bufferOffset: 0) -// context.restoreGState() -// -// switch Int.random(in: 0..<3) { -// case 0: -// context.setFillColor(red: 1.0, green: 0, blue: 0, alpha: 0.3) -// case 1: -// context.setFillColor(red: 0, green: 1.0, blue: 0, alpha: 0.3) -// case 2: -// context.setFillColor(red: 0, green: 0, blue: 1.0, alpha: 0.3) -// default: -// context.setFillColor(red: 0.4, green: 0, blue: 1.0, alpha: 0.3) -// } -// context.fill ([dirtyRect]) } open override var bounds: CGRect {