Skip to content

Commit

Permalink
Merge pull request #320 from VODGroup/extendedInsets
Browse files Browse the repository at this point in the history
Improve zooming
  • Loading branch information
akaDuality authored Jan 5, 2024
2 parents a124f56 + 57d11ac commit 04cc125
Show file tree
Hide file tree
Showing 23 changed files with 314 additions and 167 deletions.
12 changes: 2 additions & 10 deletions Shared/Sources/Canvas/CanvasPresenter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,30 +12,22 @@ import TextRecognition
import Foundation
import QuartzCore

public protocol CanvasScrollViewProtocol: AnyObject {
func fitToWindow(animated: Bool)
}

public class CanvasPresenter: DocumentPresenter {

public weak var uiContent: DrawingView!
public weak var uiScroll: CanvasScrollViewProtocol!
var drawingController: DrawingController!

public func didLoad(
uiContent: DrawingView,
uiScroll: CanvasScrollViewProtocol,
initialScale: CGFloat,
previewSource: PreviewSourceProtocol
) {
self.uiContent = uiContent
self.uiScroll = uiScroll
self.scale = initialScale
self.drawingController = DrawingController(view: uiContent)
self.document.previewSource = previewSource

redraw(artboard: document.artboard)

}

private var scale: CGFloat = 1
Expand Down Expand Up @@ -173,8 +165,8 @@ public class CanvasPresenter: DocumentPresenter {
}

// MARK: Image
public func add(image: Image) {
add(image: image, origin: document.artboard.suggestOrigin())
public func add(image: Image, name: String) {
add(image: image, name: name, origin: document.artboard.suggestOrigin())
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,15 @@ open class DocumentPresenter {
}

// MARK:
open func add(image: Image, origin: CGPoint) {
open func add(
image: Image,
name: String?,
origin: CGPoint
) {
document.invalidateQuickViewPreview()

let frame = Frame(image: image,
name: name,
frame: CGRect(origin: origin,
size: image.size))

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,9 @@ public class VODesignDocument: AppleDocument, VODesignDocumentProtocol {

displayName = image.name() ?? Date().description

addFrame(with: image, origin: .zero)
addFrame(with: image,
name: image.name(),
origin: .zero)
}

var version: DocumentVersion!
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,7 @@ extension VODesignDocumentProtocol {
?? CGRect(origin: .zero,
size: image?.size ?? defaultFrameSize) // TODO: Add image's scale

let name = frameWrapper.filename ?? UUID().uuidString
let name = frameWrapper.filename ?? UUID().uuidString // TODO: Remove uuidString from here

return Frame(label: name,
imageLocation: .relativeFile(path: "Frame/\(FileName.screen)"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,11 @@ public protocol VODesignDocumentProtocol: AnyObject {
extension VODesignDocumentProtocol {
public func addFrame(
with newImage: Image,
name: String?,
origin: CGPoint
) {
let frame = Frame(image: newImage,
name: name,
frame: CGRect(origin: origin,
size: newImage.size))
artboard.append(frame)
Expand Down Expand Up @@ -87,14 +89,16 @@ public protocol PreviewSourceProtocol: AnyObject {
}

extension Frame {
public convenience init(image: Image, frame: CGRect) {
let name = UUID().uuidString // TODO: Create fancy name

// let frame = CGRect(origin: .zero, size: image.size)
public convenience init(
image: Image,
name: String?,
frame: CGRect
) {
let name = name ?? UUID().uuidString

self.init(label: name,
imageLocation: .cache(image: image,
name: UUID().uuidString), // TODO: Make fancy name. Call to ChatGPT!!
name: name),
frame: frame,
elements: [])
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ final class DocumentImage_ScaleTests: XCTestCase {
func test_APPKit_whenUpdateImageExternaly_shouldSetImageSizeWithScale() throws {
let document = try VODesignDocument(type: uti)

document.addFrame(with: try imageWith3xScale(), origin: .zero)
document.addFrame(with: try imageWith3xScale(), name: "Sample", origin: .zero)

let frame = try XCTUnwrap(document.artboard.frames.first)
XCTAssertEqual(
Expand All @@ -59,7 +59,7 @@ final class DocumentImage_ScaleTests: XCTestCase {
func test_whenAddImage_shouldCreateFrameOfImageSize() throws {
let document = try VODesignDocument(type: uti)

document.addFrame(with: try imageWith3xScale(), origin: .zero)
document.addFrame(with: try imageWith3xScale(), name: "Sample", origin: .zero)

let frame = try XCTUnwrap(document.artboard.frames.first, "should create frame from image")
XCTAssertEqual(frame.frame, CGRect(origin: .zero, size: scaledSize), "should use scaled frame")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ class DocumentWrappersInvalidationTests: XCTestCase {
func test_documentWithImage_whenUpdateImage_shouldInvalidateQuickLookFile() throws {
let document = try Sample().document(name: .artboard, testCase: self)

document.addFrame(with: Image(), origin: .zero)
document.addFrame(with: Image(), name: "Sample", origin: .zero)

XCTAssertNil(document.frameWrappers[0][FolderName.quickLook], "quickLook is invalidated")
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ final class DocumentPresenterTests_Inserting: XCTestCase {

private func addFrameWithElement() throws {
sut.disableUndoRegistration()
sut.add(image: Sample().image3x(), origin: .zero)
sut.add(image: Sample().image3x(), name: "Sample", origin: .zero)
sut.append(control: A11yDescription.testMake(frame: CGRect(x: 0, y: 0, width: 20, height: 20)))
sut.enableUndoRegistration()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ class DocumentPresenterTests_Movement: XCTestCase {
artboard = document.artboard

sut.disableUndoRegistration()
sut.add(image: Sample().image3x(), origin: .zero)
sut.add(image: Sample().image3x(), name: "Test Frame", origin: .zero)

frame = try XCTUnwrap(artboard.frames.first)
frame.label = "Frame"
Expand Down Expand Up @@ -413,7 +413,7 @@ Container:
}

func test_canNotInsertFrameInFrame() throws {
sut.add(image: Sample().image3x(), origin: .zero)
sut.add(image: Sample().image3x(), name: "Sample", origin: .zero)
let frame2 = try XCTUnwrap(artboard.frames.last)
frame2.label = "Frame 2"

Expand Down
16 changes: 13 additions & 3 deletions VoiceOver Designer/Documents/WindowManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -38,14 +38,24 @@ class WindowManager: NSObject {
private var projectController: ProjectController?

func start() {
print("Start")

// This method is called from applicationDidFinishLaunching
// This place is too early to restore any previous opened windows
// As a result we had to slightly wait to check what is restored
// In there in no other windows – show recent or new
//
// It can be fixed by finding correct place. Maybe NSApplicationDidFinishRestoringWindowsNotification will help?
DispatchQueue.main.async {
self.showRecentIfNeeded()
}
}

private func showRecentIfNeeded() {
if newDocumentIsCreated {
// Document has been created from [NSDocumentController openUntitledDocumentAndDisplay:error:]
return
}
if documentsPresenter.shouldShowThisController {
self.showRecent()
showRecent()
} else {
// TODO: Do we need it or document will open automatically?
showNewDocument()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB" version="3.0" toolsVersion="22155" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" initialViewController="LVp-Pr-lAJ">
<document type="com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB" version="3.0" toolsVersion="22154" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" initialViewController="LVp-Pr-lAJ">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="22155"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="22154"/>
<capability name="NSView safe area layout guides" minToolsVersion="12.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
Expand All @@ -14,7 +14,7 @@
<view key="view" translatesAutoresizingMaskIntoConstraints="NO" id="ndy-hA-WOO" customClass="CanvasView" customModule="CanvasAppKit">
<rect key="frame" x="0.0" y="0.0" width="800" height="906"/>
<subviews>
<scrollView borderType="none" horizontalLineScroll="10" horizontalPageScroll="10" verticalLineScroll="10" verticalPageScroll="10" hasHorizontalScroller="NO" hasVerticalScroller="NO" allowsMagnification="YES" maxMagnification="6" minMagnification="0.01" usesPredominantAxisScrolling="NO" translatesAutoresizingMaskIntoConstraints="NO" id="JHg-wh-ieX" customClass="CanvasScrollView" customModule="CanvasAppKit">
<scrollView borderType="none" horizontalLineScroll="10" horizontalPageScroll="10" verticalLineScroll="10" verticalPageScroll="10" hasHorizontalScroller="NO" hasVerticalScroller="NO" allowsMagnification="YES" maxMagnification="6" minMagnification="0.10000000000000001" usesPredominantAxisScrolling="NO" translatesAutoresizingMaskIntoConstraints="NO" id="JHg-wh-ieX" customClass="CanvasScrollView" customModule="CanvasAppKit">
<rect key="frame" x="0.0" y="40" width="800" height="814"/>
<clipView key="contentView" drawsBackground="NO" id="lki-H1-X1O" customClass="CenteredClipView" customModule="CanvasAppKit">
<rect key="frame" x="0.0" y="0.0" width="800" height="814"/>
Expand Down Expand Up @@ -138,7 +138,6 @@
<viewLayoutGuide key="layoutMargins" id="QBA-NU-qhe"/>
<connections>
<outlet property="clipView" destination="lki-H1-X1O" id="yfA-nS-Iqj"/>
<outlet property="contentView" destination="JlK-Oy-LgO" id="6NV-jp-qGf"/>
<outlet property="dragnDropView" destination="dHk-Q5-FAZ" id="9Yb-dX-lbU"/>
<outlet property="footer" destination="xXt-RS-DEx" id="CUu-m3-y9O"/>
<outlet property="scrollView" destination="JHg-wh-ieX" id="oB8-ia-UPa"/>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,40 @@
import AppKit
import CommonUI
import Canvas

protocol ScrollViewZoomDelegate: AnyObject {
func didUpdateScale(_ magnification: CGFloat)
}

class CanvasScrollView: NSScrollView {

weak var hud: HUDLayer?
weak var delegate: ScrollViewZoomDelegate?

func documentView() -> ContentView {
documentView! as! ContentView
}

override func awakeFromNib() {
super.awakeFromNib()

verticalScrollElasticity = .none
horizontalScrollElasticity = .none

documentView().wantsLayer = true
documentView().addHUD()
}

// MARK: - Zoom actions

// Touch pad zooming is implemented at CanvasView

public override func scrollWheel(with event: NSEvent) {
let isCommandPressed = event.modifierFlags.contains(.command)

guard isCommandPressed else {
let isTrackpad = !event.hasPreciseScrollingDeltas
let canScroll = isTrackpad || isCommandPressed // Command changes mouse behaviour from pan to scroll

guard canScroll else {
super.scrollWheel(with: event)
return
}
Expand All @@ -22,13 +48,103 @@ class CanvasScrollView: NSScrollView {
}
}

override func setMagnification(_ magnification: CGFloat, centeredAt point: NSPoint) {
super.setMagnification(magnification, centeredAt: point)
public override func smartMagnify(with event: NSEvent) {
let artboardCoordinateTouch = event.location(in: documentView())

let frame = documentView().frames.first { frameLayer in
frameLayer.frame.contains(artboardCoordinateTouch)
}

updateHud(to: magnification)
guard let frame else {
fitToWindow(animated: true)
return
}

guard !isNear(toFit: frame.frame) else {
fitToWindow(animated: true)
return
}

fit(to: frame.frame,
animated: true)
}

func updateHud(to magnification: CGFloat) {
hud?.scale = 1 / magnification
// MARK: Delegates

// Update after any change of position or magnification
override func reflectScrolledClipView(_ cView: NSClipView) {
super.reflectScrolledClipView(cView)

delegate?.didUpdateScale(magnification)
}

// MARK: Custom zooming
func fittingMagnification(bounds: CGRect) -> CGFloat {
let fittingMagnification = (frame.size / bounds.size).min()

let limited = (minMagnification...maxMagnification).trim(fittingMagnification)

return limited
}

private var fittingMagnification: CGFloat {
let contentBounds = documentView().boundingBox

return fittingMagnification(bounds: contentBounds)
}

func fitToWindowIfAlreadyFitted() {
if imageMagnificationFitsToWindow {
fitToWindow(animated: false)
}
}

func fitToWindow(animated: Bool) {
let contentBounds = documentView().boundingBox

fit(to: contentBounds, animated: animated)
}

func changeMagnification(_ change: (_ current: CGFloat) -> CGFloat) {
let changed = change(magnification)
setMagnification(to: changed, animated: false)
}

private var imageMagnificationFitsToWindow: Bool {
abs(fittingMagnification - magnification) < 0.01
}

private func setMagnification(
to magnification: CGFloat,
center: CGPoint? = nil,
animated: Bool
) {
let center = center
?? documentView().hud.selectedControlFrame?.center
?? documentView().frame.center

(animated ? animator() : self).setMagnification(
magnification,
centeredAt: center)
}

private func isNear(toFit bounds: CGRect) -> Bool {
let fittingMagnification = fittingMagnification(bounds: bounds)

let isNear = abs(magnification - fittingMagnification) < 0.1
return isNear
}

private func fit(to bounds: CGRect, animated: Bool) {
animator().magnify(toFit: bounds)
}
}

extension ClosedRange where Bound == CGFloat {
fileprivate func trim(_ value: Bound) -> Bound {
Swift.max(
lowerBound,
Swift.min(upperBound, value)
)
}
}
Loading

0 comments on commit 04cc125

Please sign in to comment.