GtkDrawingArea Custom Drawing

前回の続き、GtkDrawingArea です。
フォトレタッチのようにマウス軌跡の連続線を GTK+ で描きたい。
って探すまでもなく GTK+ 3 Reference Manual に書いてあった。

Custom Drawing: GTK+ 3 Reference Manual

つまり軌跡を残すには draw シグナル引数にある cairo_t の中に描写しない。
サーフェス(面の意味)を別に作ってそちらに描写する、当然追記になる。
そのまま gtk_widget_queue_draw 関数を呼び出し draw シグナルを送る。
ハンドラにて先程のサーフェスを cairo_t にセットする。

解ってしまえばそりゃそうだ、みたいな。

しかし gjs へのバインディング記法がサッパリ解らない。
Gir の cairo は何もできないし、使おうとした人なら解ってくれるよね。
Gdk.cairo*** も GTK+ と同様に書き換えるとエラー。
cr.set_source_rgb みたいなバインドではないようだ、うーん。

installed-tests/js/testCairo.js ? master ? GNOME / gjs ? GitLab

散々遠回りをしてやっとこんなのを捜し出した。
リソースにある cairo を使えばいいってことね。
しかしコッチは GTK+ と違って C 言語とかなり違う表記を強いられる。

// C
// JavaScript

cairo_create (surface);
new Cairo.Context(surface);

cairo_set_source_surface (cr, surface, 0, 0);
cr.setSourceSurface(surface, 0, 0);

cairo_set_source_rgb (cr, 1, 1, 1);
cr.setSource(Cairo.SolidPattern.createRGB(1, 1, 1));

gdk_window_create_similar_surface
widget.window.create_similar_surface
//Gdk.Window.create_similar_surface // Error

CAIRO_CONTENT_COLOR
Cairo.Content.COLOR

てな感じ。

ということで、実際に上手くいったソース。

#!/usr/bin/gjs

// Resource
const System = imports.system;
const Cairo = imports.cairo;
// Gir
const GObject = imports.gi.GObject;
const GLib = imports.gi.GLib;
const Gio = imports.gi.Gio;
const Gtk = imports.gi.Gtk;
const Gdk = imports.gi.Gdk;

var DrawingWindow = GObject.registerClass({
    GTypeName: "DrawingWindow"
}, class DrawingWindow extends Gtk.ApplicationWindow {
    _init(app) {
        super._init({application: app});
        // var
        this.surface = null;
        // HeadreBar
        let hb = new Gtk.HeaderBar({show_close_button: true});
        let clearButton = new Gtk.Button({label: "Clear"});
        clearButton.connect("clicked", ()=> {
            this.clearSurface();
            this.canvas.queue_draw();
        });
        hb.pack_start(clearButton);
        this.set_titlebar(hb);
        // DrawingArea
        this.canvas = new Gtk.DrawingArea();
        this.canvas.set_events(
            Gdk.EventMask.BUTTON_PRESS_MASK |
            Gdk.EventMask.BUTTON_RELEASE_MASK |
            Gdk.EventMask.BUTTON1_MOTION_MASK |
            Gdk.EventMask.STRUCTURE_MASK );
        this.canvas.connect("configure-event", (widget, event)=> {
            this.surface = this.canvas.window.create_similar_surface(
                Cairo.Content.COLOR,
                widget.get_allocated_width(),
                widget.get_allocated_height() );
            this.clearSurface();
            return true;
        });
        this.canvas.connect("button-release-event", (widget, event)=> {
            return false;
        });
        this.canvas.connect("button-press-event", (widget, event)=> {
            if (event.get_button()[1] === 1) {
                let [res, x, y] = event.get_coords();
                hb.set_title(`${x}/${y}`);
                this.drawBrush(x, y);
            }
            return false;
        });
        this.canvas.connect("motion-notify-event", (widget, event)=> {
            let [res, x, y] = event.get_coords();
            hb.set_title(`${x}/${y}`);
            this.drawBrush(x, y);
            return false;
        });
        this.canvas.connect("draw", (widget, cr)=> {
            cr.setSourceSurface(this.surface, 0, 0);
            cr.paint();
            return false;
        });
        this.add(this.canvas);
        // this
        this.resize(400, 400);
        this.show_all();
    }
    drawBrush(x, y) {
        let cr = new Cairo.Context(this.surface);
        cr.arc(x, y, 10.0, 0.0, 2*Math.PI);
        cr.fill();
        //this.canvas.queue_draw();
        this.canvas.queue_draw_area(x-10, y-10, 20, 20);
    }
    clearSurface() {
        let cr = new Cairo.Context(this.surface);
        cr.setSource(Cairo.SolidPattern.createRGB(1, 1, 1));
        cr.paint();
    }
});

var DrawingApplication = GObject.registerClass({
    GTypeName: "DrawingApplication"
}, class DrawingApplication extends Gtk.Application {
    _init(v) {
        GLib.set_prgname("Program");
        super._init({
            application_id: "org.sasakima.drawwin",
            flags: Gio.ApplicationFlags.HANDLES_OPEN
        });
    }
    vfunc_startup() {
        super.vfunc_startup();
        new DrawingWindow(this);
    }
    vfunc_open(files, hint) {
        this.active_window.present();
    }
    vfunc_activate() {
        this.active_window.present();
    }
});

new DrawingApplication().run([System.programInvocationName].concat(ARGV));

で。

となります。
ImageSurface に画像を指定して文字入れ、なんてのもこの応用で可能。
フォトレタッチとはとても言えないけど手段は解ったということで。