🔖

Lem Advent Calendar 2023 - Frontend/Backend間のInterfaceと各Frontendについて

2023/12/02に公開

これは Lem Advent Calendar の記事です。

概要

一日目で紹介したように、LemはBackendとFrontendから構成されています。
Backendがエディタの機能を担い、Frontendはキーボードやマウス入力、画面表示などを担います。

インターフェースはlem-interfaceパッケージ内でgeneric functionとして定義されています。
https://github.com/lem-project/lem/blob/b479da1311bee951443250b09c30548cf16ed33e/src/interface.lisp#L37C2-L37C2

Frontendではこれらのインターフェースに対応するメソッドを定義していくことになります。

インターフェース詳細

view

windowに対応するviewというオブジェクトが存在します。
viewはbackend/frontend間でwindow情報をやりとりするために使われ、定義は各フロントエンド毎に行われます。
viewはx, y, width, heightなどの値を保持したクラスや構造体となることを想定しています。

メソッド一覧

以下は代表的なメソッドの一覧です。

メソッド名 説明
make-view 新しいwindowが作られたときに呼び出されます
delete-view windowが削除されたときに対応するviewが引数になり呼び出されます
set-view-pos windowの位置が変更されるときに呼び出され、引数はview, x, yになります
set-view-size windowの大きさが変更されるときに呼び出され、引数はview, width, heightになります
render-line 指定のviewのx, yにテキストを描画します
clear 指定のviewをクリアします
clear-to-end-of-window 指定のviewのy行目以降をクリアします
get-background-color フロントエンドの背景色を取得します
get-foreground-color フロントエンドの前景色を取得します
update-foreground フロントエンドの前景色を変更します
update-background フロントエンドの背景色を変更します
update-cursor-shape カーソルの形を変更します
display-width ウィンドウの幅を取得します
display-height ウィンドウの高さを取得します
display-title ウィンドウタイトルを取得します
set-display-title ウィンドウタイトルを変更します
display-fullscreen-p フルスクリーンであるかを取得します
set-display-fullscreen-p フルスクリーンの設定をします

各フロントエンド

ncurses

Terminalでの入出力を行うライブラリとしてncursesがあります。
Common Lispではcl-charmsというライブラリがあり、low-level apiとしてncursesのffiが用意されており、lemではこれを使っています。
最初期から存在し、最も長く保守され使われ続けている実装です。

Electron frontend

かつて、Lemにはelectron frontendが存在しました。
これを実現するためには、Common Lispの外部のプロセスと通信をする必要があります。
そのためフロントエンドの実装の一つにJSON-RPCを用意しました。
JSON-PRCによってelectronのプロセスと通信を行うことでlemにelectronを使うことができるようになりました。
electronを使うことにより、webviewでhtmlを表示することが出来たり、色々と可能性はありましたが、今は保守されておらず動きません。

SDL2

2023年5月のVer 2.0 リリース時に追加した実装です。
このリリースによってlemの可能性は大きく高まりました。

これまでいくつかのフロントエンド(xcb, opengl, capi, electron, etc...)が実験的に作られてきましたが、実際に使えるところまで作り込み、保守されつづけてきた実装はncursesだけでした。
SDL2によってようやくGUI版のLemをリリースできました。

SDL2 frontendには以下の特徴があります。

Windowsサポート

今までLemをwindows上で動かすことは面倒で動作も不完全でした。
2.0から複数のプラットフォームをサポートしているSDL2 frontendを実装したことにより、この問題は解決し、windows用のリリースを実現できました。

マウスのサポート

これまで実用できるfrontendはncursesのみでしたが、ncursesでマウスを使うには制限があります。
例えばncursesでクリックを入力として受け取れますが、ドラッグは入力として受け取れません。
このような理由で正式にマウスのサポートはしていませんでした。
SDL2を使うことにより、この問題は解消され、マウスによる操作が可能になりました。
(当たり前にある機能ですが、全部時前でやろうとすると色々と面倒なのです)

グラフィックス機能

特定のバッファやウィンドウにSDL2の機能を使って図形や画像を表示できます。

以下のgifはその機能を使った例です。

上記のgifで使ったソースコードを以下に貼ります。

(defpackage :graphics
  (:use :cl
        :lem
        :lem-sdl2/graphics))
(in-package :graphics)

(defvar *font*
  (sdl2-ttf:open-font
   (asdf:system-relative-pathname "lem-sdl2" "resources/fonts/NotoSansMono-Regular.ttf")
   100))

(defvar *image*
  (load-image
   (asdf:system-relative-pathname "lem-sdl2" "resources/icon.png")))

(defvar *buffer* (make-buffer "*example*"))

(clear-drawables *buffer*)

(draw-string *buffer* "Hello World" 10 10 :font *font* :color (make-color 255 255 0))

(draw-image *buffer* *image* :x 200 :y 50)

(loop :for n :from 10 :to 255 :by 20
      :do (draw-rectangle *buffer* n n 300 300 :color (make-color 0 0 0))
          (draw-rectangle *buffer* n n 300 300 :filled t :color (make-color n n n)))

open-fontやload-imageで事前にリソースを初期化しておき、draw-stringやdraw-image関数で描画できます。
この機能についての詳細は後日まとめます。

おわりに

二日目はこれで終わりです。
書いていると気付くのですが、設計が未熟な点やコードの汚なさが見つかり、改善点が出てきて良いですね。

Discussion