LuminoEngine/Lumino

UIEventHandler の登録 API を整理する

lriki opened this issue · 0 comments

lriki commented

[2021/1/6] やっぱり現状維持にしてみる

Builder パターンの導入を始めたので、そっちで対応してみる。C++ から使うときはほとんどこのケースで足りる。

auto button = UIButton::Builder()
    .onClicked(handler)
    .build();

新しめの UI フレームワークは Builder パターンないしそれに近い、副作用の少ない方法で UI要素を構築するケースが多い。これで十分な感じ。

connectXX は主に Binding を作るとき、「これはイベントハンドラの登録関数です」を強調するのに使うことにしてみる。

イベント名は過去形?

ネイティブに近いフレームワークは過去形が多い傾向。
ただ過去形に統一しすぎると MouseDowned とかなじみ無い名前になる。

タイミングを区別するなら WPF の "Preview" Prefix のようなものを付ける、でいいかも。

Proposal

connectOnXXXX 及び onXXXX のインターフェイスを変更する。
例えば UIButton の Clicked イベントであれば、

Ref<EventConnection> connectOnClicked(Ref<UIEventHandler> handler);
virtual void onClicked(UIEventArgs* e) override;

UIButton* onClicked(Ref<UIEventHandler> handler, Ref<EventConnection>* outConnection = nullptr);
virtual void onClickedEvent(UIEventArgs* e) override;

ただし、on~ は他のモジュールでも使用している仮想関数実装の命名規則でもあるため、上記そのまま適用は難しいかもしれない。

候補としては、

  • setOnClicked
    • Ruby や C# binding で使うときに on_clicked や OnClicked となるため、他モジュールの命名規則と衝突する。
  • setClickedHandler
    • まだちょっと長いかも…。特にスクリプト系 Binding から見た時。
  • setClicked
    • 落としどころかも?通常プロパティと区別するため、イベント名には名詞を使わないように注意する必要がある。

Motivation

現在、ユーザープログラムから、イベントをハンドリングする方法は次の2通り。

  • A. connectOnXXXX にコールバックを登録する
  • B. コントロールを継承して仮想関数 onXXXX を実装する

ユーザープログラムから見ると、このうち A は非常によく使い、B はあまり使わない。
(A はコントロールを利用するための API であるが、B はコントロールをカスタマイズするための API)

そのため以下のような不満が出てきた。

  • A のほうが頻繁に使うのにタイプ量が多い
  • 戻り値が決まっているので Builder パターンのようなメソッドチェインが組めない
  • EventConnection はほとんどのケースで使用しないが、そうすると VisualStudio でインテリセンスの警告が出る → C26444

Note

on~ を採用しているものはそれなりにある。ただ、signal ではなく普通のプロパティとして単一のハンドラをセットするケースが多い印象。

C# の Event や、signal-slot の仕組みで作られているものは on~ ではないことが多いみたい。

WPF

  • MouseEnter += _handler

Material-UI

  • onClick = _handler

onClick の呼び出し元は handleClick.
ただ仮想関数という考え方ではない。

一番根っこは div の onClick にローカル定義した関数をセットしているだけ。
<div ... onClick={handleClick} ...>

JavaScript

Flutter

  • onPressed: _handler

Unity(UIElements)

  • clicked += _handler
    ※ onClick もあるが、Obsolete としてマークされている。また、コンストラクタでもハンドラを受け取れる。

これらはすべて C# の event として実装されている。

  • Button.clicked
  • ListView.onSelectionChanged

仮想関数は次のような命名になっている。

  • Clickable.OnMouseDown()
  • Clickable.OnMouseMove()

UE4

  • (BP) On Clicked
  • (Slate) OnClicked(_handler)

イベントと仮想関数の命名規則に区別はない。

  • OnMouseButtonDown()
  • OnPaint()
  • OnClicked
  • OnAcceptDrop
  • OnPaintHandler

Kivy

  • (.kv) on_press: _handler
  • (.py) on_press=_handler (コンストラクタ名前付き引数)
  • (.py) b.bind(on_press=anim_btn)

イベントと仮想関数の命名規則に区別はない。

  • on_touch_down()
  • on_touch_move()
  • on_touch_up()
  • on_press:
  • on_release:

Qt

  • (QWidgets) connect(a, SIGNAL(valueChanged()), b, SLOT(_handler));
  • (QML) onClicked: _handler

Android

  • setOnClickListener(_handler)

UIKit

  • button.addTarget(self, action: #selector(_handler),

GTK

  • g_signal_connect (button, "clicked", G_CALLBACK (_handler), (gpointer) "button");

Rust azul

let button = Button::with_label("+1").dom().with_id("button")
      .with_callback(On::MouseUp, Callback(update_counter));

Rust gtk

button.connect_clicked(move |_| { ... });

Rust iced

Button::new(&mut self.reset_button, Text::new("reset"))
    .on_press(Message::Reset),

OrbTk iced

self.on_mouse_up(move |p| { ... });

Note

現時点で Event<> な変数は次の通り。

canUndoChanged;
canRedoChanged;

m_onCollisionEnter;
m_onCollisionLeave;
m_onCollisionStay;
m_onUIEvent;
m_onChecked;
m_onUnchecked;

m_onActivated;
m_onDeactivated;

m_onSubmit;
DragStarted;
DragDelta;
DragCompleted;
DragCanceled;

m_onItemSubmitted;
m_onGenerateTreeItem;

m_onCanExecuteChanged;
m_onCanExecuteEvent;
m_onExecuteEvent;

m_onClosed;
m_onImGuiLayer;

m_onClick;
m_onItemClick;
m_onSelectionChanged;
m_onSelectedTabChanged;

m_onRender

onGenerateTreeItem, onRender あたりは他の名前にし辛いかも。

  • (C++) setRender(...)
  • (C#) Render += ...
  • (Ruby) render = ...

onUIEvent は動詞でもないケースなので、Ruby とかだと普通の変数と区別ができなくなる。
スクリプト系だとこのあたりの視認性が悪くなるのはマイナス。