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

画像の拡大縮小を行いたい

NSRect と bounds を理解するために、作ってみましたが、未だ理解不足です。

zoom の Sample

サンプルコード

Powered by Prism

zoom/dub.json

{
    "authors": ["hidhok"],
    "description": "A D/Objective-C Cocoa application.",
    "name": "zoom",
    "dflags": ["-L-framework", "-LCocoa"],
    "sourceFiles": ["../cocoa/window",
                    "../cocoa/control",
                    "../cocoa/menus"]
}               
zoom/source/app.d

/**
 *  zoom/source/app.d     Version 0.0.5      zoom Test
 */
module zoom.main;
import core.attribute : optional, selector;
import std.stdio;
import std.format;
import std.conv : to;
import cocoa.window;
import cocoa.control;
import cocoa.menus;

string appName;
string versionNo = "0.0.5";
string helpMessage =  "CocoaAPIを使用して 図形の拡大・縮小を、\nD言語で表示してみました。";

NSWindow win;
ViewController vCtrler;
NSScrollView scrView;
BezierView bzView;
Slider sliderZoom;
Label zoomVal;
string pathType, dashType;
double scaleFactor = 1;

void main() {
    auto delegate_ = AppDelegate.alloc.init;
    NSApp.delegate_ = cast(void*) delegate_;
    NSApp.activationPolicy = NSAppActivationPolicy.regular;
    NSApp.run;
}

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;
        buildContents;
    }
    void aboutPanel() @selector("aboutPanel") {
        dialog(appName, "Version " ~ versionNo);
    }
    void showHelp() @selector("showHelp") {
        dialog("Help", helpMessage);
    }
}

void buildWindow() {
    appName = NSProcessInfo.processInfo.processName.toDs;
    NSApp.menu = miniMenu(appName);
    NSRect w_rect = nRect(100, 500, 300, 450);
    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(300, 478);
    vCtrler = ViewController.alloc.init;
    vCtrler.view.subview = win.contentView;
    win.delegate_ = vCtrler;
    win.makeKeyAndOrderFront = null;
    NSApp.activateIgnoringOtherApps = true;
}

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);
        bzView.needsDisplay = true;
    }
}

void buildContents() {
    /* NSScrollView & NSClipView & BezierView */
    scrView = NSScrollView.alloc.init(nRect(10, 160, 280, 280));
    scrView.resizeMask = ResizeMask.Width | ResizeMask.Height;
    // scrView.bgColor = NSColor.white;
    win.contentView.subview = scrView;
    bzView = BezierView.alloc.init(nRect(0, 0, 280, 280));
    NSClipView cpView = NSClipView.alloc.init;
    scrView.contentView = cpView;
    cpView.subview = bzView;
    /* Box */
    Box box1, box2;
    box1 = Box.alloc.init(nRect(10, 60, 135, 88));
    box2 = Box.alloc.init(nRect(155, 60, 135, 88));
    box1.setBoxTitle("Path Type");
    box2.setBoxTitle("Dash Type");
    /* Radio Button */
    RadioBtn[6] rb;
    string[6] btnTitle = ["Line", "Square", "Oval", "Solid", "10-10", "8-2-8"];
    foreach (i; 0 .. 3) {
        int j = i + 3;
        rb[i] = RadioBtn.alloc.init(nRect(0, 40 - i * 20, 100, 20));
        rb[j] = RadioBtn.alloc.init(nRect(0, 40 - i * 20, 100, 20));
        rb[i].setRadioBtn(btnTitle[i], box1, "onRadioBtnBox1");
        rb[j].setRadioBtn(btnTitle[j], box2, "onRadioBtnBox2");
    }
    /* Initial value setting */
    pathType = "Square";
    rb[1].state = StateValue.On; 
    rb[3].state = StateValue.On;
    /* Slider & sliderLabel */
    sliderZoom  = Slider.alloc.init(nRect(88,  10, 200, 30));
    sliderZoom.action_ = "sliderZoomAction";  //
    sliderZoom.setInitValues(1, 0.01, 3); // .... start value
    Label titleZoom  = Label.alloc.init(nRect(12, 15, 50, 20));
    titleZoom.text  = "Zoom ✕";
    zoomVal  = Label.alloc.init(nRect(55,  15, 30, 20));
    zoomVal.text  = format("%5.1f", sliderZoom.value);
    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.title = title;
        win.contentView.subView = this;
    }
}

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:action:") {
        this.title = title;
        this.type = NSButtonType.Radio;
        this.target = this;
        this.action_ = action; //
        box.subview = this;
    }
    void onRadioBtnBox1() @selector("onRadioBtnBox1") {
        pathType = this.title.toDs;
        bzView.needsDisplay = true;
    }
    void onRadioBtnBox2() @selector("onRadioBtnBox2") {
        dashType = this.title.toDs;
        bzView.needsDisplay = true;
    }
}

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.resizeMask = ResizeMask.Width;
        this.target = this;
        this.continuous = true;
        this.maxValue = max;
        this.minValue = min;
        this.value = start;
        this.sliderType = NSSliderType.Linear;
        this.vertical = false;
        win.contentView.subview = this;
    }
    void sliderZoomAction() @selector("sliderZoomAction") {
        scaleFactor = this.value;
        zoomVal.text = format("%5.1f", scaleFactor);
        bzView.setZoom(scaleFactor);
        bzView.needsDisplay = true;
    }
}

extern (Objective-C)
class Label : NSTextField {
    override static Label alloc() @selector("alloc");
    override Label init(NSRect) @selector("initWithFrame:");
    void text(string labelStr) @selector("setVAluesText:") {
        this.bgColor = NSColor.clear;
        this.bordered = false;
        this.editable = false;
        this.selectable = false;
        this.str = labelStr;
        win.contentView.subview = this;
    }
}

extern (Objective-C)
class BezierView : NSView {
    override static BezierView alloc() @selector("alloc");
    override BezierView init() @selector("init");
    override BezierView init(NSRect) @selector("initWithFrame:");
    override void drawRect(NSRect r) @selector("drawRect:") {
        double bx = 140, by = 140, ra = 140/3;
        NSBezierPath path;
        path.lineWidth = 20;
        NSColor.red.stroke;
        switch (pathType) {
          case "Line":
            path = NSBezierPath.bezierPath;
            path.moveToPoint(NSPoint(bx-ra, bx-ra));
            path.lineToPoint(NSPoint(bx+ra, by+ra));
            break;
          case "Square": 
            path = NSBezierPath.rect(nRect(bx-ra, by-ra, ra*2, ra*2)); 
            break;
          case "Oval":
            path = NSBezierPath.oval(nRect(bx-ra*3/2, by-ra, ra*3, ra*2)); 
            break;
          default: 
            break;
        } 
        switch (dashType) {
          case "Solid": 
            break;
          case "10-10": 
            path.dashLine([10, 10]);  
            break;
          case "8-2-8": 
            path.dashLine([8, 2, 8]); 
            break;
          default: 
            break;
        }
        path.stroke;
    }
    void setZoom(double 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; 
        double xo = scrView.frame.size.width / 2  * (1 - sf);
        double yo = scrView.frame.size.height / 2 * (1 - sf);
        this.frameOrigin = NSPoint(xo, yo);
        setToCenter(sf);
    }
}

void setToCenter(double s) {
    double cx, cy, ox, oy;
    cx = scrView.frame.size.width / 2;
    cy = scrView.frame.size.height / 2;
    ox = cx - bzView.bounds.size.width * s / 2;
    oy = cy - bzView.bounds.size.height * s / 2;
    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