GUI program on Mac OS in D?

Adam D. Ruppe destructionator at gmail.com
Thu Dec 21 15:45:32 UTC 2017


On Thursday, 14 December 2017 at 19:10:26 UTC, mrphobby wrote:
> This looks pretty awesome and very much like something I was 
> looking for. Would really appreciate if you could share your 
> work. Otherwise I'll have to roll up my sleeves and try hacking 
> it on my own :)

oh sorry i forgot to post this sooner here's my code so far.

when i'm reasonably happy with it, it will be part of my 
simpledisplay.d. I might leave it undocumented, but if you wanted 
to dive into my source the final version will be in there 
somewhere.


CODE MODULE

---------
import helper_module;
import core.stdc.math;
import core.stdc.stdio;

void main() {
	auto delegate_ = AppDelegate.alloc.init;
	assert(delegate_, "AppDelegate null");

	NSApp.delegate_ = delegate_;

	NSApp.setActivationPolicy(NSApplicationActivationPolicy.regular);
	NSApp.run();
}

@ObjCParentOverride("NSObject")
extern (Objective-C) interface AppDelegate : 
NSApplicationDelegate {

	mixin IVar!(NSWindow, "window");
	mixin IVar!(ViewController, "controller");

	extern (C)
	@ObjCMethodOverride("applicationShouldTerminateAfterLastWindowClosed:")
	static bool 
applicationShouldTerminateAfterLastWindowClosed(AppDelegate self, 
SEL sel, NSNotification notification) {
		return true;
	}

	extern (C)
	@ObjCMethodOverride("applicationDidFinishLaunching:")
	static void applicationDidFinishLaunching(AppDelegate self, SEL 
sel, NSNotification notification) {
		NSApp.menu = mainMenu();

		immutable style = NSWindowStyleMask.resizable |
			NSWindowStyleMask.closable |
			NSWindowStyleMask.miniaturizable |
			NSWindowStyleMask.titled;

		auto window = NSWindow.alloc.initWithContentRect(
			NSMakeRect(10, 10, 300, 300),
			style,
			NSBackingStoreType.buffered,
			false
		);

		window.title = "D Rox";

		auto controller = ViewController.alloc.init;
		window.contentView = controller.view;

		window.center();

		self.window = window;
		self.controller = controller;

		window.makeKeyAndOrderFront(null);
		NSApp.activateIgnoringOtherApps(true);
	}

	// copy these two lines on any class
	typeof(this) init() @selector("init");
	mixin RegisterObjCClass;
}

extern (Objective-C) interface ViewController : NSViewController {
	extern (C)
	@ObjCMethodOverride("loadView")
	static void loadView(ViewController self, SEL sel) {
		printf("loadView\n");
	}

	extern (C)
	@ObjCMethodOverride("viewDidLoad")
	static void viewDidLoad(ViewController self, SEL sel) {
		printf("viewDidLoad\n");
	}

	ViewController init() @selector("init");
	mixin RegisterObjCClass;
}

NSMenu mainMenu() {
	auto mainMenu = NSMenu.alloc.init("MainMenu".toNSString);

	auto title = "Apple";
	auto menu = NSMenu.alloc.init(title.toNSString);

	auto item = mainMenu.addItem(title.toNSString, null, 
"".toNSString);
	mainMenu.setSubmenu(menu, item);

	menu.addItem(NSMenuItem.alloc.init("Quit", "stop:", "q"));

	return mainMenu;
}

---------

HELPER MODULE:

-----

version(OSX) {
	/* **************************** */
	// Obj-C / Cocoa bindings and helpers
	/* **************************** */

	// Special thanks to Jacob Carlborg
	// see: 
http://forum.dlang.org/thread/qzitebxwvavcfamsluji@forum.dlang.org

	/// Add an instance var to an Obj-C subclass
	mixin template IVar(T, string name) {
		extern(D) final {
			mixin("@IVarAttr T " ~ name ~ "() {
				return this.ivar!(name, T);
			}");

			mixin("void " ~ name ~ "(T v) {
				this.ivar!(name, T) = v;
			}");
		}
	}

	/// Add to your extern(Objective-C) interfaces if you need the 
parent class to be different
	/// from what in inherits from
	struct ObjCParentOverride { string parentClassName; }

	/// Attach to a method like `extern(C) static R 
name(typeof(this) self, SEL sel, Args...)`
	/// to give it a selector to override.
	struct ObjCMethodOverride { string selector; }

	/// And mix this in to your subclasses of the 
extern(Objective-C) interfaces
	mixin template RegisterObjCClass() {
		mixin ClassTrait;

		private static objc_method method(alias imp, string selector, 
const char* type = null)() {
			return objc_method(sel_registerName(selector.ptr), type, 
cast(IMP) &imp);
		}

		shared static this() {
			string name = typeof(this).stringof;
			string parent = "NSObject";
			static if(is(typeof(this) P == super)) {
				parent = P[0].stringof;
			}
			foreach(attr; __traits(getAttributes, typeof(this))) {
				static if(is(typeof(attr) == ObjCParentOverride))
					parent = attr.parentClassName;
			}

			objc_method[] methods;
			objc_ivar[] ivars;

			foreach(member; __traits(derivedMembers, typeof(this))) {
				foreach(attr; __traits(getAttributes, __traits(getMember, 
typeof(this), member))) {
					static if(is(attr == IVarAttr)) {
						static if(is(typeof(__traits(getMember, typeof(this), 
member)) R == return)) {
							ivars ~= objc_ivar(member, "^v", cast(int) log2(R.sizeof), 
R.sizeof);
						}
					} else static if(is(typeof(attr) == ObjCMethodOverride)) {
						enum sel = attr.selector; // weird hack here, passing 
attr.selector directly below caused compile errors :S
						methods ~= method!(__traits(getMember, typeof(this), 
member), sel);
					}
				}
			}
			
			registerClassInternal(name.ptr, parent.ptr, methods, ivars);
		}
	}

	/// leaks memory
	NSString toNSString(string str) {
		return NSString.alloc.initWithBytes(
		cast(immutable(ubyte)*) str.ptr,
		str.length,
		NSStringEncoding.NSUTF8StringEncoding
		);
	}


	/* Rest is private and/or bindings */

	enum IVarAttr;

	void registerClassInternal(const char* name, const char* 
superClassName, objc_method[] methods, objc_ivar[] ivars = []) {
		auto superClass = objc_lookUpClass(superClassName);
		assert(superClass, "Failed to lookup superclass");

		auto cls = objc_allocateClassPair(superClass, name, 0);
		assert(cls, "Failed to allocate class pair");

		foreach (method ; methods) {
			auto result = cls.class_addMethod(
				method.method_name,
				method.method_imp,
				method.method_types
			);

			assert(result, "Failed to add method");
		}

		foreach (objc_ivar ivar ; ivars) {
			auto result = cls.class_addIvar(
				ivar.ivar_name,
				size_t(ivar.space),
				cast(byte) ivar.ivar_offset,
				ivar.ivar_type
			);

			assert(result, "Failed to add instance variable");
		}

		objc_registerClassPair(cls);
	}

	struct IvarInternal(const char* name, T) {
		id self;

		alias value this;

		T value() {
			T value;
			object_getInstanceVariable(self, name, cast(void**) &value);
			return value;
		}

		void opAssign(T value) {
			object_setInstanceVariable(self, name, cast(void*) value);
		}
	}

	auto ivar(const char* name, T, Self)(Self self) {
		return IvarInternal!(name, T)(cast(id) self);
	}

	extern (Objective-C)
	enum NSApplicationActivationPolicy : ptrdiff_t {
		/* The application is an ordinary app that appears in the Dock 
and may have a user interface.  This is the default for bundled 
apps, unless overridden in the Info.plist. */
		regular,

		/* The application does not appear in the Dock and does not 
have a menu bar, but it may be activated programmatically or by 
clicking on one of its windows.  This corresponds to 
LSUIElement=1 in the Info.plist. */
		accessory,

		/* The application does not appear in the Dock and may not 
create windows or be activated.  This corresponds to 
LSBackgroundOnly=1 in the Info.plist.  This is also the default 
for unbundled executables that do not have Info.plists. */
		prohibited
	};

	extern (Objective-C)
	interface NSApplication : NSResponder {
		interface Class {
		mixin MetaclassTrait;
		NSApplication shared_() @selector("sharedApplication");
		}

		extern (D) static NSApplication shared_() {
		return Class.classof.shared_();
		}

		NSApplicationDelegate delegate_() @selector("delegate");
		void delegate_(NSApplicationDelegate) @selector("setDelegate:");

		bool setActivationPolicy(NSApplicationActivationPolicy 
activationPolicy) @selector("setActivationPolicy:");

		// use `int` as workaround for 
https://github.com/ldc-developers/ldc/issues/2387
		void activateIgnoringOtherApps(int flag) 
@selector("activateIgnoringOtherApps:");

		void run() @selector("run");
	}

	extern (Objective-C)
	__gshared NSApplication NSApp_;

	NSApplication NSApp() {
		if(NSApp_ is null)
			NSApp_ = NSApplication.shared_;
		return NSApp_;
	}

	extern (Objective-C)
	interface NSApplicationDelegate {
		void applicationDidFinishLaunching(NSNotification notification) 
@selector("applicationDidFinishLaunching:");
	}

	extern (Objective-C)
	interface NSColor {
		private alias This = typeof(this);

		interface Class {
		mixin MetaclassTrait;

		This alloc() @selector("alloc");

		NSColor redColor() @selector("redColor");
		}

		extern (D) {
		private static Class classof() {
			return Class.classof;
		}

		static This alloc() {
			return classof.alloc();
		}

		static NSColor redColor() {
			return classof.redColor;
		}
		}

		CGColorRef CGColor() @selector("CGColor");
	}

	enum NSBackingStoreType : size_t {
		retained = 0,
		nonretained = 1,
		buffered = 2
	}

	extern (Objective-C)
	interface NSMenu : NSObject {
		mixin ClassTrait;

		NSMenu init() @selector("init");
		NSMenu init(NSString title) @selector("initWithTitle:");

		void setSubmenu(NSMenu menu, NSMenuItem item) 
@selector("setSubmenu:forItem:");
		void addItem(NSMenuItem newItem) @selector("addItem:");

		NSMenuItem addItem(
		NSString title,
		SEL selector,
		NSString charCode
		) @selector("addItemWithTitle:action:keyEquivalent:");
	}

	extern (Objective-C)
	interface NSMenuItem : NSObject {
		mixin ClassTrait;

		NSMenuItem init() @selector("init");

		NSMenuItem init(
		NSString title,
		SEL selector,
		NSString charCode
		) @selector("initWithTitle:action:keyEquivalent:");

		extern (D) final
		{
		NSMenuItem init(string title, const(char)* selector, string 
charCode) {
			return init(title.toNSString, sel_registerName(selector), 
charCode.toNSString);
		}
		}
	}

	extern (Objective-C)
	interface NSResponder : NSObject {
		mixin ClassTrait;

		NSMenu menu() @selector("menu");
		void menu(NSMenu menu) @selector("setMenu:");
	}

	extern (Objective-C)
	interface NSView {
		mixin ClassTrait;

		NSView init() @selector("init");
		NSView initWithFrame(NSRect frameRect) 
@selector("initWithFrame:");

		void addSubview(NSView view) @selector("addSubview:");

		bool wantsLayer() @selector("wantsLayer");
		// use `int` as workaround for 
https://github.com/ldc-developers/ldc/issues/2387
		void wantsLayer(int value) @selector("setWantsLayer:");

		CALayer layer() @selector("layer");
		void uiDelegate(NSObject) @selector("setUIDelegate:");
	}

	extern (Objective-C)
	interface NSViewController : NSObject {
		NSView view() @selector("view");
		void view(NSView view) @selector("setView:");
	}

	extern (Objective-C)
	enum NSWindowStyleMask : size_t {
		borderless = 0,
		titled = 1 << 0,
		closable = 1 << 1,
		miniaturizable = 1 << 2,
		resizable	= 1 << 3,

		/* Specifies a window with textured background. Textured 
windows generally don't draw a top border line under the 
titlebar/toolbar. To get that line, use the 
NSUnifiedTitleAndToolbarWindowMask mask.
		 */
		texturedBackground = 1 << 8,

		/* Specifies a window whose titlebar and toolbar have a unified 
look - that is, a continuous background. Under the titlebar and 
toolbar a horizontal separator line will appear.
		 */
		unifiedTitleAndToolbar = 1 << 12,

		/* When set, the window will appear full screen. This mask is 
automatically toggled when toggleFullScreen: is called.
		 */
		fullScreen = 1 << 14,

		/* If set, the contentView will consume the full size of the 
window; it can be combined with other window style masks, but is 
only respected for windows with a titlebar.
		 Utilizing this mask opts-in to layer-backing. Utilize the 
contentLayoutRect or auto-layout contentLayoutGuide to layout 
views underneath the titlebar/toolbar area.
		 */
		fullSizeContentView = 1 << 15,

		/* The following are only applicable for NSPanel (or a subclass 
thereof)
		 */
		utilityWindow			= 1 << 4,
		docModalWindow		 = 1 << 6,
		nonactivatingPanel		= 1 << 7, // Specifies that a panel that 
does not activate the owning application
		hUDWindow = 1 << 13 // Specifies a heads up display panel
	}

	extern (Objective-C)
	interface NSWindow {
		mixin ClassTrait;

		NSWindow init() @selector("init");

		NSWindow initWithContentRect(
		NSRect contentRect,
		NSWindowStyleMask style,
		NSBackingStoreType bufferingType,
		bool flag
		) @selector("initWithContentRect:styleMask:backing:defer:");

		void makeKeyAndOrderFront(id sender) 
@selector("makeKeyAndOrderFront:");
		NSView contentView() @selector("contentView");
		void orderFrontRegardless() @selector("orderFrontRegardless");
		void center() @selector("center");
		void contentView(NSView view) @selector("setContentView:");

		NSString title() @selector("title");
		void title(NSString value) @selector("setTitle:");

		extern (D) final
		{
		void title(string value) {
			this.title = value.toNSString;
		}
		}
	}

	struct CGColor;
	alias CGColorRef = CGColor*;

	alias CGFloat = double;

	struct NSPoint {
		CGFloat x;
		CGFloat y;
	}

	struct NSSize {
		CGFloat width;
		CGFloat height;
	}

	struct NSRect {
		NSPoint origin;
		NSSize size;
	}

	pragma(inline, true) NSPoint NSMakePoint(CGFloat x, CGFloat y) {
		NSPoint p;
		p.x = x;
		p.y = y;
		return p;
	}

	pragma(inline, true) NSSize NSMakeSize(CGFloat w, CGFloat h) {
		NSSize s;
		s.width = w;
		s.height = h;
		return s;
	}

	pragma(inline, true) NSRect NSMakeRect(CGFloat x, CGFloat y, 
CGFloat w, CGFloat h) {
		NSRect r;
		r.origin.x = x;
		r.origin.y = y;
		r.size.width = w;
		r.size.height = h;
		return r;
	}

	extern (Objective-C)
	interface NSNotification {
	}

	extern (Objective-C)
	interface NSObject {
		mixin ClassTrait;

		NSObject init() @selector("init");
	}
	extern (Objective-C)
	interface NSString {
		mixin ClassTrait;

		NSString init() @selector("init");

		NSString initWithBytes(
			const(ubyte)* bytes,
			NSUInteger length,
			NSStringEncoding encoding
		) @selector("initWithBytes:length:encoding:");
	}

	extern (Objective-C)
	enum NSStringEncoding : NSUInteger {
		NSASCIIStringEncoding = 1,		/* 0..127 only */
		NSNEXTSTEPStringEncoding = 2,
		NSJapaneseEUCStringEncoding = 3,
		NSUTF8StringEncoding = 4,
		NSISOLatin1StringEncoding = 5,
		NSSymbolStringEncoding = 6,
		NSNonLossyASCIIStringEncoding = 7,
		NSShiftJISStringEncoding = 8,		  /* 
kCFStringEncodingDOSJapanese */
		NSISOLatin2StringEncoding = 9,
		NSUnicodeStringEncoding = 10,
		NSWindowsCP1251StringEncoding = 11,	/* Cyrillic; same as 
AdobeStandardCyrillic */
		NSWindowsCP1252StringEncoding = 12,	/* WinLatin1 */
		NSWindowsCP1253StringEncoding = 13,	/* Greek */
		NSWindowsCP1254StringEncoding = 14,	/* Turkish */
		NSWindowsCP1250StringEncoding = 15,	/* WinLatin2 */
		NSISO2022JPStringEncoding = 21,		/* ISO 2022 Japanese encoding 
for e-mail */
		NSMacOSRomanStringEncoding = 30,

		NSUTF16StringEncoding = NSUnicodeStringEncoding,	  /* An alias 
for NSUnicodeStringEncoding */

		NSUTF16BigEndianStringEncoding = 0x90000100,		  /* 
NSUTF16StringEncoding encoding with explicit endianness specified 
*/
		NSUTF16LittleEndianStringEncoding = 0x94000100,	   /* 
NSUTF16StringEncoding encoding with explicit endianness specified 
*/

		NSUTF32StringEncoding = 0x8c000100,
		NSUTF32BigEndianStringEncoding = 0x98000100,		  /* 
NSUTF32StringEncoding encoding with explicit endianness specified 
*/
		NSUTF32LittleEndianStringEncoding = 0x9c000100		/* 
NSUTF32StringEncoding encoding with explicit endianness specified 
*/
	}

	alias NSUInteger = size_t;
	alias NSInteger = ptrdiff_t;

	mixin template MetaclassTrait() {
		import std.meta : Alias;

		alias Parent = Alias!(__traits(parent, typeof(this)));

		// extern (C) pragma(mangle, "objc_lookUpClass")
		//	 static typeof(this) objc_lookUpClass(const(char)* name);

		extern (D) private static Class classof() {
			enum name = __traits(identifier, Parent);

			auto cls = cast(typeof(this)) objc_lookUpClass(name);
			assert(cls, "Failed to lookup class: " ~ name);

			return cls;
		}
	}

	mixin template ClassTrait() {
		extern(Objective-C) interface Class {
			mixin MetaclassTrait;

			Parent alloc() @selector("alloc");
		}

		extern (D) private static Class classof() {
			return Class.classof;
		}

		extern (D) static typeof(this) alloc() {
			return classof.alloc();
		}
	}

	alias objc_ivar* Ivar;
	alias objc_method* Method;
	alias objc_object Protocol;

	alias char* SEL;
	alias objc_class* Class;
	alias objc_object* id;

	alias extern (C) id function(id, SEL, ...) IMP;

	version (X86)
		const int STRUCT_SIZE_LIMIT = 8;
	else version (PPC)
		const int STRUCT_SIZE_LIMIT = 4;
	else version (X86_64)
		const int STRUCT_SIZE_LIMIT = 16;
	else version (PPC64)
		const int STRUCT_SIZE_LIMIT = 16;

	struct objc_object {
		Class isa;
	}

	struct objc_super {
		id receiver;
		Class clazz;

		// for dwt compatibility
		alias clazz cls;
		alias clazz super_class;
	}

	struct objc_class {
		Class isa;
		Class super_class;
		const char* name;
		int versionn;
		int info;
		int instance_size;
		objc_ivar_list* ivars;
		objc_method_list** methodLists;
		objc_cache* cache;
		objc_protocol_list* protocols;
	}

	struct objc_ivar {
		const(char)* ivar_name;
		const(char)* ivar_type;
		int ivar_offset;

		version (X86_64)
		int space;
	}

	struct objc_ivar_list {
		int ivar_count;

		version (X86_64)
		int space;

		/* variable length structure */
		objc_ivar[1] ivar_list;
	}

	struct objc_method {
		SEL method_name;
		const(char)* method_types;
		IMP method_imp;
	}

	struct objc_method_list {
		objc_method_list* obsolete;

		int method_count;

		version (X86_64)
		int space;

		/* variable length structure */
		objc_method[1] method_list;
	}

	struct objc_cache {
		uint mask /* total = mask + 1 */;
		uint occupied;
		Method[1] buckets;
	}

	struct objc_protocol_list {
		objc_protocol_list* next;
		long count;
		Protocol*[1] list;
	}

	extern (Objective-C)
	interface CALayer {
		CGFloat borderWidth() @selector("borderWidth");
		void borderWidth(CGFloat value) @selector("setBorderWidth:");

		CGColorRef borderColor() @selector("borderColor");
		void borderColor(CGColorRef) @selector("setBorderColor:");
	}

	extern (C) {
		bool class_addIvar (Class cls, const(char)* name, size_t size, 
byte alignment, const(char)* types);
		bool class_addMethod (Class cls, SEL name, IMP imp, 
const(char)* types);
		bool class_addProtocol(Class cls, Protocol* protocol);
		IMP class_getMethodImplementation(Class cls, SEL name);
		const(char)* class_getName(Class cls);
		Class objc_allocateClassPair (Class superclass, const(char)* 
name, size_t extraBytes);
		Class objc_getClass (const(char)* name);
		Protocol* objc_getProtocol(const(char)* name);
		Class objc_lookUpClass (const(char)* name);
		void objc_registerClassPair (Class cls);
		Class object_getClass (id object);
		const(char)* object_getClassName (id obj);
		Class object_setClass (id object, Class cls);
		Ivar object_getInstanceVariable (id obj, const(char)* name, 
void** outValue);
		Ivar object_setInstanceVariable (id obj, const(char)* name, 
void* value);
		SEL sel_registerName (const(char)* str);
		// id objc_msgSend (id theReceiver, SEL theSelector, ...);
		void objc_msgSend_stret(void* stretAddr, id theReceiver, SEL 
theSelector, ...);
		id objc_msgSendSuper (objc_super* superr, SEL op, ...);
		Method class_getClassMethod (Class aClass, SEL aSelector);
		Method class_getInstanceMethod (Class aClass, SEL aSelector);
		Class class_getSuperclass (Class cls);
		IMP method_setImplementation (Method method, IMP imp);
		id class_createInstance (Class cls, size_t extraBytes);
		id objc_getMetaClass (char* name);
		void objc_msgSendSuper_stret(void* stretAddr, objc_super* 
superContext, SEL theSelector, ...);

		void instrumentObjcMessageSends(bool val);

		version (X86)
			double objc_msgSend_fpret(id self, SEL op, ...);
	}
}

-----


More information about the Digitalmars-d-learn mailing list