D言語からObjective-CとCocoaAPIを使いたい

BezierPathLab をD言語で作ってみました

Apple Documentation Archive (Sample Code) BezierPathLab をD言語で作成しました。

RadioButton の Sample

サンプルコード

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