Massive Programming Challenge

As I have mentioned over the past few years (here, and “that other forum”)… One of my pet projects has been writing an application to create iOS and macOS applications in a manner similar to Xojo. I have successfully created a Proof of concept in Xojo the is capable of creating a compilable/operations iOS UI (no methods, just the GUI and events). and also a partial POC doing the same for macOS.

However with my decision to no longer support Xojo in an active manner, I decided to rethink my approach. And have decided to start from ground Zero, and do the entire project in Swift for macOS.

And the macOS enviroment (AppKit/Cocoa) is in some respects VERY different from the same functions for iOS… And I will have to be writing a ton of custom controls.
Of which so far this week I have created a TreeView, an Icon Toolstrip, and a screen splitter. (I spent an entire day trying to get NSSplitView to work, but it is a complicated mess, so I made my own which works quite nicely) :slight_smile:

Just getting started. but here is what I have cobbled together so far… not bad for 3 days work actually :slight_smile:

the TreeView just has “test data” but gives an idea what it will look like

2 Likes

Looks amazing, @DaveS!

I assume the splitter is built into the OS? Xojo doesn’t have one (oddly) so I rolled my own, but not happy with the look of the splitter bar. I would like to have a nice bar with the 3 dots and whatever the equivalent is in Windows (I think just a 3d bar).
TL;DR: Did you create the bar or is it built-in to the OS?

I originally tried to use NSSplitView as I said… but couldn’t get it work… so I rolled my own

//
//  ctrl$SPLITTER.swift
//  macOS_Test
//
//  Created by David Sisemore on 5/23/20.
//  Copyright © 2020 sisemore. All rights reserved.
//
import Cocoa
import Foundation

@objc protocol splitterDelegate {
    func moved(_ splitter:ctrl$Splitter,newPosition:CGFloat)
}

class ctrl$Splitter : NSView { // this is a VERTICAL splitter
    let splitWidth         : CGFloat = 10
    var delegate           : splitterDelegate?
    private var zTrackArea : NSTrackingArea?
    //
    //
    public func updateSIZE(top:CGFloat,height:CGFloat) {
        self.frame=CGRect(x:self.frame.minX,y:top,width:splitWidth,height:height)
        self.needsDisplay     = true
        updateTrackArea()
    }

    public var xPos :CGFloat {
        get { return self.frame.minX }
        set {
            if self.frame.minX != newValue {
                self.frame.origin.x=newValue
                self.delegate?.moved(self, newPosition: newValue)
            }
        }
    }

    //
    //
    required init?(coder: NSCoder) { fatalError("init(coder:) ctrl$Splitter") }
    override var isFlipped : Bool { return true }
    //
    init(frame:NSRect=NSRect.zero,_ index:Int=(-1)) { super.init(frame:frame) }

    override func draw(_ dirtyRect: NSRect) {
        super.draw(dirtyRect)
        let img=NSImage(named: "vSPLIT")
        let dot=NSImage(named: "dSPLIT")
        var r = NSRect(x:0,y:0,width:img!.size.width,height:img!.size.height)
        while r.minY<dirtyRect.maxY {
            img?.draw(in: r)
            r.origin.y += r.size.height
        }
        let x = (dirtyRect.size.width-dot!.size.width)/2
        let y = (dirtyRect.size.height-dot!.size.height)/2
        r = NSRect(x:x,y:y,width:dot!.size.width,height:dot!.size.height)
        dot?.draw(in: r)
    }

    override func mouseDown(with event: NSEvent) {
        // We need to use a mouse-tracking loop as otherwise mouseUp events are not delivered when the mouse button is
        // released outside the view.
        while true {
            guard let nextEvent = self.window?.nextEvent(matching: [.leftMouseUp, .leftMouseDragged]) else { continue }
            switch nextEvent.type {
            case .leftMouseDragged : if nextEvent.deltaX != 0 { self.xPos = (self.frame.origin.x + nextEvent.deltaX ) }
            case .leftMouseUp      : return
            default                : break
            }
        }
    }

    private func updateTrackArea() {
        if zTrackArea != nil { self.removeTrackingArea(zTrackArea!) }
        zTrackArea  = NSTrackingArea.init(rect: self.bounds,
                                          options: [.mouseEnteredAndExited, .mouseMoved, .cursorUpdate,.activeAlways,.enabledDuringMouseDrag],
                                          owner: self,
                                          userInfo: nil)
        self.addTrackingArea(zTrackArea! )
    }

    internal override func cursorUpdate(with event: NSEvent) { NSCursor.resizeLeftRight.set() }
}
1 Like

So vSplit and dSplit are the images of the 3 dots? Sorry - I know nothing of swift; Are those built-in or did you create them?

vSPLIT@1x
vSPLIT@1x
vSPLIT@2x
vSPLIT@2x
dSPLIT@1x
dSPLIT@1x
dSPLIT@2x
dSPLIT@2x

Thanks, Dave. I guess that answers my question - you had to make them yourself.

BTW, the vSPLIT images seem to be just blank squares but the dSPLIT looks good…

yes… vSplit is the “background” of the splitter, dSplit is the “dot”… it is a seperate image, as it has to by dynamiclly placed depending on how tall the scrollbar is.

Someday I may make a horizontal version… but for now I don’t need that

DOH! I thought it was horizontal and vertical dots. Makes sense. Thank you!
Do you mind if I borrow that image for my splitter (it’s not a commercial app)?

Go right ahead…

1 Like