dimanche 12 mai 2019

Implement Composite-Pattern in Swift to group drawings (NSRect)

I want to implement to composite pattern in my simple drawing program written in Swift for macOS.

The goal is to be able to group figures in order to handle (move, etc.) them as being one figure.

I have already implemented the DrawingComposite class that inherits from the Drawing class. I am currently stuck on which method I need to implement in order to be able to group figures and move them as one.


import Cocoa
import Foundation


class Drawing: NSView {
    var x = 0
    var selected = false
    var shouldMove = false
    var anchorPoint: NSPoint!
    var nframe: NSRect
    var shouldDrawRectangle: Bool
    var view: NSView
    let colorSelection = NSColorPanel.shared.color.cgColor
    var firstMouseDownPoint: NSPoint = NSZeroPoint

    init(frame: NSRect, positions _: NSRect, drawRect: Bool, view: NSView) {
        self.view = view
        nframe = frame
        shouldDrawRectangle = drawRect
        super.init(frame: nframe)
        self.view.addSubview(self)
        arrayOfRect.append(frame)
        arrayOfType.append(drawRect)
    }

    required init?(coder _: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override func draw(_ dirtyRect: NSRect) {
        super.draw(dirtyRect)
        let context = NSGraphicsContext.current!.cgContext
        let border = NSBezierPath(rect: dirtyRect)
        var color = NSColor()
        context.saveGState()
        context.setFillColor(colorSelection)
        context.fillEllipse(in: dirtyRect)

        if shouldDrawRectangle {
            context.addRect(dirtyRect)
            context.drawPath(using: .fillStroke)
        }

        if selected {
            color = NSColor.black
            border.lineWidth = CGFloat(1)
            color.setStroke()
            border.stroke()
        }

        context.restoreGState()
    }

    // Allow view to receive keypress (remove the purr sound)
    override var acceptsFirstResponder: Bool {
        return true
    }

    override func mouseDown(with theEvent: NSEvent) {
        // get coordinates
        let pos = theEvent.locationInWindow
        // Check if inside the rect
        if pos.x >= nframe.origin.x, pos.x <= nframe.origin.x + nframe.size.width {
            // X match, now check Y
            if pos.y >= nframe.origin.y, pos.y <= nframe.origin.y + nframe.size.height {
                // If we get here, then we're insisde the rect!
                shouldMove = true
                selected = true
                display()

                updateValues(nframe: nframe)

                // OPTIONAL : Set an anchor point
                // self.anchorPoint = NSMakePoint(pos.x - self.nframe.origin.x, pos.y - self.nframe.origin.y);
                firstMouseDownPoint = (window?.contentView?.convert(theEvent.locationInWindow, to: self))!
            }
        }

    }

    override func mouseUp(with _: NSEvent) {
        if shouldMove {
            selected = false
            shouldMove = false
            display()
        }
    }


    @objc func moveShape(offset: NSPoint, d: Drawing, index: Int) {
        (undoManager?.prepare(withInvocationTarget: self) as AnyObject).unmoveShape(offset: offset, d: d, index: index)
        undoManager?.setActionName("Move Shape")
        print("Move Shape")
        ViewController.MoveShape(offset: offset, drawing: d, index: index).execute()
    }

    @objc func unmoveShape(offset: NSPoint, d: Drawing, index: Int) {
        (undoManager?.prepare(withInvocationTarget: self) as AnyObject).moveShape(offset: offset, d: d, index: index)
        undoManager?.setActionName("Unmove Shape")
        print("Unmove Shape")
        ViewController.MoveShape(offset: offset, drawing: d, index: index).unexecute()
    }

    override func mouseDragged(with theEvent: NSEvent) {
        if shouldMove {
            let newPoint = (window?.contentView?.convert(theEvent.locationInWindow, to: self))!
            for _ in arrayOfRect {
                if let index = arrayOfRect.firstIndex(of: self.frame) {
                    let offset = NSPoint(x: newPoint.x - firstMouseDownPoint.x, y: newPoint.y - firstMouseDownPoint.y)
                    moveShape(offset: offset, d: self, index: index)
                    break
                }
            }

            // Redraw the view
            display()
        }
    }

    private func rectsBeingDrawn() -> [NSRect] {
        var rectsPtr: UnsafePointer<NSRect>?
        var count: Int = 0
        getRectsBeingDrawn(&rectsPtr, count: &count)
        return Array(UnsafeBufferPointer(start: rectsPtr, count: count))
    }
}

class DrawingComposite : Drawing{
    private let drawings:[Drawing]

    init(frame: NSRect, positions _: NSRect, drawRect: Bool, view: NSView, drawings: Drawing) {
        self.drawings = drawings
        super.init(frame: frame, positions: positions, drawRect: drawRect, view:view)
    }

    /* implement some method to group figures */

}

class ViewController: NSViewController {
    var brect = true
    var startPoint: NSPoint?
    var endPoint: NSPoint?
    var URL: URL?
    lazy var window: NSWindow = self.view.window!
    var mouseLocation: NSPoint {
        return NSEvent.mouseLocation
    }

    var location: NSPoint {
        return window.mouseLocationOutsideOfEventStream
    }

    override func mouseDown(with event: NSEvent) {
        startPoint = event.locationInWindow
        positionXYMouse.stringValue = NSStringFromPoint(startPoint ?? NSMakePoint(0, 0))
    }

    override func mouseUp(with event: NSEvent) {
        // TODO: fix for drawing rectangles leftwards and upwards.
        endPoint = event.locationInWindow

        let positions = NSMakeRect(0, 0, 0, 0)

        addShape(x: x, y: y, w: w, h: h, positions: positions, drawRect: brect, view: view)

        positionXYMouse.stringValue = NSStringFromPoint(endPoint ?? NSMakePoint(0, 0))
    }

    class MoveShape: Command {
        let offset: NSPoint
        let drawing: Drawing
        let index: Int
        init(offset: NSPoint, drawing: Drawing, index: Int) {
            self.offset = offset
            self.drawing = drawing
            self.index = index
        }

        func execute() {

            drawing.frame.origin = NSMakePoint(drawing.frame.origin.x + offset.x, drawing.frame.origin.y + offset.y)
            drawing.nframe.origin = NSMakePoint(drawing.nframe.origin.x + offset.x, drawing.nframe.origin.y + offset.y)

            arrayOfRect[self.index] = drawing.nframe
        }

        func unexecute() {

            drawing.frame.origin = NSMakePoint(drawing.frame.origin.x - offset.x, drawing.frame.origin.y - offset.y)
            drawing.nframe.origin = NSMakePoint(drawing.nframe.origin.x - offset.x, drawing.nframe.origin.y - offset.y)

            arrayOfRect[self.index] = drawing.nframe
        }
    }

    override func viewDidLoad() {
        super.viewDidLoad()

    }

    override var representedObject: Any? {
        didSet {
            // Update the view, if already loaded.
        }
    }
}




I expect that when properly implemented, I should be able to move multiple figures/drawings as one.

Aucun commentaire:

Enregistrer un commentaire