D言語からObjective-CとCocoaAPIを使いたい
BezierPathLab をD言語で作ってみました
Apple Documentation Archive (Sample Code) BezierPathLab をD言語で作成しました。

サンプルコード
Powered by Prism
radiobtn/dub.json
{
"authors": ["hidhok"],
"description": "A D/Objective-C Cocoa application.",
"name": "radiobtn",
"dflags": ["-L-framework", "-LCocoa"],
"sourceFiles": ["../cocoa/window",
"../cocoa/control",
"../cocoa/menus"]
}
radiobtn/source/app.d
/**
* radiobtn/source/app.d Version 0.0.4 Radio Button Test
*/
module radiobtn.main;
import core.attribute : optional, selector;
import std.stdio;
import std.format;
import std.conv : to;
import std.math;
import cocoa.window;
import cocoa.control;
import cocoa.menus;
string appName;
string versionNo = "0.0.4";
string helpMessage = "CocoaAPIを使用して GUIを、\nD言語で表示してみました。";
NSWindow win;
NSScrollView scrView;
NSClipView cpView;
BezierView bzView;
NSBezierPath bzPath;
Box[4] box;
CheckBox cBox;
Slider[3] sld;
Label[3] label, labelSlid;
ColorWell[3] cw;
string pathType, dashType;
int capStyle;
double scaleFactor = 1, angle = 0;
void main() {
auto delegate_ = AppDelegate.alloc.init;
NSApp.delegate_ = cast(void*) delegate_;
NSApp.activationPolicy = NSAppActivationPolicy.regular;
NSApp.run;
}
void buildWindow() {
appName = NSProcessInfo.processInfo.processName.toDs;
NSApp.menu = miniMenu(appName);
NSRect w_rect = nRect(50, 450, 500, 530);
immutable w_style = NSWindowStyleMask.titled |
NSWindowStyleMask.closable |
NSWindowStyleMask.resizable |
NSWindowStyleMask.miniaturizable;
win = NSWindow.alloc.init(w_rect, w_style, backing.buffered, false);
win.title = (appName ~ " (D言語)").toNs;
win.minSize = NSSize(500, 558);
ViewController vCtrler = ViewController.alloc.init;
vCtrler.view.subview = win.contentView;
win.delegate_ = vCtrler; //
win.makeKeyAndOrderFront = null;
NSApp.activateIgnoringOtherApps = true;
}
extern (Objective-C)
class AppDelegate : NSObject {
override static AppDelegate alloc() @selector("alloc");
override typeof(this) init() @selector("init");
bool applicationShouldTerminateAfterLastWindowClosed(
NSNotification notification) @selector(
"applicationShouldTerminateAfterLastWindowClosed:") {
return true;
}
void applicationDidFinishLaunching(NSNotification notification)
@selector("applicationDidFinishLaunching:") {
buildWindow;
buildBox;
buildRadioBtn;
buildSlider;
buildColorWell;
buildScrollView;
square; // Initial display
}
void aboutPanel() @selector("aboutPanel") {
// dialog(appName, "Version " ~ versionNo);
aboutPanelOpt;
}
void aboutPanelOpt() @selector("aboutPanelOpt") {
NSMutableDictionary options = NSMutableDictionary.dictionary;
// NSImage img = NSImage.name("AppIcon.icns".toNs);
// options.setObjKey(img, "ApplicationIcon".toNs);
options.setObjKey(versionNo.toNs, "Version".toNs);
options.setObjKey("horkew.la.coocan.jp".toNs, "Copyright".toNs);
// options.setObjKey("1".toNs, "ApplicationVersion".toNs);
NSApplication.shared_.aboutPanelOptions(options.copy);
}
void showHelp() @selector("showHelp") {
dialog("Help", helpMessage);
}
}
void buildBox() {
string[3] boxTitle = ["Path Type", "Line Cap Style", "Dash Type"];
box[0] = Box.alloc.init(nRect(365, 355, 123, 160));
box[1] = Box.alloc.init(nRect(365, 245, 123, 100));
box[2] = Box.alloc.init(nRect(365, 95, 123, 140));
box[3] = Box.alloc.init(nRect( 10, 95, 347, 65));
foreach (i; 0 .. 3) box[i].setBoxTitle = boxTitle[i];
box[3].setBoxTitle = "Colors" ~ r(14) ~ "Line"
~ r(23) ~ "Fill" ~ r(16) ~ "Background";
box[3].resizeMask = ResizeMask.Not;
}
string r(uint n) { return format("%*s", n, ' '); }
void buildRadioBtn() {
RadioBtn[13] rb;
string[13] btnTitle = ["Line", "Square", "Circle", "Arc",
"Oval", "Butt Line", "Round Line", "Square Line",
"Solid", "5-5", "8-3-8", "9-6-3", "12-8-6-8-12"];
/* radio button on Box[0] */
foreach (i; 0 .. 5) {
rb[i] = RadioBtn.alloc.init(nRect(5, 110-20*i, 140, 20));
rb[i].setRadioBtn(btnTitle[i], box[0], "onRadioBtnBox1");
}
rb[1].state = StateValue.On; // Initial value setting
pathType = "Square";
/* radio button on Box[1] */
foreach (i; 5 .. 8) {
rb[i] = RadioBtn.alloc.init(nRect(5, 50-20*(i-5), 140, 20));
rb[i].setRadioBtn(btnTitle[i], box[1], "onRadioBtnBox2");
}
rb[5].state = StateValue.On; // Initial value setting
/* radio button on Box[2] */
foreach (i; 8 .. 13) {
rb[i] = RadioBtn.alloc.init(nRect(5, 90-20*(i-8), 140, 20));
rb[i].setRadioBtn(btnTitle[i], box[2], "onRadioBtnBox3");
}
rb[8].state = StateValue.On; // Initial value setting
/* Separator */
Box.alloc.init(nRect(10, 25, 85, 1)).separator(box[0]);
/* CheckBox */
cBox = CheckBox.alloc.init(nRect(5, 2, 140, 20));
cBox.setCheckBox("Filled", box[0], "checkBoxState");
cBox.state = StateValue.On; // Initial value setting
}
void buildSlider() {
foreach (i; 0 .. 3) {
sld[i] = Slider.alloc.init(nRect(130, 67-i*30, 350, 25));
sld[i].action_ = "sldAction" ~ i.to!string; //
}
sld[0].setInitValues(0, 0, 359.99); // startValue, min, max
sld[1].setInitValues(1, 0.01, 3);
sld[2].setInitValues(10, 0.01, 30);
string[3] labelText = ["Angle", "Zoom", "Line Width"];
foreach (i; 0 .. 3) {
label[i] = Label.alloc.init(nRect(20, 70-i*30, 120, 20));
win.contentView.subview = label[i].text(labelText[i]);
labelSlid[i] = Label.alloc.init(nRect(80, 70-i*30, 50, 20));
win.contentView.subview = labelSlid[i].textC(format("%5.1f", sld[i].value));
}
}
void buildScrollView() {
scrView = NSScrollView.alloc.init(nRect(15, 175, 340, 340));
scrView.resizeMask = ResizeMask.Width | ResizeMask.Height;
win.contentView.subview = scrView;
cpView = NSClipView.alloc.init;
scrView.contentView = cpView;
bzView = BezierView.alloc.init(nRect(0, 0, 340, 340));
// bzView.bgColor = NSColor.yellow; // .....
cpView.subview = bzView;
}
void buildColorWell() {
NSColor[3] cwColor = [NSColor.red, NSColor.blue, NSColor.white];
foreach (i; 0 .. 3) {
cw[i] = ColorWell.alloc.init(nRect(70+i*90, 5, 50, 30));
cw[i].setCwValues = "actionCw" ~ i.to!string;
cw[i].color = cwColor[i];
}
}
extern (Objective-C)
class RadioBtn : NSButton, Res {
override static RadioBtn alloc() @selector("alloc");
override RadioBtn init(NSRect) @selector("initWithFrame:");
bool respondsToSelector(SEL) @selector("respondsToSelector:");
void setRadioBtn(string title, Box box, string action)
@selector("setRadioBtn:box:ac:") {
this.title = title;
this.type = NSButtonType.Radio;
this.target = this;
this.action_ = action; //
box.subview = this;
bzView.needsDisplay = true;
}
void onRadioBtnBox1() @selector("onRadioBtnBox1") {
pathType = this.title.toDs;
bzView.needsDisplay = true;
}
void onRadioBtnBox2() @selector("onRadioBtnBox2") {
switch(this.title.toDs) {
case "Butt Line":
capStyle = LineCap.Butt;
break;
case "Round Line":
capStyle = LineCap.Round;
break;
case "Square Line":
capStyle = LineCap.Square;
break;
default:
break;
}
bzView.needsDisplay = true;
}
void onRadioBtnBox3() @selector("onRadioBtnBox3") {
dashType = this.title.toDs;
bzView.needsDisplay = true;
}
}
extern (Objective-C)
class CheckBox : NSButton {
override static CheckBox alloc() @selector("alloc");
override CheckBox init(NSRect) @selector("initWithFrame:");
void setCheckBox(string title, Box box, string action)
@selector("setCheckBox:box:ac:") {
this.title = title;
this.bezelStyle = NSBezelStyle.FlexiblePush; // macOS 14.0+
this.type = NSButtonType.Switch;
this.target = this;
this.action_ = action; //
box.subview = this;
}
void checkBoxState() @selector("checkBoxState") {
bzView.needsDisplay = true;
}
}
extern (Objective-C)
class Box : NSBox {
override static Box alloc() @selector("alloc");
override Box init(NSRect) @selector("initWithFrame:");
void setBoxTitle(string title) @selector("setBoxTitle:") {
this.resizeMask = ResizeMask.MinX | ResizeMask.MinY;
this.titlePosition = NSTitlePosition.BelowTop;
this.title = title;
win.contentView.subview = this;
}
void separator(NSBox box) @selector("setSeparator:") {
this.boxType = NSBoxType.Separator;
box.subview = this;
}
}
extern (Objective-C)
class ColorWell : NSColorWell {
NSRect rf;
override static ColorWell alloc() @selector("alloc");
override ColorWell init(NSRect) @selector("initWithFrame:");
void setCwValues(string action) @selector("setCwValues:") {
this.bordered = true;
this.activate = true;
this.target = this;
this.action_ = action; //
box[3].subview = this;
}
void actionCw0() @selector("actionCw0") {
bzView.needsDisplay = true;
}
void actionCw1() @selector("actionCw1") {
cw[1].color.fill;
bzView.needsDisplay = true;
}
void actionCw2() @selector("actionCw2") {
scrView.bgColor = cw[2].color;
}
}
extern (Objective-C)
class Slider : NSSlider {
override static Slider alloc() @selector("alloc");
override Slider init(NSRect) @selector("initWithFrame:");
void setInitValues(double start, double min, double max)
@selector("setInitValues:min:max:") {
this.target = this;
this.continuous = true;
this.maxValue = max;
this.minValue = min;
this.value = start;
this.sliderType = NSSliderType.Linear;
this.vertical = false;
this.resizeMask = ResizeMask.Width;
this.sizeToFit;
win.contentView.subview = this;
}
void sldAction0() @selector("sldAction0") {
angle = sld[0].value;
labelSlid[0].textC(format("%5.1f", angle));
bzView.setAngle(angle);
bzView.needsDisplay = true;
}
void sldAction1() @selector("sldAction1") {
scaleFactor = sld[1].value;
labelSlid[1].textC(format("%5.1f", scaleFactor));
bzView.setZoom(scaleFactor);
bzView.needsDisplay = true;
}
void sldAction2() @selector("sldAction2") {
labelSlid[2].textC(format("%5.1f", sld[2].value));
bzView.needsDisplay = true;
}
}
extern (Objective-C)
class Label : NSTextField {
override static Label alloc() @selector("alloc");
override Label init(NSRect) @selector("initWithFrame:");
Label setValues() @selector("setValues") {
this.bgColor = NSColor.clear;
this.bordered = false;
this.editable = false;
this.selectable = false;
return this;
}
Label text(string labelStr) @selector("setValuesText:") {
this.setValues;
this.str = labelStr.toNs;
return this;
}
Label textC(string labelStr) @selector("setValuesCourier:") {
this.setValues;
NSFont font = NSFont.nameSize("Courier New Bold".toNs, 15);
this.attrStr = labelStr.toAttrStr(font, NSColor.darkGray);
return this;
}
}
extern (Objective-C)
class ViewController : NSViewController, NSWindowDelegate {
override static ViewController alloc() @selector("alloc");
override ViewController init() @selector("init");
override void loadView() @selector("loadView") {
printf("loadView\n");
}
override void viewDidLoad() @selector("viewDidLoad") {
printf("viewDidLoad\n");
}
void winDidResize(NSNotification) @selector("windowDidResize:") {
setToCenter(scaleFactor, angle);
bzView.needsDisplay = true;
}
}
extern (Objective-C)
class BezierView : NSView {
override static BezierView alloc() @selector("alloc");
override BezierView init() @selector("init");
override BezierView init(NSRect r) @selector("initWithFrame:");
override void drawRect(NSRect r) @selector("drawRect:") {
switch (pathType) {
case "Line": line; break;
case "Square": square; break;
case "Circle": circle; break;
case "Arc": arc; break;
case "Oval": oval; break;
default: break;
}
}
void setZoom(CGFloat sf) @selector("setZoom:") {
NSRect frame = this.frame;
NSRect bounds = this.bounds;
frame.size.width = bounds.size.width * sf;
frame.size.height = bounds.size.height * sf;
this.frame = frame;
this.bounds = bounds;
setToCenter(sf, angle);
}
void setAngle(double a) @selector("setAngle:") {
this.frameRotation(a);
setToCenter(scaleFactor, a);
}
}
void square() {
NSRect bzRect = nRect(120, 120, 100, 100);
bzPath = NSBezierPath.rect(bzRect);
cw[0].color.stroke;
cw[1].color.fill;
bzPath.lineWidth = sld[2].value;
lineDash(dashType);
if (cBox.state == StateValue.On) bzPath.fill;
bzPath.stroke;
}
void circle() {
NSRect bzRect = nRect(100, 100, 140, 140);
bzPath = NSBezierPath.oval(bzRect);
cw[0].color.stroke;
cw[1].color.fill;
bzPath.lineWidth = sld[2].value;
lineDash(dashType);
if (cBox.state == StateValue.On) bzPath.fill;
bzPath.stroke;
}
void oval() {
NSRect bzRect = nRect(80, 120, 180, 100);
bzPath = NSBezierPath.oval(bzRect);
cw[0].color.stroke;
cw[1].color.fill;
bzPath.lineWidth = sld[2].value;
lineDash(dashType);
if (cBox.state == StateValue.On) bzPath.fill;
bzPath.stroke;
}
void arc() {
bzPath = NSBezierPath.bezierPath;
NSPoint center = NSPoint(170, 170);
CGFloat radius = 70;
CGFloat startAngle = sld[0].value;
CGFloat endAngle = 359.99;
bzPath.arcWithCenter(center, radius, startAngle, endAngle);
cw[0].color.stroke;
cw[1].color.fill;
bzPath.lineCapStyle = capStyle;
lineDash(dashType);
bzPath.lineWidth = sld[2].value;
bzPath.stroke;
}
void line() {
bzPath = NSBezierPath.bezierPath;
cw[0].color.stroke;
bzPath.moveToPoint(NSPoint(100, 100));
bzPath.lineToPoint(NSPoint(240, 240));
bzPath.lineWidth = sld[2].value;
bzPath.lineCapStyle = capStyle;
lineDash = dashType;
bzPath.stroke;
}
void lineDash(string dashType) {
switch (dashType) {
case "Solid":
break;
case "5-5":
bzPath.dashLine = [5, 5];
break;
case "8-3-8":
bzPath.dashLine = [8, 3, 8];
break;
case "9-6-3":
bzPath.dashLine = [9, 6, 3];
break;
case "12-8-6-8-12":
bzPath.dashLine = [12, 8, 6, 8, 12];
break;
default:
break;
}
}
void setToCenter(double scaleF, double degree) {
double cx, cy, ox, oy;
cx = scrView.frame.size.width / 2;
cy = scrView.frame.size.height / 2;
double radian = PI * (45 + degree) / 180;
double bz = 170; // = bzView.bounds.size.height / 2
ox = cx - bz * scaleF * SQRT2 * cos(radian);
oy = cy - bz * scaleF * SQRT2 * sin(radian);
bzView.frameOrigin = NSPoint(ox, oy);
}
NSRect nRect(double x, double y, double w, double h) {
return NSMakeRect(x, y, w, h);
}
cocoa/window.d
cocoa/control.d
cocoa/menus.d
- Log
- 2025-03-11 修正しました
- 2024-06-08 修正しました
- 2024-01-26 修正しました
- 2024-01-20 upload