T time

プログラミングや電子工作、各種ガジェットに関するブログです。

PebbleTime 開発 - HelloWorld のコード説明

前回 は、CloudPebble を使って HelloWorld を作成し、エミュレータで動作確認するところまで見ました。

今回は、コードを説明していきます。
基本なので良く理解してください。

ソースコード

これが hello_world.c の全コードです。

コードの説明です。

include <pebble.h>

#include <pebble.h>

Pebble SDK を使用する場合は、pebble.h をインクルードしてください。 それにより API や型が定義されます。

なお、自分で作成したヘッダは #include "foo.h" でインクルードできます。

main 関数

まずは main 関数ですね。 これがないと始まりません。

int main(void) {
    handle_init();
    app_event_loop();
    handle_deinit();
}

この中で handle_inithandle_deinit はこのファイルで定義した関数ですね。 それぞれ、Watchface 起動時に必要な資源の確保と、終了時の資源開放処理です。

では app_event_loop は何でしょう?

app_event_loop 関数

これはいわゆるイベントループ関数です(関数名そのままですが)。 GUI のプログラムを作ったことがある人はおなじみですね。

Pebble のアプリは、Watchface にしろ Watchapp*1 にしろ様々なイベントを受け取って動いています。 ボタンの入力、加速度センサー、タイマーやスマートフォンからの通信など。 それらのイベントはいつ発生するか分からないので*2、 全てのイベントを待ち続ける必要があります。 また、イベントが発生したらすぐに処理する必要もあります。 ボタンが押されてから 1 分後に画面が変わるのでは、その Watchface は使えません。

それらの取りまとめは大変なので、SDK が代わりにやってくれます。 それがこの関数です。

なお、この関数は必ず実行する必要があります
Watchface を作るときは、main 関数はこれと同じにすると良いでしょう。

app_event_loop に関する詳細はオンラインマニュアルを参照してください。
http://developer.getpebble.com/docs/c/Foundation/App/#app_event_loop

この関数にかぎらず、SDK に関することは全てオンラインマニュアルに載っているので、一読すると良いです。全て英語ですが…。

handle_init 関数

では、main から呼ばれている handle_init を見て行きましょう。

この関数では以下の処理を行っています。

  1. window の生成
  2. text_layer の生成
  3. text_layer に表示する文字や、フォントの情報を設定
  4. text_layer を window 内の root_layer の子レイヤーに登録
  5. window を Window Stack に登録
  6. ログを表示

レイヤーモデル

Watchface の画面は、以下の様なレイヤーモデルで作られています。

f:id:tatur0u:20150705194540p:plain

2015-07-05 追記
上記の図は、text_layer の背景が透明となっていましたが、実際には白色でした。 修正した図に差し替えました。

まず、土台なる window があり、そこには必ず root_layer が乗っています。 これだけだと何も表示しないので、今回はさらにその上に text_layer が乗せて文字を表示しています。

Watchface を作るときは、複数のレイヤーを重ねて一つの画面を作ります。

レイヤーとは文字や画像を表示するためのシートのようなもので、レイヤー同士を重ねることができます。

2015-07-05 修正
レイヤーは透明なので、重ねてもその下のレイヤーが見えます。もちろん色を塗って下のレイヤーを見せないようにすることもできます。 レイヤーは、背景を透明にして下のレイヤーを見せることもできますし、色を塗って見せないようにすることもできます。

root_layertext_layer の背景はデフォルトで白色です。

Window

では、window を作成するコードを見てみましょう。 handle_init 関数の先頭で作成しています。

Window *window;

void handle_init(void) {
    window = window_create();

    ....

    window_stack_push(window, true);
}

windowWindow 型のポインタです。
window_create 関数により作成します。

その後、window_stack_push しています。これは何でしょう。

ウィンドウ・スタックモデル

先ほど、レイヤーモデルを紹介しました。 土台となる window があるのでした。

その window は、それ自体も重ねることができます。

f:id:tatur0u:20150702221029p:plain

レイヤーは、重ねることで画面を作るのでした。Window は何のために重ねるのでしょう。

Window は、別の画面を表示したいときに重ねます。 たとえば設定画面を表示するときなどです。 非透明のレイヤーを重ねても別の画面を表示できるのですが、Window を重ねるとボタンの制御を作りやすいという利点があります。 今回は Window はひとつですし、Watchface なのでボタンは使わないですが、今後の記事でそれらも取り上げていきます。

Window を重ねるときは window_stack_push を使います。第1引数には重ねたい Window を、第2引数には重ねた時にアニメーションさせるか否かを truefalse で設定します。 true を設定した場合は、Window が右から左へ滑り込むように表示されます*3

TextLayer

次は text_layer を生成するコードを見てみましょう。text_layer は文字を表示するためのレイヤーです。

TextLayer *text_layer;

void handle_init(void) {
    ....
    text_layer = text_layer_create(GRect(0, 0, 144, 154));
    ....
}

text_layerTextLayer 型のポインタです。 text_layer_create 関数により作成します。

第1引数の GRect(0, 0, 144, 154) はレイヤーの位置と大きさです。 この例だと「位置 0,0 に大きさ 144x154 のレイヤーを作成する」となります。

Pebble SDK にはいくつかの種類のレイヤーが定義されていますが、どれも XxxLayer 型のポインタを定義して xxx_layer_create 関数で作成します。

TextLayer に表示する文字列の設定

その後、text_layer を第1引数に取る3つの関数を実行しています。

void handle_init(void) {
    ....
    text_layer_set_text(text_layer, "Hi, I'm a Pebble!");
    text_layer_set_font(text_layer, fonts_get_system_font(FONT_KEY_GOTHIC_28_BOLD));
    text_layer_set_text_alignment(text_layer, GTextAlignmentCenter);
    ....
}

これらは表示する文字列に関する設定です。

関数名 説明
text_layer_set_text 表示する文字列を設定します。
今回は "Hi, I'm a Pebble!" と表示します。
text_layer_set_font 表示するフォントを設定します。
今回は GOTHIC フォントを28ポイントの大きさで太字で表示します。
text_layer_set_text_alignment 表示する位置を設定します。
今回は中央に寄せて表示します。

TextLayer を root_layer に重ねる

TextLayer は作っただけでは表示できません。root_layer に重ねることにより、初めて表示されます。

void handle_init(void) {
    ....
    layer_add_child(window_get_root_layer(window), text_layer_get_layer(text_layer));
    ....
}

レイヤーを重ねるには layer_add_child を使います。 第1引数に土台となるレイヤーを、第2引数に重ねたいレイヤーを設定します。

今回は root_layertext_layer を重ねたいのですが何やら複雑なコードです。 何をやっているのでしょう。

これは、Layer を取得しているのです。

引数 コード 説明
第1引数 window_get_root_layer(window) window から root_layer(Layer型)を取得する
第2引数 text_layer_get_layer(text_layer) text_layer から Layer を取得する

ここで layer_add_child の定義を見てみましょう。

void layer_add_child ( Layer * parent, Layer * child )

http://developer.getpebble.com/docs/c/User_Interface/Layers/#layer_add_child

どちらの引数も Layer 型を要求しています。

text_layerTextLayer 型なのでそのままでは渡せません。 そこで text_layer_get_layer 関数を用いて text_layer に含まれる Layer 型のオブジェクトを取得して渡しています。

このように、XxxLayer 型のオブジェクトから Layer を取得するには、xxx_layer_get_layer 関数を使用します。

ログ表示

handle_init 関数の最後では、ログを表示しています。

void handle_init(void) {
    ....
    APP_LOG(APP_LOG_LEVEL_DEBUG, "Just pushed a window!");
}

APP_LOG はログを表示するための関数(実際にはマクロ関数)で、第1引数にログレベルを、第2引数にログ文字列を設定します。

ログレベルというのは、ログの緊急度です*4。以下の5つが定義されています。

ログレベル 説明
APP_LOG_LEVEL_ERROR エラーが発生したことを通知する。
APP_LOG_LEVEL_WARNING 何らかの警告を通知する。
APP_LOG_LEVEL_INFO 一般的な情報を通知する。
APP_LOG_LEVEL_DEBUG デバッグ用のログを表示する。
APP_LOG_LEVEL_DEBUG_VERBOSE デバッグログの詳細を表示する。

レベルは定義されていますが、今のところレベルでフィルタしたりできないので、あまり気にすることはないです。 作成する Watchface 内で意味が統一されていれば良いでしょう。

ちなみに、今回の場合は以下のように表示されます。

[DEBUG] hello_world.c:23: Just pushed a window!

APP_LOG の詳細に関しては、以下のオンラインマニュアルを参照してください。
http://developer.getpebble.com/docs/c/Foundation/Logging/#APP_LOG

handle_deinit 関数

handle_init の説明はちょっと長かったですね。

handle_deinit は短いのでご安心を。 やっているのはこれだけです。

  1. text_layer の削除
  2. window の削除

使っている API はオンラインマニュアルを見て理解してください。

終わり。

まとめ

今回のまとめです。

  • #include <pebble.h> は必須
  • main 関数は毎回この形
  • ウィンドウ・スタックモデルとレイヤーモデルがある
  • windowroot_layer というレイヤーを持っている
  • レイヤーは root_layer に重ねないと表示されない
  • ログにはレベルがある(が今のところ気にしなくてよい)
  • XxxLayer 型の API 名には規則性がある
  • APIリファレンスはここ

あとがき

Watchface の画面の構造がイメージできたでしょうか。 まだ説明が足りないところもありますが、今後の記事で説明していきます。

次回は、このコードを修正して時間を表示してみたいと思います。


PebbleTime は、公式サイトでプレオーダーを受付中です。 2015-07-03 現在で $199.99 です。

getpebble.com


Amazon.co.jp並行輸入品が出てますね。 ただ、正直高いです。 公式サイトで買うことをおすすめします。


こちらは古い白黒のやつ。 見た目の違いで2種類あります(性能は同じ)。

Pebble Steel (Brushed Stainless)

Pebble Steel (Brushed Stainless)

*1:Watchfaceは時計盤で、Watchappはアプリです。

*2:一般的に「イベントが非同期に発生する」といいます

*3:いわゆるワイプトランジションです。

*4:syslog をご存知の方ならイメージしやすいと思います。