The Third week of GSoC 2018. ended. It's just another ~1 week before the first phase ends, which means deadlines are just around the corner.

I've started by refreshing my knowledge of Objective-C by creating some simple apps. After that, I dived into class categories, a feature of the language that I had never met before. Ivan provided me with a simple introductory task that required to add some functionality to NSString. This was important because we agreed that the work I'll be doing was going to be implemented using class categories. This enables the "old" code (the one not using Opal as back-end) to work, while making changes to the codebase. All in all, class categories allow for a neat way of adding new features without breaking backwards compatibility.

After that, I started the actual work.I modified the Headers/AppKit/NSView.h file in the libs-gui by adding a variable id _coreAnimationData . The plan is to eventually implement a bunch of CoreAnimation related methods on NSView (eg. setWantsLayer). In order to do that without breaking the current way GNUstep works, all of the work will be done in a Class Category of NSView. But we do need to store some information about the NSView instance. To minimise the impact on ABI, we added the _coreAnimationData pointer to a instance of our custom class that will store the data such as BOOL _wantsLayer, CALayer * _layer, etc.


After I finished the minimal version of what I just explained, I created a directory for my project inside the libs-quartzcore : libs-quartzcore/CAAppKitBridge and placed all the files there. It took me some time to "glue" everything together. I created a GNUmakefile that builds everything and installs it as a framework called CAAppKitBridge.framework. I've also writted a simple demo in the libs-quartzcore/CAAppKitBridge/Demo (and wrote its GNUmakefile) to test that everything builds and links correctly. Since we are, once again, implementing this as a class category, the linking in the GNUmakefile is important becase we need to add our additional flag - lCAAppKitBridge.

This is how our approach basically works. Every NSView instance has _coreAnimationData ivar. When we compile things and link them with our library, we can call [nsviewinstance setWantsLayer:YES]. What happens is that an instance of CAData class is created, its ivar named _wantsLayer is set to YES,and the newly created instance of CAData bound to _coreAnimationData on the NSView instance. CAData is our custom class that contains relevant data about the NSView instance, such as BOOL _wantsLayer, CALayer * _layer.

Finally, this minimal skeleton of my project works! Yay! It's time to start the more serious development.


Next problem was to propagate the CA related stuff down the tree. Lets say we have the following NSView tree:

view1
└── view2
    └── view3
        ├── view4
        │   └── view5
        └── view4.5

And let's say we call [view2 setWantsLayer:YES]. We need to bind each views CALayer (_layer) to its parent CALayer (by calling addSublayer). On this example, view3_layer is sublayer of view2_layer, view4_layer and view4.5_layer are sublayers of view3_layer, etc.

I implemented this behaviour by using recursive calls. When you call setWantsLayer:YES on a NSView instance, it sets _wantsLayer to YES, then it creates a new CALayer (_layer), adds it as a sublayer to its parent. It then calls the method that does the same thing to all the subviews of the original receiver, which then calls itself again on the children of the children, and so on. One thing that wasn't intuitive to me was that the original Apple behaviour doesn't set wantsLayer to YES on all the children. In our example, only view2 will return YES from getWantsLayer.

That's done. The only problem that remains is to handle the user-made changes to the NSView tree after setWantsLayer:YES was called. The ideal solution would be to somehow implement callbacks that would fire when user changes the NSView tree. But I'm not sure if that is doable. The alternative would be to periodically check for changes on the tree. I'm thinking of implementing Blum-Kannan for that purpose. But that is a complex task, and since the "changing tree problem" is not on the top of my list, I'll leave that for later.


The next problem was to make our class category of NSView "compatible" with CARenderer. CARenderer is usually paired with an instance NSOpenGLView, which handles all the OpenGL "stuff" (ie contexts). I implemented all of the methods from NSOpenGLView on our class category,so that they can effectively be called on instances of NSView.

This means that we can substitute NSOpenGLView-s from the CA with our NSView-s.

In the end, I implemented GNUstep-specific addCARenderer and removeCARenderer methods.

Till next week,
Stjepan