Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Drag'n'Drop for containers #147

Merged
merged 4 commits into from
Dec 4, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import Foundation

extension Array where Element == any AccessibilityView {

@discardableResult
public mutating func wrapInContainer(
_ items: [A11yDescription],
label: String
) {
guard items.count > 0 else { return }
) -> A11yContainer? {
guard items.count > 0 else { return nil }

var extractedElements = [A11yDescription]()

Expand All @@ -30,6 +32,8 @@ extension Array where Element == any AccessibilityView {
insert(container, at: insertIndex ?? 0)

removeEmptyContainers()

return container
}

/// - Returns: Element index
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,31 @@
{
"object": {
"pins": [
{
"package": "swift-custom-dump",
"repositoryURL": "[email protected]:pointfreeco/swift-custom-dump.git",
"state": {
"branch": null,
"revision": "ead7d30cc224c3642c150b546f4f1080d1c411a8",
"version": "0.6.1"
}
},
{
"package": "swift-snapshot-testing",
"repositoryURL": "[email protected]:pointfreeco/swift-snapshot-testing.git",
"state": {
"branch": "main",
"revision": "ad2c83170e82954d9504e4db205c43a3f493bc55",
"version": null
"branch": null,
"revision": "f29e2014f6230cf7d5138fc899da51c7f513d467",
"version": "1.10.0"
}
},
{
"package": "xctest-dynamic-overlay",
"repositoryURL": "https://github.com/pointfreeco/xctest-dynamic-overlay",
"state": {
"branch": null,
"revision": "5a5457a744239896e9b0b03a8e1a5069c3e7b91f",
"version": "0.6.0"
}
}
]
Expand Down
5 changes: 4 additions & 1 deletion VoiceOver Designer/Features/Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,10 @@ let package = Package(
dependencies: [
.package(
url: "[email protected]:pointfreeco/swift-snapshot-testing.git",
branch: "main"
.upToNextMajor(from: "1.10.0")
),
.package(url: "[email protected]:pointfreeco/swift-custom-dump.git",
.upToNextMajor(from: "0.6.1")),
.package(name: "Shared", path: "./../../Shared")
],
targets: [
Expand Down Expand Up @@ -99,6 +101,7 @@ let package = Package(
name: "TextUITests",
dependencies: [
"TextUI",
.productItem(name: "CustomDump", package: "swift-custom-dump"),
drucelweisse marked this conversation as resolved.
Show resolved Hide resolved
.product(name: "Document", package: "Shared"),
]
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
<clipView key="contentView" ambiguous="YES" drawsBackground="NO" translatesAutoresizingMaskIntoConstraints="NO" id="RRR-wz-1X7">
<rect key="frame" x="0.0" y="0.0" width="390" height="902"/>
<subviews>
<stackView distribution="fill" orientation="vertical" alignment="leading" horizontalStackHuggingPriority="249.99998474121094" verticalStackHuggingPriority="249.99998474121094" verticalHuggingPriority="251" ambiguous="YES" detachesHiddenViews="YES" translatesAutoresizingMaskIntoConstraints="NO" id="LGK-Fp-or4" customClass="FlippedStackView" customModule="Settings">
<stackView distribution="fill" orientation="vertical" alignment="leading" horizontalStackHuggingPriority="249.99998474121094" verticalStackHuggingPriority="249.99998474121094" verticalHuggingPriority="251" ambiguous="YES" detachesHiddenViews="YES" translatesAutoresizingMaskIntoConstraints="NO" id="LGK-Fp-or4" customClass="FlippedStackView" customModule="Canvas">
<rect key="frame" x="0.0" y="192" width="405" height="710"/>
<subviews>
<customView ambiguous="YES" translatesAutoresizingMaskIntoConstraints="NO" id="7lc-fy-0gJ">
Expand Down
76 changes: 76 additions & 0 deletions VoiceOver Designer/Features/Sources/TextUI/Array+Move.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,79 @@ extension Array where Element == any AccessibilityView {
return true
}
}

// TODO: Remove duplication of functions
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know how to merge both variant, extension Array where Element == any AccessibilityView doesn't work as expected

extension Array where Element == A11yDescription {
/// - Returns: From and To indexes
@discardableResult
mutating func move(_ element: Element, to: Int) -> Bool {
guard let from = firstIndex(where: { control in
control === element
}) else { return false }

if to == from + 1 { // Can't move items after themselves
return false
}

if to == from { // Can't move to same position
return false
}

remove(at: from)
if to > from {
insert(element, at: to - 1)
} else {
insert(element, at: to)
}
return true
}
}

extension Array where Element == any AccessibilityView {
/// - Returns: From and To indexes
mutating func move(
_ element: A11yDescription, fromContainer: A11yContainer?,
toIndex: Int, toContainer: A11yContainer?
) {
if fromContainer == toContainer {
if let fromContainer {
fromContainer.elements.move(element,
to: toIndex)
} else {
move(element, to: toIndex)
}
}

if let toContainer {
// Insert in container
toContainer.elements.insert(element, at: toIndex)
} else {
insert(element, at: toIndex)
}

if let fromContainer {
fromContainer.elements.remove(element)
} else {
remove(element)
}
}

mutating func remove(_ element: Element) {
if let from = firstIndex(where: { control in
control === element
}) {
remove(at: from)
}
}
}

extension Array where Element == A11yDescription {
mutating func remove(_ element: Element) {
if let from = firstIndex(where: { control in
control === element
}) {
remove(at: from)
}
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ extension TextRepresentationController {
}

public func outlineView(_ outlineView: NSOutlineView, draggingSession session: NSDraggingSession, willBeginAt screenPoint: NSPoint, forItems draggedItems: [Any]) {
draggedNode = draggedItems[0] as? A11yDescription
draggedNode = draggedItems[0] as? any AccessibilityView
session.draggingPasteboard.setData(Data(), forType: REORDER_PASTEBOARD_TYPE)
}

Expand All @@ -32,14 +32,25 @@ extension TextRepresentationController {
}

public func outlineView(_ outlineView: NSOutlineView, acceptDrop info: NSDraggingInfo, item: Any?, childIndex toIndex: Int) -> Bool {
guard toIndex != NSOutlineViewDropOnItemIndex else { return false } // When place over item, check `item` for this case. Will help lately when deal with container

guard document.controls.move(draggedNode!, to: toIndex) else {
print("did not move to \(toIndex)")
if toIndex == NSOutlineViewDropOnItemIndex {
guard let onElement = item as? any AccessibilityView
else {
return false
}

document.controls.wrapInContainer(
[draggedNode!, onElement].extractElements(),
label: "Container")

return false
} else {
guard let element = draggedNode as? A11yDescription else { return false } // TODO: Move containers

let currentParent = outlineView.parent(forItem: draggedNode) as? A11yContainer

document.controls.move(element, fromContainer: currentParent,
toIndex: toIndex, toContainer: item as? A11yContainer)
}

// outlineView.moveItem(at: fromIndex, inParent: nil,
// to: toIndex, inParent: nil)
outlineView.reloadData()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ public class TextRepresentationController: NSViewController {
}

var document: VODesignDocument!
var draggedNode: A11yDescription?
var draggedNode: (any AccessibilityView)?

private var cancellables = Set<AnyCancellable>()

Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,57 @@
import Document
import XCTest
@testable import TextUI
import CustomDump

class A11yDescriptionArrayTests: XCTestCase {

var sut: [any AccessibilityView] = []
var el1: A11yDescription!
var el2: A11yDescription!
var el3: A11yDescription!

override func setUp() {
super.setUp()

el1 = A11yDescription.make(label: "1")
el2 = A11yDescription.make(label: "2")
el3 = A11yDescription.make(label: "3")

sut = [
A11yDescription.make(label: "1"),
A11yDescription.make(label: "2"),
A11yDescription.make(label: "3"),
el1,
el2,
el3,
]
}
}

extension Array where Element == any AccessibilityView {
func assert(
labels: String...,
file: StaticString = #file, line: UInt = #line
) {
XCTAssertNoDifference(recursiveDescription(),
akaDuality marked this conversation as resolved.
Show resolved Hide resolved
labels,
file: file, line: line)
}

func recursiveDescription() -> [String] {
map { view in
if let container = view as? A11yContainer {
if container.elements.isEmpty {
return container.label
}

let elementsDescription = (container.elements as [any AccessibilityView]).recursiveDescription().joined(separator: ", ")
return "\(container.label): \(elementsDescription)"
} else {
return view.label
}
}
}
}

class A11yDescriptionArrayMoveTests: A11yDescriptionArrayTests {

// MARK: Move first
func test_move0_to0_shouldNotMove() {
Expand Down Expand Up @@ -120,3 +157,68 @@ extension A11yDescription {
)
}
}

class A11yDescriptionArrayMoveContainerTests: A11yDescriptionArrayTests {

func test_simpleMove() {
sut.move(el1, fromContainer: nil, toIndex: 2, toContainer: nil)
sut.assert(labels: "2", "1", "3")
}

// MARK: - Inside containers

func test_correctDescription() {
sut.wrapInContainer([el1], label: "Container1")
sut.assert(labels: "Container1: 1", "2", "3")
}

func test_whenMove2IntoContainer_shouldMoveToContainer() {
let container = sut.wrapInContainer([el1], label: "Container1")

sut.move(el2, fromContainer: nil,
toIndex: 1, toContainer: container!)

sut.assert(labels: "Container1: 1, 2", "3")
}

// MARK: Outside containers

func test_whenMove1OutOfContainer_shouldKeepContainerEmpty() {
let container = sut.wrapInContainer([el1], label: "Container")

sut.move(el1, fromContainer: container,
toIndex: 1, toContainer: nil)

sut.assert(labels: "Container", "1", "2", "3")
}

func test_whenMoveInSameContainer() {
let container = sut.wrapInContainer([el1, el2], label: "Container")

sut.move(el1, fromContainer: container,
toIndex: 2, toContainer: container)

sut.assert(labels: "Container: 2, 1", "3")
}

func test_whenMoveInSameContainerToBeginning() {
let container = sut.wrapInContainer([el2, el1], label: "Container") // TODO: Strange that I should wrap in reverse order
sut.assert(labels: "Container: 1, 2", "3")

sut.move(el2, fromContainer: container,
toIndex: 0, toContainer: container)

sut.assert(labels: "Container: 2, 1", "3")
}

func test_whenMoveFromOneContainerToAnother() {
let container1 = sut.wrapInContainer([el1], label: "Container1")
let container2 = sut.wrapInContainer([el2], label: "Container2")

sut.move(el1, fromContainer: container1,
toIndex: 0, toContainer: container2)

sut.assert(labels: "Container1", "Container2: 1, 2", "3")
}
}