デジタル時計は簡単に作れたので、次はアナログ時計を作りたい。
アナログ時計は、文字盤と針があればいける。文字盤はビットマップイメージを貼れればOKだろう。針は…グラフィックで線を描けばいいのかな?
とりあえず、線を引いてみたいので、グラフィック関連のAPIを確認する。
グラフィック関連のAPIは http://developer.getpebble.com/docs/c/Graphics/ このあたり。
線も引けるし円も書けるようですね。そしたら文字盤もこれで書いちゃっていいかもという感じです。
よし、やってみるか、と思ったのだけど、これまで出てこなかった「GContext」を取得する必要があるようです。TextLayerの時のように、単純に表示したいものをセットすればいい、というのとは少々異なっていそう。
むむ、「チュートリアル1をベースにしてtick_handlerで針を書けばいいんじゃないの?」程度に思ってたら、そんな簡単ではないようでした。
こういうときはサンプルプログラムを見てみましょう。
「PROJECT」の「CREATE」時に、テンプレートとしていろいろなサンプルプログラムが選択できます。
「Draw Bitmap」を選んでみました。
/*
* The original source image is from:
* <http://openclipart.org/detail/26728/aiga-litter-disposal-by-anonymous>
*
* The source image was converted from an SVG into a RGB bitmap using
* Inkscape. It has no transparency and uses only black and white as
* colors.
*/
#include "pebble.h"
static Window *s_main_window;
static Layer *s_image_layer;
static GBitmap *s_image;
static void layer_update_callback(Layer *layer, GContext* ctx) {
// We make sure the dimensions of the GRect to draw into
// are equal to the size of the bitmap--otherwise the image
// will automatically tile. Which might be what *you* want.
#ifdef PBL_PLATFORM_BASALT
GSize image_size = gbitmap_get_bounds(s_image).size;
#else
GSize image_size = s_image->bounds.size;
#endif
graphics_draw_bitmap_in_rect(ctx, s_image, GRect(5, 5, image_size.w, image_size.h));
graphics_draw_bitmap_in_rect(ctx, s_image, GRect(80, 60, image_size.w, image_size.h));
}
static void main_window_load(Window *window) {
Layer *window_layer = window_get_root_layer(s_main_window);
GRect bounds = layer_get_frame(window_layer);
s_image_layer = layer_create(bounds);
layer_set_update_proc(s_image_layer, layer_update_callback);
layer_add_child(window_layer, s_image_layer);
s_image = gbitmap_create_with_resource(RESOURCE_ID_IMAGE_NO_LITTER);
}
static void main_window_unload(Window *window) {
gbitmap_destroy(s_image);
layer_destroy(s_image_layer);
}
static void init() {
s_main_window = window_create();
window_set_window_handlers(s_main_window, (WindowHandlers) {
.load = main_window_load,
.unload = main_window_unload,
});
window_stack_push(s_main_window, true);
}
static void deinit() {
window_destroy(s_main_window);
}
int main(void) {
init();
app_event_loop();
deinit();
}
実行するとこんな。
コップの画像を2か所に描画します。プログラムを見ると、pngをリソースに登録してGBitmapに読み込むのはチュートリアル2と同じですが、描画方法が違ってますね。
BitmapLayerに画像を設定するのではなくて、Layerに対して「graphics_draw_bitmap_in_rect」を用いて描画をしているようです。
さらに、その描画処理も「layer_set_update_proc(s_image_layer, layer_update_callback)」
を設定してコールバックで呼び出している。
このように呼ばれると「static void layer_update_callback(Layer *layer, GContext* ctx) 」
で画像描画に必要なGContextの情報が手に入ると。なるほど…。
チュートリアル1のtick_handlerの処理的なことを、layer_set_update_procでやればよいわけですね。
GContextの取得ができそうなので、円でも描いてみましょう。円を描くAPIはgraphics_draw_circle です。
void graphics_draw_circle(GContext * ctx, GPoint p, uint16_t radius)
GContextに引数で渡ってきたctx を指定、GPoint で中心座標を指定、raduis で半径を指定です。こんなかんじかな。
//円を描く graphics_draw_circle(ctx, GPoint(144 / 2,168 / 2), 50);
全体のプログラムは以下の通り。
#include <pebble.h>
static Window *s_main_window;
static Layer *s_image_layer;
static void layer_update_callback(Layer *layer, GContext* ctx) {
//円を描く
graphics_draw_circle(ctx, GPoint(144 / 2,168 / 2), 50);
}
static void main_window_load(Window *window) {
Layer *window_layer = window_get_root_layer(s_main_window);
GRect bounds = layer_get_frame(window_layer);
s_image_layer = layer_create(bounds);
layer_set_update_proc(s_image_layer, layer_update_callback);
layer_add_child(window_layer, s_image_layer);
}
static void main_window_unload(Window *window) {
layer_destroy(s_image_layer);
}
static void init() {
// Create main Window element and assign to pointer
s_main_window = window_create();
// Set handlers to manage the elements inside the Window
window_set_window_handlers(s_main_window, (WindowHandlers) {
.load = main_window_load,
.unload = main_window_unload
});
// Show the Window on the watch, with animated=true
window_stack_push(s_main_window, true);
}
static void deinit() {
// Destroy Window
window_destroy(s_main_window);
}
int main(void) {
init();
app_event_loop();
deinit();
}
さーて動くかなー。
バッチリいけましたー。
プログラムですが、init() / deinit() / main(void) は チュートリアル1と同じです。やったことは、
1. グラフィック描画レイヤーの定義
static Layer *s_image_layer;
2. main_window_load でグラフィック描画レイヤーの作成
static void main_window_load(Window *window) {
Layer *window_layer = window_get_root_layer(s_main_window);
GRect bounds = layer_get_frame(window_layer);
s_image_layer = layer_create(bounds);
layer_set_update_proc(s_image_layer, layer_update_callback);
layer_add_child(window_layer, s_image_layer);
}
(1) window_layerで画面全体のレイヤーを取得。
(2) その縦横サイズをboundsに取得。
(3) bounds のサイズに合わせて描画レイヤー s_image_layer を作成。
(4) s_image_layer のコールバック関数 layer_update_callback を設定。
(5) window_layer に 描画レイヤーs_image_layerを追加。
3. main_window_unload に s_image_layer 削除処理追加
static void main_window_unload(Window *window) {
layer_destroy(s_image_layer);
}
4. コールバック処理作成
static void layer_update_callback(Layer *layer, GContext* ctx) {
//円を描く
graphics_draw_circle(ctx, GPoint(144 / 2,168 / 2), 50);
}
書いてみたら、わりかし簡単でしたね。
せっかくなので色も付けよう!
描画色は、graphics_context_set_stroke_color で設定します。
void graphics_context_set_stroke_color(GContext * ctx, GColor color)
ctxに対して、GColor 指定するだけですね。ちなみに塗りつぶす(fill)場合の色はgraphics_context_set_fill_color になります。
線の太さも設定しよう。太さは graphics_context_set_stroke_width です。(これはSDK 3のみ使えるようです。)
void graphics_context_set_stroke_width(GContext * ctx, uint8_t stroke_width)
これも太さ設定するだけですね。
static void layer_update_callback(Layer *layer, GContext* ctx) {
//太さ10
graphics_context_set_stroke_width(ctx,10);
//色:赤 GColorRed
graphics_context_set_stroke_color(ctx, GColorRed);
//円を描く
graphics_draw_circle(ctx, GPoint(144 / 2,168 / 2), 50);
//色:緑 GColorGreen
graphics_context_set_stroke_color(ctx, GColorGreen);
//円を描く
graphics_draw_circle(ctx, GPoint(144 / 2,168 / 2), 40);
}
これでどうだ。
よいかんじですよ。
コールバックがいつ呼ばれてるのか気になるので、ログ入れてみた。
static void layer_update_callback(Layer *layer, GContext* ctx) {
APP_LOG(APP_LOG_LEVEL_INFO, "Callback!!");
実行してみると
[INFO] ocess_manager.c:369: Heap Usage for App <TESTCLOCK>: Total Size <64904B> Used <164B> Still allocated <0B> [INFO] main.c:7: Callback!!
1回だけ呼ばれてますね。layer_add_childの時かな?と思って
APP_LOG(APP_LOG_LEVEL_INFO, "Before layer_add_child!!"); layer_add_child(window_layer, s_image_layer); APP_LOG(APP_LOG_LEVEL_INFO, "After layer_add_child!!");
ログ入れてみたところ、
[INFO] main.c:25: Before layer_add_child!! [INFO] main.c:27: After layer_add_child!! [INFO] main.c:7: Callback!!
それより後でした。main_window_load 終了のタイミングっぽいですね。



