mirror of
https://github.com/dscyrescotti/Memola.git
synced 2026-05-10 01:43:50 +02:00
feat: replace array with rtree
This commit is contained in:
@@ -11,7 +11,7 @@ import CoreData
|
||||
import Foundation
|
||||
|
||||
final class GraphicContext: @unchecked Sendable {
|
||||
var strokes: [any Stroke] = []
|
||||
var tree: RTree = RTree<PenStroke>(maxEntries: 8)
|
||||
var object: GraphicContextObject?
|
||||
|
||||
var currentStroke: (any Stroke)?
|
||||
@@ -37,20 +37,26 @@ final class GraphicContext: @unchecked Sendable {
|
||||
]
|
||||
}
|
||||
|
||||
func undoGraphic() {
|
||||
guard !strokes.isEmpty else { return }
|
||||
let stroke = strokes.removeLast()
|
||||
withPersistence(\.backgroundContext) { [stroke] context in
|
||||
stroke.stroke(as: PenStroke.self)?.object?.graphicContext = nil
|
||||
try context.saveIfNeeded()
|
||||
func undoGraphic(for event: HistoryEvent) {
|
||||
switch event {
|
||||
case .stroke(let stroke):
|
||||
guard let _stroke = stroke.stroke(as: PenStroke.self) else { return }
|
||||
let deletedStroke = tree.remove(_stroke, in: _stroke.strokeBox)
|
||||
withPersistence(\.backgroundContext) { [stroke = deletedStroke] context in
|
||||
stroke?.stroke(as: PenStroke.self)?.object?.graphicContext = nil
|
||||
try context.saveIfNeeded()
|
||||
}
|
||||
previousStroke = nil
|
||||
}
|
||||
previousStroke = nil
|
||||
|
||||
}
|
||||
|
||||
func redoGraphic(for event: HistoryEvent) {
|
||||
switch event {
|
||||
case .stroke(let stroke):
|
||||
strokes.append(stroke)
|
||||
if let stroke = stroke.stroke(as: PenStroke.self) {
|
||||
tree.insert(stroke, in: stroke.strokeBox)
|
||||
}
|
||||
withPersistence(\.backgroundContext) { [weak self, stroke] context in
|
||||
stroke.stroke(as: PenStroke.self)?.object?.graphicContext = self?.object
|
||||
try context.saveIfNeeded()
|
||||
@@ -65,9 +71,10 @@ extension GraphicContext {
|
||||
guard let object else { return }
|
||||
let queue = OperationQueue()
|
||||
queue.qualityOfService = .userInteractive
|
||||
self.strokes = object.strokes.compactMap { stroke -> PenStroke? in
|
||||
guard let stroke = stroke as? StrokeObject else { return nil }
|
||||
object.strokes.forEach { stroke in
|
||||
guard let stroke = stroke as? StrokeObject else { return }
|
||||
let _stroke = PenStroke(object: stroke)
|
||||
tree.insert(_stroke, in: _stroke.strokeBox)
|
||||
if _stroke.isVisible(in: bounds) {
|
||||
let id = stroke.objectID
|
||||
queue.addOperation {
|
||||
@@ -85,14 +92,13 @@ extension GraphicContext {
|
||||
context.refresh(stroke, mergeChanges: false)
|
||||
}
|
||||
}
|
||||
return _stroke
|
||||
}
|
||||
queue.waitUntilAllOperationsAreFinished()
|
||||
}
|
||||
|
||||
func loadQuads(_ bounds: CGRect) {
|
||||
for stroke in self.strokes {
|
||||
guard stroke.isVisible(in: bounds), stroke.isEmpty else { continue }
|
||||
for stroke in self.tree.search(box: bounds.box) {
|
||||
guard stroke.isEmpty else { continue }
|
||||
stroke.stroke(as: PenStroke.self)?.loadQuads()
|
||||
}
|
||||
}
|
||||
@@ -135,7 +141,6 @@ extension GraphicContext {
|
||||
graphicContext?.strokes.add(stroke)
|
||||
_stroke.object = stroke
|
||||
}
|
||||
strokes.append(stroke)
|
||||
currentStroke = stroke
|
||||
currentPoint = point
|
||||
currentStroke?.begin(at: point)
|
||||
@@ -152,8 +157,9 @@ extension GraphicContext {
|
||||
}
|
||||
|
||||
func endStroke(at point: CGPoint) {
|
||||
guard currentPoint != nil, let currentStroke else { return }
|
||||
guard currentPoint != nil, let currentStroke = currentStroke?.stroke(as: PenStroke.self) else { return }
|
||||
currentStroke.finish(at: point)
|
||||
tree.insert(currentStroke, in: currentStroke.strokeBox)
|
||||
withPersistence(\.backgroundContext) { [currentStroke] context in
|
||||
guard let stroke = currentStroke.stroke(as: PenStroke.self) else { return }
|
||||
stroke.object?.bounds = stroke.bounds
|
||||
@@ -168,10 +174,10 @@ extension GraphicContext {
|
||||
}
|
||||
|
||||
func cancelStroke() {
|
||||
if !strokes.isEmpty {
|
||||
let stroke = strokes.removeLast()
|
||||
withPersistence(\.backgroundContext) { [graphicContext = object, _stroke = stroke] context in
|
||||
if let stroke = _stroke.stroke(as: PenStroke.self)?.object {
|
||||
if !tree.isEmpty, let stroke = currentStroke?.stroke(as: PenStroke.self) {
|
||||
let _stroke = tree.remove(stroke, in: stroke.strokeBox)
|
||||
withPersistence(\.backgroundContext) { [graphicContext = object, _stroke] context in
|
||||
if let stroke = _stroke?.stroke(as: PenStroke.self)?.object {
|
||||
graphicContext?.strokes.remove(stroke)
|
||||
context.delete(stroke)
|
||||
}
|
||||
|
||||
@@ -123,10 +123,6 @@ extension Canvas {
|
||||
func setGraphicRenderType(_ renderType: GraphicContext.RenderType) {
|
||||
graphicContext.renderType = renderType
|
||||
}
|
||||
|
||||
func getNewlyAddedStroke() -> (any Stroke)? {
|
||||
graphicContext.strokes.last
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Rendering
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
import MetalKit
|
||||
import Foundation
|
||||
|
||||
protocol Stroke: AnyObject, Drawable, Hashable, Equatable {
|
||||
protocol Stroke: AnyObject, Drawable, Hashable, Equatable, Comparable {
|
||||
var id: UUID { get set }
|
||||
var bounds: [CGFloat] { get set }
|
||||
var color: [CGFloat] { get set }
|
||||
@@ -43,6 +43,10 @@ extension Stroke {
|
||||
return CGRect(x: x, y: y, width: width, height: height)
|
||||
}
|
||||
|
||||
var strokeBox: Box {
|
||||
Box(minX: bounds[0], minY: bounds[1], maxX: bounds[2], maxY: bounds[3])
|
||||
}
|
||||
|
||||
func isVisible(in bounds: CGRect) -> Bool {
|
||||
bounds.contains(strokeBounds) || bounds.intersects(strokeBounds)
|
||||
}
|
||||
@@ -107,6 +111,10 @@ extension Stroke {
|
||||
func hash(into hasher: inout Hasher) {
|
||||
hasher.combine(id)
|
||||
}
|
||||
|
||||
static func < (lhs: Self, rhs: Self) -> Bool {
|
||||
lhs.createdAt < rhs.createdAt
|
||||
}
|
||||
}
|
||||
|
||||
extension Stroke {
|
||||
|
||||
@@ -23,12 +23,12 @@ class History: ObservableObject {
|
||||
redoStack.isEmpty
|
||||
}
|
||||
|
||||
func undo() -> Bool {
|
||||
func undo() -> HistoryEvent? {
|
||||
guard let event = undoStack.popLast() else {
|
||||
return false
|
||||
return nil
|
||||
}
|
||||
addRedo(event)
|
||||
return true
|
||||
return event
|
||||
}
|
||||
|
||||
func redo() -> HistoryEvent? {
|
||||
|
||||
@@ -18,6 +18,10 @@ class RTree<T> where T: Equatable & Comparable {
|
||||
self.root = Node<T>.createNode()
|
||||
}
|
||||
|
||||
var isEmpty: Bool {
|
||||
root.children.isEmpty
|
||||
}
|
||||
|
||||
// MARK: - Retrival
|
||||
func traverse() -> [T] {
|
||||
_traverse(from: root)
|
||||
@@ -40,7 +44,7 @@ class RTree<T> where T: Equatable & Comparable {
|
||||
return result
|
||||
}
|
||||
|
||||
func _merge(_ left: [T], _ right: [T]) -> [T] {
|
||||
private func _merge(_ left: [T], _ right: [T]) -> [T] {
|
||||
var mergedArray: [T] = []
|
||||
var leftIndex = 0
|
||||
var rightIndex = 0
|
||||
|
||||
@@ -45,7 +45,9 @@ class GraphicRenderPass: RenderPass {
|
||||
let graphicContext = canvas.graphicContext
|
||||
if renderer.redrawsGraphicRender {
|
||||
canvas.setGraphicRenderType(.finished)
|
||||
for stroke in graphicContext.strokes {
|
||||
let strokes = graphicContext.tree.search(box: canvas.bounds.box)
|
||||
print(strokes.count)
|
||||
for stroke in strokes {
|
||||
if graphicContext.previousStroke === stroke || graphicContext.currentStroke === stroke {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -141,8 +141,8 @@ extension CanvasViewController {
|
||||
|
||||
func updateDocumentBounds() {
|
||||
var bounds = scrollView.bounds.muliply(by: drawingView.ratio / scrollView.zoomScale)
|
||||
let xDelta = bounds.minX * 0.05
|
||||
let yDelta = bounds.minY * 0.05
|
||||
let xDelta = bounds.minX * 0.0
|
||||
let yDelta = bounds.minY * 0.0
|
||||
bounds.origin.x -= xDelta
|
||||
bounds.origin.y -= yDelta
|
||||
bounds.size.width += xDelta * 2
|
||||
@@ -323,9 +323,9 @@ extension CanvasViewController {
|
||||
|
||||
extension CanvasViewController {
|
||||
func historyUndid() {
|
||||
guard history.undo() else { return }
|
||||
guard let event = history.undo() else { return }
|
||||
drawingView.disableUserInteraction()
|
||||
canvas.graphicContext.undoGraphic()
|
||||
canvas.graphicContext.undoGraphic(for: event)
|
||||
renderer.redrawsGraphicRender = true
|
||||
renderer.resize(on: renderView, to: renderView.drawableSize)
|
||||
renderView.draw()
|
||||
|
||||
@@ -20,4 +20,8 @@ extension CGRect {
|
||||
func muliply(by factor: CGFloat) -> CGRect {
|
||||
CGRect(origin: origin.muliply(by: factor), size: size.multiply(by: factor))
|
||||
}
|
||||
|
||||
var box: Box {
|
||||
Box(minX: minX, minY: minY, maxX: maxX, maxY: maxY)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user