Vala プログラミング

GNUstep プログラミング

おなが@京都先端科学大

GhostBSD New Release 25.02-R14.3

GhostBSD の新バージョン GhostBSD 25.02-R14.3 p2 がリリースされました。
ghostbsd.org
このバージョンから、Community Images に Gershwin が加わりました。
Gershwin は GNUstep-base の desktop 環境です。
今のところPreview版です。
(Downloadページ)

VirtualBox にインストールしてみました。実機では、メインメモリは8GBが必要
です。VirtualBoxでは5GB以上で起動できます。

起動直後のデスクトップ画面

Workspace FileViewer 画面

GNUstep Core Animation with CAAppKitBridge

新しい GNUstep Project site が開設されています。
GNUstep 関連のドキュメントが整備され、再編集されています。
new GNUstep Project site
https://gnustep.github.io/

Core Animation Example

参考サイト
Core Animation Examples
https://medium.com/@quangtqag/core-animation-examples-742f9af00188

CoreAnimation
gnustep/libs-quartzcore
https://github.com/gnustep/libs-quartzcore
2024-10-17付 Camlkit v0.2 の記事に記載されている方法で、
CoreFoundation, CoreGraphics, CoreAnimation
をインストールします。

CAAppKitBridge の header files はグローバルにインストールされないので、
/usr/local/GNUstep/Local/Library/Frameworks/CAAppKitBridge.framework/Headers/
にコピーします。
header files
GSCAData.h
NSView+CAMethods.h
CAAppKitBridge.h
 #import "GSCAData.h"
 #import "NSView+CAMethods.h"

プログラム
Application project
main.m
AppController.h
AppController.m
TestView.h
TestView.m
TextLayer.h
TextLayer.m
GNUmakefile

main.m

#import "AppController.h"
#import <Foundation/Foundation.h>

int
main(int argc, const char ** argv, char ** environ)
{
  NSAutoreleasePool * pool = [NSAutoreleasePool new];
  id controller = [AppController new];
  [[NSApplication sharedApplication] setDelegate: controller];
  [NSProcessInfo initializeWithArguments: (char**)argv
                                   count: argc
                             environment: environ];
  [NSApp run];
  [pool drain];
  return 0;
}

AppController
AppController.h

#import <Foundation/Foundation.h>
#import <AppKit/NSWindow.h>
#import <AppKit/NSMenu.h>

#import "TestView.h"

@interface AppController : NSObject
{
   NSWindow   *window;
   TestView     *view;
   NSMenu       *menu;
}
@end

AppController.m

#import "AppController.h"

@implementation AppController
- (void) applicationDidFinishLaunching: (id)t
{
   menu = [[NSMenu alloc] initWithTitle: @"Main Menu"];

   [menu addItemWithTitle: @"CAAppKitBridgeTest"
                  action: @selector(orderFrontStandardAboutPanel:)
           keyEquivalent: @""];
   [menu addItemWithTitle: @"Quit"
                  action: @selector(terminate:)
           keyEquivalent: @"q"];

   [NSApp setMainMenu: menu];
   [menu release];
   
   window = [[NSWindow alloc] initWithContentRect: NSMakeRect(0,0,450,580)
                                        styleMask: NSTitledWindowMask | NSClosableWindowMask
                                          backing: NSBackingStoreBuffered
                                            defer: NO];
   NSLog(@"window %p", window);
                                            
   view = [[TestView alloc] initWithFrame: window.frame];
   NSLog(@"view %p", view);

   [[window contentView] addSubview: view];

   [window cascadeTopLeftFromPoint: NSMakePoint(150, 700)];
   [window makeKeyAndOrderFront: nil];

   [view prepareOpenGL];
}

-(BOOL)applicationShouldTerminateAfterLastWindowClosed: (id)sender
{
  return YES;
}

-(void)dealloc
{
  [super dealloc];
}

@end

TestView
TestView.h

#import <Foundation/Foundation.h>
#import <GL/gl.h>

#import <QuartzCore/QuartzCore.h>
#import <CAAppKitBridge/CAAppKitBridge.h>

#import "TextLayer.h"

@interface TestView : NSView
{
   CARenderer *renderer;
   CALayer *rootLayer;
   CALayer *subLayer1;
   CALayer *subLayer2;
   CALayer *subLayer3;
   CALayer *subLayer4;
   CALayer *subLayer5;
   CALayer *subLayer6;
   CALayer *subLayer7;
   TextLayer *textLayer1;
   TextLayer *textLayer2;
   TextLayer *textLayer3;
   TextLayer *textLayer4;
   TextLayer *textLayer5;
   TextLayer *textLayer6;
   TextLayer *textLayer7;
   NSTimer *_timer;
   NSOpenGLContext   *context;
   NSMutableArray  *sublayers;
   NSMutableArray *textlayers;
}
@end

TestView.m

#import "TestView.h"

CGColorRef NSColorToCGColor(NSColor *color)
{
   return CGColorCreateGenericRGB(
      color.redComponent,
      color.greenComponent,
      color.blueComponent,
      color.alphaComponent);
}

@implementation TestView
- (void) prepareOpenGL
{
   NSLog(@"view wantsLayer value: %d", self.wantsLayer);

   NSLog(@"Setting view wantsLayer to true");
   [self setWantsLayer: YES];
   NSLog(@"view wantsLayer value: %d", self.wantsLayer);

   CGColorRef whiteColor  = CGColorCreateGenericRGB(1, 1, 1, 1);
   CGColorRef blueColor   = CGColorCreateGenericRGB(0, 0, 1, 1);

   /* Create renderer */
   renderer = [CARenderer rendererWithNSOpenGLContext: self._gsCreateOpenGLContext
                                              options: nil];

   [renderer retain];
   [renderer setBounds: NSRectToCGRect(self.frame)];

   /* Create root layer and sub layers */
   {
      rootLayer = self.makeBackingLayer;
      [renderer setLayer: rootLayer];
      [rootLayer setBounds: NSRectToCGRect(NSMakeRect(0,0,
                        self.frame.size.width,
                        self.frame.size.height))];
      [rootLayer setBackgroundColor: whiteColor];
   
      CGPoint midPos = CGPointMake(renderer.bounds.size.width/2,
                                   renderer.bounds.size.height/2);
      [rootLayer setPosition: midPos];

      sublayers = [NSMutableArray new];
      for (int i = 0; i < 7; i++) {
         CALayer *subLayer = CALayer.layer;
         [rootLayer addSublayer: subLayer];    
         subLayer.bounds = NSRectToCGRect(NSMakeRect(0, 0, 50, 50));
         subLayer.position = CGPointMake(50, 530 - i*80);
         subLayer.speed = 2;
         [subLayer setNeedsDisplay];
         [sublayers addObject: subLayer];
      }

      subLayer1 = sublayers[0];
      subLayer1.backgroundColor = NSColorToCGColor(NSColor.orangeColor);
      subLayer2 = sublayers[1];
      subLayer2.backgroundColor = NSColorToCGColor(NSColor.yellowColor);
      subLayer3 = sublayers[2];
      subLayer3.backgroundColor = NSColorToCGColor(NSColor.magentaColor);
      subLayer4 = sublayers[3];
      subLayer4.backgroundColor = NSColorToCGColor(NSColor.purpleColor);
      subLayer5 = sublayers[4];
      subLayer5.backgroundColor = NSColorToCGColor(NSColor.redColor);
      subLayer6 = sublayers[5];
      subLayer6.backgroundColor = NSColorToCGColor(NSColor.redColor);
      subLayer7 = sublayers[6];
      subLayer7.backgroundColor = NSColorToCGColor(NSColor.redColor);

      // TextLayer
      textlayers = [NSMutableArray new];
      for (int i = 0; i < 7; i++) {
         TextLayer *textLayer = TextLayer.layer;
         [rootLayer addSublayer: textLayer];
         [textLayer setBounds: CGRectMake(0, 0, 300, 50)];
         [textLayer setPosition: CGPointMake(250, 530 - i*80)];
         [textLayer setColor: blueColor];
         [textLayer setFontSize: 20];
         [textLayer setNeedsDisplay];
         [textlayers addObject: textLayer];
      }

      textLayer1 = textlayers[0];
      [textLayer1 setText: @"Change Background Color"];
      textLayer2 = textlayers[1];
      [textLayer2 setText: @"Add Layer"];
      textLayer3 = textlayers[2];
      [textLayer3 setText: @"Shadow"];
      textLayer4 = textlayers[3];
      [textLayer4 setText: @"Opacity"];
      textLayer5 = textlayers[4];
      [textLayer5 setText: @"Translation"];
      textLayer6 = textlayers[5];
      [textLayer6 setText: @"Translation & Rotation"];
      textLayer7 = textlayers[6];
      [textLayer7 setText: @"Rotate3D"];
   
   }

   CGColorRelease(whiteColor);
   CGColorRelease(blueColor);
  
   context = [self _gsCreateOpenGLContext];
   [context makeCurrentContext];

   glClear(GL_COLOR_BUFFER_BIT);

   glMatrixMode(GL_PROJECTION);
   glLoadIdentity();
   glOrtho(0, [self frame].size.width, 0, [self frame].size.height, -1500, 1500);

   _timer = [NSTimer scheduledTimerWithTimeInterval: 1./60.
                                             target: self 
                                           selector: @selector(timerAnimation:)
                                           userInfo: nil
                                            repeats: YES];
}

-(void) timerAnimation: (NSTimer*)t
{
   [renderer beginFrameAtTime: CACurrentMediaTime()
                    timeStamp: NULL];
   [self clearBounds: [renderer updateBounds]];
   [renderer render];

  glFlush();
  [context flushBuffer];
}

- (void)clearBounds:(CGRect)bounds
{  
  glBegin(GL_QUADS);
  glColor4f(0,0,0,1);
  glVertex2f(bounds.origin.x, bounds.origin.y);
  glVertex2f(bounds.origin.x+bounds.size.width, bounds.origin.y);
  glVertex2f(bounds.origin.x+bounds.size.width, bounds.origin.y+bounds.size.height);
  glVertex2f(bounds.origin.x, bounds.origin.y+bounds.size.height);
  glEnd();
}

- (void) mouseDown:(NSEvent *)theEvent
{
	NSLog(@"mouse down event");
  
   NSPoint curPoint = [self convertPoint:[theEvent locationInWindow] fromView:nil];
   NSLog(@"mouse down event x: %f y: %f", curPoint.x, curPoint.y);
  
   for (int i = 0; i < [sublayers count]; i++)
   {
      CALayer *sublayer = [sublayers objectAtIndex:i];   
      CGRect frame = [sublayer bounds];
      frame.origin = CGPointMake([sublayer position].x - frame.size.width/2, [sublayer position].y - frame.size.height/2);
      if (CGRectContainsPoint(frame, NSPointToCGPoint(curPoint)))
      {
         NSLog(@"sublayer position x: %f y: %f", sublayer.position.x, sublayer.position.y);
         switch (i) {
            case 0:
               subLayer1.backgroundColor = NSColorToCGColor(NSColor.greenColor);   
               break;
            case 1:
                  CALayer *alayer = CALayer.layer;
                  alayer.bounds = NSRectToCGRect(NSMakeRect(0, 0, 25, 25));
                  alayer.position = CGPointMake(12.5, 37.5);
                  alayer.backgroundColor = NSColorToCGColor(NSColor.blueColor);
                  [subLayer2 addSublayer: alayer];
               break;
            case 2:
               subLayer3.shadowOpacity = 0.7;
               subLayer3.shadowOffset = CGSizeMake(10, -10);
               break;
            case 3:
                  CABasicAnimation *opacity = [CABasicAnimation animationWithKeyPath:@"opacity"];
                  [opacity setFromValue: [NSNumber numberWithFloat: [subLayer4 opacity]]];
                  [opacity setToValue: [NSNumber numberWithFloat: 0.0]];
                  [opacity setDuration: 3];
                  [opacity setAutoreverses: YES];
                  [subLayer4 addAnimation: opacity forKey: @"pulse"];
               break;
            case 4:
                  CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"position"];
                  [animation setDuration: 3];
                  [animation setFromValue: [NSValue valueWithPoint: NSMakePoint(50, 210)]];
                  [animation setToValue: [NSValue valueWithPoint: NSMakePoint(100, 210)]];
                  [animation setAutoreverses: YES];
                  [subLayer5 addAnimation: animation forKey:@"thePositionAnimation"];
               break;
            case 5:
                  CABasicAnimation *animation2 = [CABasicAnimation animationWithKeyPath:@"transform"];
                  [animation2 setFromValue: [NSValue valueWithCATransform3D: [subLayer6 transform]]];
                  [animation2 setToValue: [NSValue valueWithCATransform3D: CATransform3DTranslate(CATransform3DRotate([subLayer6 transform], M_PI, 0, 0, 1), -80, 0, 0)]];
                  [animation2 setDuration: 4];
                  [animation2 setAutoreverses: YES];
                  [subLayer6 addAnimation: animation2 forKey: @"doABarrelRoll"];
               break;
            case 6:
                  CABasicAnimation *animation3 = [CABasicAnimation animationWithKeyPath:@"transform"];
                  [animation3 setFromValue: [NSValue valueWithCATransform3D: [subLayer7 transform]]];
                  [animation3 setToValue: [NSValue valueWithCATransform3D: CATransform3DRotate([subLayer7 transform], M_PI, 0, 1, 0)]];
                  [animation3 setDuration: 4];
                  [animation3 setAutoreverses: YES];
                  [subLayer7 addAnimation: animation3 forKey: @"transform"];
               break;
            default:
               break;
         }
      }
   }
}

@end

TextLayer
TextLayer.h, TextLayer.m ともに quartzcore/Demo 内にあるファイルをそのまま
使用している。

GNUmakefile

include $(GNUSTEP_MAKEFILES)/common.make

APP_NAME = CAAppKitBridgeTest

CAAppKitBridgeTest_OBJC_FILES = \
	main.m \
	AppController.m \
	TestView.m \
	TextLayer.m


ADDITIONAL_LDFLAGS += $(shell pkg-config --libs cairo) -L../Source/CAAppKitBridge.framework

ADDITIONAL_OBJCFLAGS += -Wall -g -O0 -std=gnu11
ADDITIONAL_CFLAGS += -Wall -g -O0 -std=gnu11
ADDITIONAL_TOOL_LIBS =  -lgnustep-gui -lGL -lGLU -lopal -lQuartzCore -lCAAppKitBridge

-include GNUmakefile.preamble
include $(GNUSTEP_MAKEFILES)/tool.make
include $(GNUSTEP_MAKEFILES)/application.make
-include GNUmakefile.postamble

Camlkit & GNUstep OpenGLView (2)

前回に続き OpenGLView の記事です。
gles3 https://github.com/craff/gles3 のプロジェクト内の examples に
rotating cube があります。
これを NSOpenGLView で描画してみました。

[実行結果]

cube の回転には NSTimer を用いています。

プロジェクト構成

/bin
    /Resources
        MainMenu.gorm
        Info-gnustep.plist
    dune
    app_delegate.ml
    main.ml
    main_window.ml
dune-project
(プロジェクト名).opam

dune (前回と同じ)

main.ml (前回と同じ)

app_delegate.ml (前回と同じ)

main_window.ml

(* NSOpenGLView Rotating Cube *)
open Foundation
open AppKit
open Runtime

open Egl
open Gles3
open Gles3.Type
open Shaders
open Buffers
open Matrix

let w = 400. and h = 400.
let gwidth = ref 400 and gheight = ref 350

let ratio = ref (float_of_int !gwidth /. float_of_int !gheight)

let make_button ~title ~frame ~target ~action =
    let btn = alloc NSButton.self |> NSButton.initWithFrame frame in
    btn |> NSControl.setTarget target;
    btn |> NSControl.setAction action;
    btn |> NSButton.setTitle title;
    btn

let make_timer ~interval ~target ~selector ~userInfo ~repeats =
    let timer = NSTimer.self |> NSTimerClass.scheduledTimerWithTimeInterval2 interval ~target:target ~selector_:selector ~userInfo:userInfo ~repeats:repeats in
    timer

let make_glView ~frame =
    let glView =
    _new_ NSOpenGLView.self in
    glView |> NSView.setFrame frame;
    glView

let light_shader =
    ("light_shader",
    [{ name = "vertex_main";
        ty   = gl_vertex_shader;
        src  = "
        uniform mat4 ModelView,Projection;
      
        uniform vec4 lightDiffuse,lightAmbient,color;
        uniform vec3 lightPos;
      
        in vec3  in_position, in_normal;
      
        out vec4 diffuse,ambient,m_position;
        out vec3 normal,halfVector;
      
        void main()
        {
           /* pass the halfVector to the fragment shader */
           m_position = ModelView * vec4(in_position,1.0);
           halfVector = normalize(lightPos - 2.0 * m_position.xyz);
      
           // only works for orthogonal matrices
           mat3 NormalMatrix=mat3(ModelView[0].xyz,ModelView[1].xyz,ModelView[2].xyz);
           /* first transform the normal into eye space and
           normalize the result */
           normal = normalize(NormalMatrix * in_normal);
      
           /* Compute the diffuse, ambient and globalAmbient terms */
           diffuse = color * lightDiffuse;
           ambient = color * lightAmbient;
           gl_Position = Projection * m_position;
        }"};
    { name = "fragment_main";
        ty   = gl_fragment_shader;
        src  = "
        uniform vec3 lightPos;
        uniform float specular,shininess;
        in vec3 normal,halfVector;
        in vec4 diffuse,ambient,m_position;
        out vec4 FragColor;
      
        void main()
        {
           vec3 halfV,lightDir;
           float NdotL,NdotHV;
      
           lightDir = normalize(lightPos - m_position.xyz);
      
           /* The ambient term will always be present */
           vec4 color = ambient;
           /* compute the dot product between normal and ldir */
      
           NdotL = dot(normal,lightDir);
           if (NdotL > 0.0) {
              color += diffuse * NdotL;
              halfV = normalize(halfVector);
              NdotHV = max(dot(normal,halfV),0.0);
              color += specular * pow(NdotHV, shininess);
           }
      
           FragColor=color;
        }"};
    ])

let vertices : Gles3.float_bigarray = Buffers.to_float_bigarray
    [|0.;0.;0.;
      0.;0.;1.;
      0.;1.;1.;
      0.;1.;0.;

      1.;0.;0.;
      1.;0.;1.;
      1.;1.;1.;
      1.;1.;0.;

      0.;0.;0.;
      1.;0.;0.;
      1.;1.;0.;
      0.;1.;0.;

      0.;0.;1.;
      1.;0.;1.;
      1.;1.;1.;
      0.;1.;1.;

      0.;0.;0.;
      1.;0.;0.;
      1.;0.;1.;
      0.;0.;1.;

      0.;1.;0.;
      1.;1.;0.;
      1.;1.;1.;
      0.;1.;1.;
    |]

let normals0 = to_float_bigarray
    [|
      -1.;0.;0.;
      -1.;0.;0.;
      -1.;0.;0.;
      -1.;0.;0.;
  
      1.;0.;0.;
      1.;0.;0.;
      1.;0.;0.;
      1.;0.;0.;
  
      0.;0.;-1.;
      0.;0.;-1.;
      0.;0.;-1.;
      0.;0.;-1.;
  
      0.;0.;1.;
      0.;0.;1.;
      0.;0.;1.;
      0.;0.;1.;
  
      0.;-1.;0.;
      0.;-1.;0.;
      0.;-1.;0.;
      0.;-1.;0.;
  
      0.;1.;0.;
      0.;1.;0.;
      0.;1.;0.;
      0.;1.;0.;
    |]

let elements = to_uint_bigarray
    [|
      0;1;2;    2;3;0;
      4;5;6;    6;7;4;
      8;9;10;   10;11;8;
      12;13;14; 14;15;12;
      16;17;18; 18;19;16;
      20;21;22; 22;23;20
    |]

let center = [|0.;0.;0.|]
let lightPos = [|0.0;1.0;4.0|]
let eyePos = [|0.;0.;3.5|]
let eyeUp = [|1.0;1.0;0.0|]

let t = ref 0.0

let modelView t =
    (mul (rotateY (10.*.t/.11.))
       (mul (rotateZ (6.*.t/.7.)) (translate (-0.5) (-0.5) (-0.5))))
    
let projection () =
        (mul (perspective 45.0 !ratio 1. 5.) (lookat eyePos center eyeUp))

let increment_sel = selector "increment:"

let update_angle () =
   Printf.printf "update\n";
   t := !t +. 0.2

let setShader () =
    let prg : unit program = compile light_shader in
    let prg = Shaders.float_cst_attr prg "in_position" vertices in
    let prg = float_cst_attr prg "in_normal" normals0 in
    let prg : (float array -> unit) program = float_mat4_uniform prg "ModelView" in
    let prg : (float array -> float array -> unit) program = float_mat4_uniform prg "Projection" in

    let prg = float4v_cst_uniform prg "color" [|0.0;0.0;1.0;1.0|] in
    let prg = float1_cst_uniform prg "specular" 0.5 in
    let prg = float1_cst_uniform prg "shininess" 10. in
    let prg = float3v_cst_uniform prg "lightPos" lightPos in
    let prg = float4v_cst_uniform prg "lightDiffuse" [|0.7;0.7;0.7;1.0|] in
    let prg = float4v_cst_uniform prg "lightAmbient" [|0.2;0.2;0.2;1.0|] in
    prg

let draw ~prg  =
    enable gl_depth_test;
    disable gl_cull_face;
    cull_face ~face: gl_back;
    clear_color { r = 0.3; g = 0.3; b = 0.3; a = 1.0 };
    clear [ gl_color_buffer; gl_depth_buffer];

    Shaders.draw_uint_elements prg gl_triangles elements (projection ()) (modelView !t)
;;

let create app =
    let win =
    alloc NSWindow.self
    |> NSWindow.initWithContentRect
        (CGRect.make ~x: 0. ~y: 0. ~width: w ~height: h)
        ~styleMask: Bitmask.(
            _NSWindowStyleMaskTitled +
            _NSWindowStyleMaskClosable +
            _NSWindowStyleMaskResizable)
        ~backing: _NSBackingStoreBuffered
        ~defer: false
    and glView =
        make_glView
        ~frame: (CGRect.make ~x: 0. ~y: 50. ~width: w ~height: (h -. 50.))
    in
    win
    |> NSWindow.cascadeTopLeftFromPoint (CGPoint.init ~x: 200. ~y: 800.)
    |> ignore;
    win |> NSWindow.makeKeyAndOrderFront nil;
    win |> NSWindow.contentView |> NSView.addSubview glView;

    let glcontext = glView |> NSOpenGLView.openGLContext
    in
    glcontext|> NSOpenGLContext.makeCurrentContext;

    let prg = setShader () in
    let increment_count_method _self _cmd _sender =
        print_endline "incremet method";
        update_angle ();
        Printf.printf "%f\n" !t;
        draw ~prg: prg;
        glcontext |> NSOpenGLContext.flushBuffer;
    in     
    let controller_class =
        Class.define "MyController"
            ~methods:
            [ Method.define increment_count_method
                ~cmd: increment_sel ~args: Objc_t.[id] ~return: Objc_t.void
            ]
    in         
    let controller = _new_ controller_class
    in
    let _timer =
        make_timer
            ~interval:0.2
            ~target: controller
            ~selector: increment_sel
            ~userInfo: nil
            ~repeats: true
    in
    draw ~prg: prg;
    glcontext |> NSOpenGLContext.flushBuffer;
    win
;;

Resources (以前の記事参照)

Camlkit & GNUstep OpenGLView

OCamlOpenGL用のライブラリ gles3 を利用して、GNUstep の NSOpenGLView に triangle を描画してみました。shader を使って描画します。
gles3 https://github.com/craff/gles3

[実行結果]

プロジェクト構成

/bin
    /Resources
        MainMenu.gorm
        Info-gnustep.plist
    dune
    app_delegate.ml
    main.ml
    main_window.ml
dune-project
(プロジェクト名).opam


dune

(executable
 (public_name gnustepAppTemplate2)
 (name main)
 (flags -ccopt -L/usr/local/GNUstep/System/Library/Libraries  -cclib -lgnustep-base -cclib -lgnustep-gui)
 (libraries 
    gles3
    camlkitGS.Foundation
    camlkitGS.AppKit
    camlkitGS.camlkit))

(subdir GSApp.app
 (rule
  (deps ../main.exe)
  (targets GSApp)
  (action (copy ../main.exe GSApp))))

(subdir GSApp.app/Resources
  (rule
    (deps ../../Resources/Info-gnustep.plist)
    (targets Info-gnustep.plist)
    (action (progn
      (copy ../../Resources/Info-gnustep.plist Info-gnustep.plist))))
)

(subdir GSApp.app/Resources/MainMenu.gorm
  (rule
    (deps ../../../Resources/MainMenu.gorm/objects.gorm ../../../Resources/MainMenu.gorm/data.classes ../../../Resources/MainMenu.gorm/data.info)
    (targets objects.gorm data.classes data.info)
    (action (progn
       (copy ../../../Resources/MainMenu.gorm/objects.gorm objects.gorm)
       (copy ../../../Resources/MainMenu.gorm/data.classes data.classes)
       (copy ../../../Resources/MainMenu.gorm/data.info data.info))))
)


main.ml

open Foundation
open AppKit
open Camlkit
open Runtime

module Delegate = Appkit_AppDelegate.Create (App_delegate)

let main () =
	let _ = new_object "NSAutoreleasePool"
    and app = NSApplication.self |> NSApplicationClass.sharedApplication
    and argc = Array.length Sys.argv
    and argv =
       Sys.argv
       |> Array.to_list
       |> Objc.(CArray.of_list string)
       |> Objc.CArray.start
    in
    app |> NSApplication.setDelegate (_new_ Delegate._class_);
    app |> NSApplication.activateIgnoringOtherApps true;
     
    _NSApplicationMain argc argv |> exit

let () = main ()


app_delegate.ml

open Foundation
open Runtime
open AppKit

let app_name = "gnustep-app"
let class_name = "MyAppDelegate"

let on_before_start _ = ()

let on_started notification =
   let app = NSNotification.object_ notification in
   let win = Main_window.create app in
   win |> NSWindow.setTitle (new_string app_name);
;;

let on_before_terminate _ = ()

let terminate_on_windows_closed _ = true


main_window.ml

(* NSOpenGLView Triangle *)
open Foundation
open AppKit
open Runtime

open Egl
open Gles3
open Gles3.Type
open Shaders
open Buffers

let w = 400. and h = 400.
let gwidth = ref 400 and gheight = ref 350

let make_glView ~frame =
    let glView =
    _new_ NSOpenGLView.self in
    glView |> NSView.setFrame frame;
    glView

let light_shader =
    ("light_shader",
    [{ name = "vertex_main";
        ty   = gl_vertex_shader;
        src  = "
        in vec3  in_position;
      
        void main()
        {
            gl_Position = vec4(in_position, 1.0);
         }"};
         { name = "fragment_main";
           ty   = gl_fragment_shader;
           src  = "
        out vec4 FragColor;
      
        void main()
        {
            FragColor = vec4(0.0, 0.0, 1.0, 1.0);
          }"};
        ])

let vertices : Gles3.float_bigarray = Buffers.to_float_bigarray
    [| -0.5;-0.5;0.0; 0.0; 0.5;0.0; 0.5;-0.5;0.0; |]

let create app =
    let win =
    alloc NSWindow.self
    |> NSWindow.initWithContentRect
        (CGRect.make ~x: 0. ~y: 0. ~width: w ~height: h)
        ~styleMask: Bitmask.(
            _NSWindowStyleMaskTitled +
            _NSWindowStyleMaskClosable +
            _NSWindowStyleMaskResizable)
        ~backing: _NSBackingStoreBuffered
        ~defer: false
    and glView =
        make_glView
        ~frame: (CGRect.make ~x: 0. ~y: 50. ~width: w ~height: (h -. 50.))
    in
    win
    |> NSWindow.cascadeTopLeftFromPoint (CGPoint.init ~x: 200. ~y: 800.)
    |> ignore;
    win |> NSWindow.makeKeyAndOrderFront nil;

    win |> NSWindow.contentView |> NSView.addSubview glView;
    let glcontext = glView |> NSOpenGLView.openGLContext
    in
        glcontext|> NSOpenGLContext.makeCurrentContext;

        clear_color { r = 0.7; g = 0.7; b = 0.7; a = 1.0 };
        clear [ gl_color_buffer];

        let prg : unit program = compile light_shader in
        let prg = Shaders.float_cst_attr prg "in_position" vertices in
        Shaders.draw_arrays prg gl_triangles 3;

        glcontext |> NSOpenGLContext.flushBuffer;

    win
;;

Resources については、以前の記事参照。

Camlkit & GNUstep ImageView

camlkitとGNUstepを使用して、imageを表示してみました。
NSImageView を使用している。

[実行結果]

画像はGNUstep.tiff
button, label(TextField), image, image付きbuttonを表示しています。

プロジェクト構成
(dune init project で作成。前回同様、build, exec の後lib, test フォルダを削除。)

/bin
   dune
   main.ml
GNUstep.tiff
dune-project
(プロジェクト名).opam


dune

(executable
 (public_name camlkitGSTestImageView)
 (name main)
    (flags -ccopt -L/usr/local/GNUstep/System/Library/Libraries -cclib -lgnustep-base -cclib -lgnustep-gui)
 (libraries
    camlkitGS.Foundation
    camlkitGS.AppKit))


main.ml

open Foundation
open Runtime
open AppKit

let win_width = 400.
let win_height = 300.

let app_window () =
  let win =
    alloc NSWindow.self
    |> NSWindow.initWithContentRect
      (CGRect.make ~x: 0. ~y: 0. ~width: win_width ~height: win_height)
      ~styleMask: Bitmask.(
        _NSWindowStyleMaskTitled +
        _NSWindowStyleMaskClosable +
        _NSWindowStyleMaskResizable)
      ~backing: _NSBackingStoreBuffered
      ~defer: false
  in
  win
  |> NSWindow.cascadeTopLeftFromPoint (CGPoint.init ~x:20. ~y:20.)
  |> ignore;
  win |> NSWindow.setTitle (new_string "Hello Caml");
  win |> NSWindow.makeKeyAndOrderFront nil;
  win

let make_button ~title ~frame ~target ~action =
  let btn = alloc NSButton.self |> NSButton.initWithFrame frame in
  btn |> NSControl.setTarget target;
  btn |> NSControl.setAction action;
  btn |> NSButton.setTitle title;
  btn

let make_button2 ~frame ~target ~action =
  let btn = alloc NSButton.self |> NSButton.initWithFrame frame
  and image =
    alloc NSImage.self |> NSImage.initWithContentsOfFile (new_string "GNUstep.tiff") in
  btn |> NSControl.setTarget target;
  btn |> NSControl.setAction action;
  btn |> NSButton.setImage image;
  btn  

let make_label ~frame ~text =
  let label = _new_ NSTextField.self in
  label |> NSView.setFrame frame;
  label |> NSControl.setStringValue text;
  label |> NSTextField.setBezeled false;
  label |> NSTextField.setDrawsBackground false;
  label |> NSTextField.setEditable false;
  label

let create_image ~file =
  let image =
  alloc NSImage.self |> NSImage.initWithContentsOfFile file in
  image

let make_imageView ~frame ~image =
  let imageView =
  _new_ NSImageView.self in
  imageView |> NSView.setFrame frame;
  imageView |> NSImageView.setImage image;
  imageView

let main () =
  let _ = new_object "NSAutoreleasePool"
  and app = NSApplication.self |> NSApplicationClass.sharedApplication
  and win = app_window ()
  in
  let btn =
    make_button
      ~title:(new_string "Quit")
      ~target:app
      ~action:(selector "terminate:")
      ~frame:(CGRect.make
        ~x:10. ~y:(win_height -. 40.)
        ~width:100. ~height:30.)
 
  and label =
    make_label
      ~frame:(CGRect.make
        ~x:10. ~y:(win_height -. 100.)
        ~width:100. ~height:30.)
      ~text: (new_string "Hello")
  and btn2 =
    make_button2
      ~target:app
      ~action:(selector "terminate:")
      ~frame:(CGRect.make
        ~x:10. ~y:(win_height -. 200.)
        ~width:100. ~height:30.)
  and image =
    create_image ~file: (new_string "GNUstep.tiff")
  in
  let imageView =
    make_imageView
      ~frame: (CGRect.make
      ~x:200. ~y:(win_height -. 200.)
      ~width:50. ~height:50.)
      ~image: image in
  win |> NSWindow.contentView |> NSView.addSubview btn;
  win |> NSWindow.contentView |> NSView.addSubview label;
  win |> NSWindow.contentView |> NSView.addSubview btn2;
  win |> NSWindow.contentView |> NSView.addSubview imageView;

  app |> NSApplication.activateIgnoringOtherApps true;
  NSApplication.run app

let () = main ()

Camlkit v0.2 & GNUstep (2)

前回報告した camlkitGS ライブラリを用いて、GNUstepのAppKit と CoreGraphics プログラムが実行できます。

[実行結果]
AppKit

CoreGraphics

AppKit プログラム
(dune init project で作成、その後libとtestを削除している。)

/bin
   dune
   main.ml

dune

(executable
 (public_name camlkitTest1)
 (name main)
   (flags -ccopt -L/usr/local/GNUstep/System/Library/Libraries -cclib -lgnustep-base -cclib -lgnustep-gui)
 (libraries
    camlkitGS.Foundation
    camlkitGS.AppKit))

main.ml

open Foundation
open Runtime
open AppKit

let win_width = 400.
let win_height = 300.

let app_window () =
  let win =
    alloc NSWindow.self
    |> NSWindow.initWithContentRect
      (CGRect.make ~x: 0. ~y: 0. ~width: win_width ~height: win_height)
      ~styleMask: Bitmask.(
        _NSWindowStyleMaskTitled +
        _NSWindowStyleMaskClosable +
        _NSWindowStyleMaskResizable)
      ~backing: _NSBackingStoreBuffered
      ~defer: false
  in
  win
  |> NSWindow.cascadeTopLeftFromPoint (CGPoint.init ~x:20. ~y:20.)
  |> ignore;
  win |> NSWindow.setTitle (new_string "Hello Caml");
  win |> NSWindow.makeKeyAndOrderFront nil;
  win

let make_button ~title ~frame ~target ~action =
  let btn = alloc NSButton.self |> NSButton.initWithFrame frame in
  btn |> NSControl.setTarget target;
  btn |> NSControl.setAction action;
  btn |> NSButton.setTitle title;
  btn

let main () =
  let _ = new_object "NSAutoreleasePool"
  and app = NSApplication.self |> NSApplicationClass.sharedApplication
  and win = app_window ()
  in
  let btn =
    make_button
      ~title:(new_string "Quit")
      ~target:app
      ~action:(selector "terminate:")
      ~frame:(CGRect.make
        ~x:10. ~y:(win_height -. 40.)
        ~width:100. ~height:30.)
  in
  win |> NSWindow.contentView |> NSView.addSubview btn;
  (*
  assert (app |> NSApplication.setActivationPolicy
    _NSApplicationActivationPolicyRegular);
  *)
    app |> NSApplication.activateIgnoringOtherApps true;
  NSApplication.run app

let () = main ()


CoreGraphics プログラム

/bin
   dune
   main.ml
   ovals.ml
   roundRects.ml

これは、gnustep/libs-opalのTestsにあるpdf.mサンプルです。

dune

(executable
   (public_name camlkitTest2)
   (name main)
   (flags -ccopt -L/usr/local/GNUstep/Local/Library/Libraries  -cclib -lgnustep-corebase -cclib -lopal)
   ;(flags -ccopt -L/usr/local/GNUstep/Local/Library/Libraries  -cclib -lopal)
   (libraries
     camlkit-base.runtime
     camlkitGS.CoreFoundation
     camlkitGS.CoreGraphics))

main.ml

open CoreFoundation
open CoreGraphics

open Runtime
open Objc

let _MyCGPDFContextCreateWithURL = Foreign.foreign "CGPDFContextCreateWithURL" ((ptr void) @-> (ptr void) @-> (ptr void) @-> returning (ptr CGContext.t))
let _MyCGPDFContextClose = Foreign.foreign "CGPDFContextClose" ((ptr CGContext.t) @-> returning void)
let _MyCGContextBeginTransparencyLayer = Foreign.foreign "CGContextBeginTransparencyLayer" ((ptr CGContext.t) @-> (ptr void) @-> returning void)

let pi = 3.141592

let _ = print_endline "Test CoreGraphics"
let str = "test.pdf"

let rec drawRect ctx rect =
   let origin = CGRect.origin rect
   and size = CGRect.size rect
   in
   let x = CGPoint.x origin
   and y = CGPoint.y origin
   and width  = CGSize.width size
   and height = CGSize.height size
   in
   _CGContextTranslateCTM ctx x y;
	_CGContextSetRGBFillColor ctx 0. 0. 0. 1.;
	_CGContextFillRect ctx (CGRect.make ~x:0. ~y:(height/.2.) ~width:width ~height:(height/.2.));

   _CGContextSetAlpha ctx 0.5;
   _MyCGContextBeginTransparencyLayer ctx null;
   

   let a = 0.9 *. width /. 4.
	and b = 0.3 *. height /.2.
	and count = 5
   in
   _CGContextSetRGBFillColor ctx 0. 0. 1. 1.;
   _CGContextSetRGBStrokeColor ctx 0. 0. 0. 1.;
   _CGContextSetLineWidth ctx 3.;
	_CGContextSaveGState ctx;
	_CGContextTranslateCTM ctx (width/.4.) (height/.2.);

   let r = CGRect.make ~x:(-.a) ~y:(-.b) ~width:(2.*.a) ~height:(2.*.b)
   in
   for i = 1 to 5 do
      Ovals.paintOval ctx r;
      Ovals.frameOval ctx r;
      _CGContextRotateCTM ctx (pi /. (Float.of_int count));
   done;

   _CGContextRestoreGState ctx;
   _CGContextEndTransparencyLayer ctx;

   _CGContextSetRGBFillColor ctx 1. 0. 0. 0.5;
	_CGContextSetRGBStrokeColor ctx 0. 0. 0. 1.;
	_CGContextSetLineWidth ctx 3.;
	_CGContextSaveGState ctx;
	_CGContextTranslateCTM ctx (width/.4. +. width/.2.) (height/.2.);

   for i = 1 to 5 do
      RoundRects.fillRoundedRect ctx r 20. 20.;
      RoundRects.strokeRoundedRect ctx r 20. 20.;
      _CGContextRotateCTM ctx (pi /. (Float.of_int count));
   done;
;;

let () =
   let nsurl =
      alloc NSURL.self |> NSURL.initFileURLWithPath (new_string str)
   in
   Printf.printf "nsurl\n";

   let ctx =
      _MyCGPDFContextCreateWithURL
         nsurl
         null
         null
   and rect = (CGRect.make ~x:0. ~y:(2.25*.72.) ~width:(8.5*.72.) ~height:(5.5*.72.))
   in
   Printf.printf "context\n";

   drawRect ctx rect;

   _MyCGPDFContextClose ctx;

ovals.ml

open CoreFoundation
open CoreGraphics

open Runtime
open Objc

let pi = 3.141592

let addOvalToPath ctx rect =
   _CGContextSaveGState ctx;

   let origin = CGRect.origin rect
   and size = CGRect.size rect
   in
   let x = CGPoint.x origin
   and y = CGPoint.y origin
   and width  = CGSize.width size
   and height = CGSize.height size
   in
   Printf.printf "addOvalToPath width  %f\n" width;
   Printf.printf "addOvalToPath height %f\n" height;

   let width2  = width/.2.
   and height2 = height/.2.
   in
   let matrix = _CGAffineTransformMake width2 0. 0. height2 (x+.width2) (y+.height2)
   in
   Printf.printf "addOvalToPath\n";
   
   _CGContextConcatCTM ctx matrix;
   _CGContextBeginPath ctx;
   _CGContextAddArc ctx 0. 0. 1. 0. (2.*.pi) 0;
   _CGContextRestoreGState ctx;
;;

let paintOval ctx rect =
   addOvalToPath ctx rect;
   _CGContextStrokePath ctx;
;;
let frameOval ctx rect =
   addOvalToPath ctx rect;
   _CGContextFillPath ctx;
;;

roundRects.ml

open CoreFoundation
open CoreGraphics

open Runtime
open Objc

let pi = 3.141592

let addOvalToPath ctx rect =
   _CGContextSaveGState ctx;

   let origin = CGRect.origin rect
   and size = CGRect.size rect
   in
   let x = CGPoint.x origin
   and y = CGPoint.y origin
   and width  = CGSize.width size
   and height = CGSize.height size
   in
   Printf.printf "addOvalToPath width  %f\n" width;
   Printf.printf "addOvalToPath height %f\n" height;

   let width2  = width/.2.
   and height2 = height/.2.
   in
   let matrix = _CGAffineTransformMake width2 0. 0. height2 (x+.width2) (y+.height2)
   in
   Printf.printf "addOvalToPath\n";
   
   _CGContextConcatCTM ctx matrix;
   _CGContextBeginPath ctx;
   _CGContextAddArc ctx 0. 0. 1. 0. (2.*.pi) 0;
   _CGContextRestoreGState ctx;
;;

let paintOval ctx rect =
   addOvalToPath ctx rect;
   _CGContextStrokePath ctx;
;;
let frameOval ctx rect =
   addOvalToPath ctx rect;
   _CGContextFillPath ctx;
;;

let addRoundedRectToPath ctx rect ovalWidth ovalHeight =

	if ovalWidth == 0. || ovalHeight == 0. then 
		_CGContextAddRect ctx rect
   else
	
	_CGContextSaveGState ctx;
   	_CGContextTranslateCTM ctx (_CGRectGetMinX rect) (_CGRectGetMinY rect);
	_CGContextScaleCTM ctx ovalWidth ovalHeight;

	let fw = (_CGRectGetWidth rect) /. ovalWidth
	and fh = (_CGRectGetHeight rect) /. ovalHeight
   in
	
	_CGContextMoveToPoint ctx fw (fh/.2.);
	_CGContextAddArcToPoint ctx fw fh (fw/.2.) fh 1.;
	_CGContextAddArcToPoint ctx 0. fh 0. (fh/.2.) 1.;
	_CGContextAddArcToPoint ctx 0. 0. (fw/.2.) 0. 1.;
	_CGContextAddArcToPoint ctx fw 0. fw (fh/.2.) 1.;
	
	_CGContextClosePath ctx;
	_CGContextRestoreGState ctx;
;;

let fillRoundedRect ctx rect ovalWidth ovalHeight =
	_CGContextBeginPath ctx;
	addRoundedRectToPath ctx rect ovalWidth ovalHeight;
	_CGContextFillPath ctx;
;;

let strokeRoundedRect ctx rect ovalWidth ovalHeight =
	_CGContextBeginPath ctx;
	addRoundedRectToPath ctx rect ovalWidth ovalHeight;
	_CGContextStrokePath ctx;
;;

Camlkit v0.2 & GNUstep

GhostBSD の新しい バージョン、GhostBSD-24.07.3 がリリースされました。
これまで利用していた devtools (os-generic-userland-devtools) が変更になって
います。devtools をインストールするには、以下のようにします。
 sudo pkg install -g 'GhostBSD*-dev'

Camlkit のバージョンが v0.2 にバージョンアップされました。
Foundation, AppKit とともに CoreFoundation, CoreGrahpics も、インストールした
ライブラリから利用できるようになっています。

しかしながら、このライブラリを GNUstep で利用する場合、実行時にエラーが
出ます。
これは、このライブラリに GNUstepインプリメントされていない文が含まれているためです。これらはFoundation_globals.ml, AppKit_globals.ml 等で定義されています。

v0.1 ではこれらのファイルを修正して利用するこができましたが、v0.2 ではこれらのファイル(モジュール)がライブラリ内で読み込まれるため、修正することができません。
このため、GNUstep で利用するには、これらを修正したローカルライブラリを作成し、opam でインストールして利用します。

GNUstepのインストール
libobjc2, Foundation, AppKitライブラリ、ビルドツール、gnustepアプリケーションは、前回同様、GhostBSDのSoftware Stationからインストールします。

ビルドツールを利用するため、環境設定を行います。
Homeフォルダの .profile に以下の行を追加します。
. /usr/local/GNUstep/System/Makefiles/GNUstep.sh

CoreFoundation, CoreGraphics, CoreAnimationのライブラリは、ソースファイルからビルド、インストールします。
インストール先:/usr/local/GNUstep/Local/Library/

CoreFoundation
gnustep/libs-corebase
ビルド、インストールは以下のようにします。
./configure
gmake
sudo gmake install

このライブラリはlibdispatchライブラリに依存しているので、これを先に
インストールします。
しかしここで問題が起こります。
問題点
FreeBSD系のシステムでは、libdispatchとgnustepはconflictします。
libdispatchをインストールするとgnustepが削除されます。また逆も同様です。
そこで一旦libdispatchをインストールし、Homeフォルダにコピーしておきます。
コピーするファイル
/usr/local/include/dispatch, /os フォルダ
/usr/local/lib/libdispatch.so ファイル
その後、gnustep をインストールし、コピーしてあったdispatch 関連ファイルを戻します。
これで CoreFoundation のビルドができます。

CoreGraphics
gnustep/libs-opal
gmake
sudo -E gmake install

CoreAnimation
gmake
sudo -E gmake install

Camlkit ローカルライブラリの作成
1dune project を作成
 dune init project camlkitGS
 (camlkitGSがライブラリ名になる)
 作成時はcamlkitGS.opam は空なので、dune buildで再生成する。

2lib, test フォルダを削除
 lib, test フォルダは使用しないので、削除する。

3 必要ライブラリをコピー
 camlkitのソースファイルから必要なライブラリをコピーする。
 camlkit ソースファイルは、https://github.com/dboris/camlkit からダウンロード。
 runtime, Foundation, Appkit, Appkit_extra, camlkit, CoreFoundation,
 CoreGraphics, CoreAnimationをコピー。

4各ライブラリの修正必要なファイルの編集
 修正が必要なファイル
 各ライブラリのduneファイル
 Foundation_globals.ml
 AppKit_globals.ml
 CoreFoundation_fn.ml
 CoreFoundation_globals.ml
 CoreGraphics_fn.ml
 CoreGraphics_globals.ml
 具体的な編集箇所は以下のファイルを参照して下さい。
 https://drive.google.com/file/d/1tMgyGonrLQCiBFo2WC3STwqKoyDvNMci/view?usp=sharing


5 binフォルダをコピー
 binフォルダにあるdune, main.ml を修正する。

dune
(executable
 (public_name camlkitv02_test1)
 (name main)
   (flags -ccopt -L/usr/local/GNUstep/System/Library/Libraries -cclib -lgnustep-base -cclib -lgnustep-gui)
 (libraries Foundation AppKit))

 

main.ml
open Foundation
open Runtime
open AppKit

let win_width = 400.
let win_height = 300.

let app_window () =
  let win =
    alloc NSWindow.self
    |> NSWindow.initWithContentRect
      (CGRect.make ~x: 0. ~y: 0. ~width: win_width ~height: win_height)
      ~styleMask: Bitmask.(
        _NSWindowStyleMaskTitled +
        _NSWindowStyleMaskClosable +
        _NSWindowStyleMaskResizable)
      ~backing: _NSBackingStoreBuffered
      ~defer: false
  in
  win
  |> NSWindow.cascadeTopLeftFromPoint (CGPoint.init ~x:20. ~y:20.)
  |> ignore;
  win |> NSWindow.setTitle (new_string "Hello Caml");
  win |> NSWindow.makeKeyAndOrderFront nil;
  win

let make_button ~title ~frame ~target ~action =
  let btn = alloc NSButton.self |> NSButton.initWithFrame frame in
  btn |> NSControl.setTarget target;
  btn |> NSControl.setAction action;
  btn |> NSButton.setTitle title;
  btn

let main () =
  let _ = new_object "NSAutoreleasePool"
  and app = NSApplication.self |> NSApplicationClass.sharedApplication
  and win = app_window ()
  in
  let btn =
    make_button
      ~title:(new_string "Quit")
      ~target:app
      ~action:(selector "terminate:")
      ~frame:(CGRect.make
        ~x:10. ~y:(win_height -. 40.)
        ~width:100. ~height:30.)
  in
  win |> NSWindow.contentView |> NSView.addSubview btn;
  (*
  assert (app |> NSApplication.setActivationPolicy
    _NSApplicationActivationPolicyRegular);
  *)
    app |> NSApplication.activateIgnoringOtherApps true;
  NSApplication.run app

let () = main ()

5ビルドと実行
 ビルド、実行します。
 dune build
 dune exec ./bin/main.exe

6ライブラリのインストール
 opam ライブラリとしてインストールします。
 opam install .
 Homeフォルダの .opam/default/lib/ にインストールされる。