Pebbleアプリ開発メモ

Pebbleアプリ、Watchface開発のメモを残していきます。

【Pebble】Random Deciderをリリースしました。

f:id:shotsep:20151111222438p:plain

http://apps.getpebble.com/en_US/application/564334d7a566b5954200003e

新しい職場で働き出した時は、今日のランチ何食べようかな〜って楽しみでした。

でも3年も経つと決めることすらおっくうになりますよね。まずいもの食うのは嫌だし、うまいものはうまいもので飽きてるし。

他にも着ていくジャケットの色だったり、靴だったり。

そういうことが結構あるので代わりに決めてくれるアプリです。

グループのタイトルや選択肢はiPhone側の設定画面で自由に設定できます。

携帯取り出すと色々見ちゃって結局迷うだけ迷っていつものとこ、なんてことも多いですが、これだとスパっと後腐れなく決められますよ。

尚、今回からPebble TimeやPebble Time Round向けにもビルドしてあります。色は相変わらず白黒ですが。

管理画面やCloudPebbleは結構よく出来てると思いますが、複雑な事やろうとするとC言語でつまづくのがけっこうだるいです。せめてC++で書かせて欲しい。

【Pebble】【Watchface】時間帯ごとにアナログ/デジタルを切り替える

f:id:shotsep:20151014113201p:plain

Pebbleをしはじめてしばらく経つのですが、相変わらずデジタル時計には慣れません。

例えばですが、11時から会議で時計見たら10時58分だったとき、60-58であと2分ではなく、12度だからあと2分みたいな感じで認識するクセがついているのかと。

少し前にデジタル時計とアナログ時計の電池持ちを比べてみたところ、相当な差が出ました。AppMessage呼んでいるのも原因の一つだと思いますが、描画もかなり電池食うってのをどっかで見ました。

というわけで、時間帯でFaceを切り替える時計を作ってみました。これでしばらく実験してみたいと思います。

以下は適当ですが5分毎にアナログ/デジタル表記を切替えるサンプルです。

これで動かしてみたところ、9日くらいは持ちそうな感じですね。確実に電池持ちは良くなります。

実験結果

切り替えずに常時アナログでもあまり変わらない結果に。。

電池食うのはAppMessageみたいですね。

#include "pebble.h"
#include "line_draw_with_width.c"

#include <math.h>

#define PI 3.141592653589793

static const GPathInfo MINUTE_HAND_POINTS = {
  10, (GPoint []){
    {-1, 6},
    {1, 6},
    {1, -8},
    {3, -10},
    {3, -68},
    {1, -71},
    {-1, -71},
    {-3, -68},
    {-3, -10},
    {-1, -8}
  }
};

static const GPathInfo HOUR_HAND_POINTS = {
  10, (GPoint []){
    {-1, 6},
    {1, 6},
    {1, -8},
    {3, -10},
    {3, -40},
    {1, -43},
    {-1, -43},
    {-3, -40},
    {-3, -10},
    {-1, -8}
  }
};

static Window *window;
static Layer *s_simple_bg_layer, *s_date_layer, *s_hands_layer;
static TextLayer *s_num_label, *s_battery_layer;

static GPath *s_minute_arrow, *s_hour_arrow;
static char s_num_buffer[4];

static BitmapLayer *s_bt_layer;
static GBitmap *s_bt_bitmap;

static BitmapLayer *s_charge_layer;
static GBitmap *s_charge_bitmap;

static TextLayer *s_digit_layer;

static bool isShowingAnalog;
static void toggleWatch();

static void bg_update_proc(Layer *layer, GContext *ctx) {
  graphics_context_set_fill_color(ctx, GColorBlack);
  graphics_fill_rect(ctx, layer_get_bounds(layer), 0, GCornerNone);
}

static void handle_battery(BatteryChargeState charge_state) {
  static char battery_text[] = "100%";

  layer_set_hidden(bitmap_layer_get_layer(s_charge_layer), !charge_state.is_charging);

  snprintf(battery_text, sizeof(battery_text), "%d%%", charge_state.charge_percent);

  text_layer_set_text(s_battery_layer, battery_text);
}

static void handle_bluetooth(bool connected) {
  layer_set_hidden(bitmap_layer_get_layer(s_bt_layer), !connected);
}

static void hands_update_proc(Layer *layer, GContext *ctx) {

  APP_LOG(APP_LOG_LEVEL_DEBUG, "hands_update_proc");
  if(isShowingAnalog) {
    GRect bounds = layer_get_bounds(layer);
    GPoint center = grect_center_point(&bounds);
    int16_t second_hand_length = bounds.size.w / 2 - 10;

    time_t now = time(NULL);
    struct tm *t = localtime(&now);
    int32_t second_angle = TRIG_MAX_ANGLE * t->tm_sec / 60;
    GPoint second_hand = {
      .x = (int16_t)(sin_lookup(second_angle) * (int32_t)second_hand_length / TRIG_MAX_RATIO) + center.x,
      .y = (int16_t)(-cos_lookup(second_angle) * (int32_t)second_hand_length / TRIG_MAX_RATIO) + center.y,
    };

    graphics_context_set_stroke_color(ctx, GColorWhite);
    graphics_draw_line2(ctx, second_hand, center, 1);

    graphics_context_set_fill_color(ctx, GColorWhite);
    graphics_context_set_stroke_color(ctx, GColorBlack);

    gpath_rotate_to(s_minute_arrow, TRIG_MAX_ANGLE * t->tm_min / 60);
    gpath_draw_filled(ctx, s_minute_arrow);
    gpath_draw_outline(ctx, s_minute_arrow);

    gpath_rotate_to(s_hour_arrow, (TRIG_MAX_ANGLE * (((t->tm_hour % 12) * 6) + (t->tm_min / 10))) / (12 * 6));
    gpath_draw_filled(ctx, s_hour_arrow);
    gpath_draw_outline(ctx, s_hour_arrow);

    handle_battery(battery_state_service_peek());
  } else {
    time_t now = time(NULL);
    struct tm *current_time = localtime(&now);
    static char s_time_text[] = "00:00";

    strftime(s_time_text, sizeof(s_time_text), "%X", current_time);
    text_layer_set_text(s_digit_layer, s_time_text);
  }
  
}

static void date_update_proc(Layer *layer, GContext *ctx) {
  time_t now = time(NULL);
  struct tm *t = localtime(&now);

  strftime(s_num_buffer, sizeof(s_num_buffer), "%d", t);
  text_layer_set_text(s_num_label, s_num_buffer);
}

static void handle_second_tick(struct tm *tick_time, TimeUnits units_changed) {

  if(isShowingAnalog) {
    layer_mark_dirty(window_get_root_layer(window));
  } else {
    if(tick_time->tm_sec == 0) {
      layer_mark_dirty(window_get_root_layer(window));
    }
  }

  //if(tick_time->tm_sec % 30 == 0) {
  if(tick_time->tm_sec == 0 && tick_time->tm_min % 5 == 0) {
  /*if((tick_time->tm_hour == 1 &&
      tick_time->tm_min == 0 &&
      tick_time->tm_sec == 0) ||
     (tick_time->tm_hour == 8 &&
      tick_time->tm_min == 0 &&
      tick_time->tm_sec == 0)
    ) {*/
    toggleWatch();
  }
}

static void addElementsToRootLayer();

static void window_load(Window *window) {

  addElementsToRootLayer();

  battery_state_service_subscribe(handle_battery);
  bluetooth_connection_service_subscribe(handle_bluetooth);
}

static void window_unload(Window *window) {
  
  layer_destroy(s_simple_bg_layer);
  if(isShowingAnalog) {
    layer_destroy(s_date_layer);
    layer_destroy(s_hands_layer);

    text_layer_destroy(s_num_label);
    text_layer_destroy(s_battery_layer);
  } else {
    text_layer_destroy(s_digit_layer);
  }
  
  battery_state_service_unsubscribe();
  bluetooth_connection_service_unsubscribe();
}

static void init() {
  window = window_create();
  window_set_window_handlers(window, (WindowHandlers) {
    .load = window_load,
    .unload = window_unload,
  });
  window_stack_push(window, true);

  s_num_buffer[0] = '\0';

  s_minute_arrow = gpath_create(&MINUTE_HAND_POINTS);
  s_hour_arrow = gpath_create(&HOUR_HAND_POINTS);

  Layer *window_layer = window_get_root_layer(window);
  GRect bounds = layer_get_bounds(window_layer);
  GPoint center = grect_center_point(&bounds);
  gpath_move_to(s_minute_arrow, center);
  gpath_move_to(s_hour_arrow, center);

  isShowingAnalog = true;
  tick_timer_service_subscribe(SECOND_UNIT, handle_second_tick);
}

static void removeElementsToRootLayer();

static void toggleWatch() {
  if(isShowingAnalog) {
    removeElementsToRootLayer();
  } else {
    addElementsToRootLayer();
  }

  isShowingAnalog = !isShowingAnalog;
}

static void addElementsToRootLayer() {
  Layer *window_layer = window_get_root_layer(window);
  
  layer_remove_child_layers(window_layer);
  
  GRect bounds = layer_get_bounds(window_layer);

  s_simple_bg_layer = layer_create(bounds);
  layer_set_update_proc(s_simple_bg_layer, bg_update_proc);
  layer_add_child(window_layer, s_simple_bg_layer);

  s_bt_bitmap = gbitmap_create_with_resource(RESOURCE_ID_IMAGE_ICON_BT);
  s_bt_layer = bitmap_layer_create(GRect(0, 1, 15, 18));
  bitmap_layer_set_bitmap(s_bt_layer, s_bt_bitmap);
  layer_add_child(window_get_root_layer(window), bitmap_layer_get_layer(s_bt_layer));

  s_charge_bitmap = gbitmap_create_with_resource(RESOURCE_ID_IMAGE_ICON_CHARGE);
  s_charge_layer = bitmap_layer_create(GRect(bounds.size.w - 15, 16, 15, 18));
  bitmap_layer_set_bitmap(s_charge_layer, s_charge_bitmap);
  layer_add_child(window_get_root_layer(window), bitmap_layer_get_layer(s_charge_layer));
  layer_set_hidden(bitmap_layer_get_layer(s_charge_layer), true);

  s_date_layer = layer_create(bounds);
  layer_set_update_proc(s_date_layer, date_update_proc);
  layer_add_child(window_layer, s_date_layer);

  s_num_label = text_layer_create(GRect(bounds.size.w - 20, bounds.size.h / 2 - 13 - 10, 27, 20));
  text_layer_set_text(s_num_label, s_num_buffer);
  text_layer_set_background_color(s_num_label, GColorClear);
  text_layer_set_text_color(s_num_label, GColorWhite);
  text_layer_set_font(s_num_label, fonts_get_system_font(FONT_KEY_GOTHIC_18));

  layer_add_child(s_date_layer, text_layer_get_layer(s_num_label));

  s_hands_layer = layer_create(GRect(0, -12, bounds.size.w, bounds.size.h));
  layer_set_update_proc(s_hands_layer, hands_update_proc);
  layer_add_child(window_layer, s_hands_layer);

  s_battery_layer = text_layer_create(GRect(bounds.size.w - 50, 0, 50, 30));
  text_layer_set_text_color(s_battery_layer, GColorWhite);
  text_layer_set_background_color(s_battery_layer, GColorClear);
  text_layer_set_font(s_battery_layer, fonts_get_system_font(FONT_KEY_GOTHIC_14));
  text_layer_set_text_alignment(s_battery_layer, GTextAlignmentRight);
  text_layer_set_text(s_battery_layer, "100%");

  layer_add_child(window_layer, text_layer_get_layer(s_battery_layer));
}

static void removeElementsToRootLayer() {
  Layer *window_layer = window_get_root_layer(window);
  layer_remove_child_layers(window_layer);

  GRect bounds = layer_get_bounds(window_layer);
  
  bitmap_layer_destroy(s_charge_layer);
  bitmap_layer_destroy(s_bt_layer);

  layer_destroy(s_date_layer);
  text_layer_destroy(s_num_label);

  layer_destroy(s_hands_layer);
  text_layer_destroy(s_battery_layer);

  gbitmap_destroy(s_bt_bitmap);
  gbitmap_destroy(s_charge_bitmap);
  
  s_simple_bg_layer = layer_create(bounds);
  layer_set_update_proc(s_simple_bg_layer, hands_update_proc);
  layer_add_child(window_layer, s_simple_bg_layer);
  
  s_digit_layer = text_layer_create(GRect(0, 67, 144, 34));
  text_layer_set_text_color(s_digit_layer, GColorBlack);
  text_layer_set_background_color(s_digit_layer, GColorClear);
  text_layer_set_font(s_digit_layer, fonts_get_system_font(FONT_KEY_GOTHIC_28_BOLD));
  text_layer_set_text_alignment(s_digit_layer, GTextAlignmentCenter);
  
  time_t now = time(NULL);
  struct tm *current_time = localtime(&now);
  static char s_time_text[] = "00:00";

  strftime(s_time_text, sizeof(s_time_text), "%X", current_time);
  text_layer_set_text(s_digit_layer, s_time_text);
  layer_add_child(window_layer, text_layer_get_layer(s_digit_layer));
}

static void deinit() {
  gpath_destroy(s_minute_arrow);
  gpath_destroy(s_hour_arrow);

  tick_timer_service_unsubscribe();
  window_destroy(window);
}

int main() {
  init();
  app_event_loop();
  deinit();
}

【Pebble】透過pngを使用する

Photoshop等できちんと背景を透過にして書き出しても、通常の画像と同じように読み込むと背景が黒く塗りつぶされてしまいます。

以下のようにすれば、背景が透明なBitmapLayerを作ることができます。

読み込み時にPNG with transparency形式に

f:id:shotsep:20151014145618p:plain

リソースを追加する際に、形式からPNG with transparencyを指定してやります。

この際、例えばタグを「IMAGE_TARGET」と付けると、以下のように2種のタグが自動的に付与されます。

f:id:shotsep:20151014145914p:plain

タグの後ろに_BLACKか_WHITEをつける

あとはGBitmap作ってBitmapLayerにセットするのですが、タグに注意。

白背景に載せたい時は_BLACKを、黒に乗せたい時は_WHITEを指定します。

target_bitmap = gbitmap_create_with_resource(RESOURCE_ID_IMAGE_TARGET_BLACK);
  //target_bitmap = gbitmap_create_with_resource(RESOURCE_ID_IMAGE_TARGET_WHITE);
  target_layer = bitmap_layer_create(GRect(0, 0, bounds.size.w, bounds.size.h));
  
  bitmap_layer_set_compositing_mode(target_layer, GCompOpClear);
  
  bitmap_layer_set_bitmap(target_layer, target_bitmap);
  bitmap_layer_set_background_color(target_layer, GColorClear);
  layer_add_child(window_get_root_layer(window), bitmap_layer_get_layer(target_layer));

【Pebble】アナログ時計とデジタル時計の電池持ちの差

どうも電池持ちが悪かったのでwatchfaceをデジタルのものに変更して過ごしてみました。

f:id:shotsep:20151005131154p:plain

全然違いますね・・・。

ちなみにデジタルは純正の日付+時間+分表示のものです。

アナログ時計は電池持ち対策を入れたいところです。色々考えてます。

【Pebble】公式アラームアプリのようなUIを作成する(角丸+三角バージョン)

f:id:shotsep:20151002163334p:plain

前回の記事で、NumberWindowを使わない数字ピッカーのようなウィンドウの作り方を掲載しました。

pebble.hatenablog.jp

今回は更に公式アラームアプリに近づけて、死角は角丸、選択されている桁には三角をつけて加算/減算ができることをわかりやすく示してみます。

コード中にもありますが、既に追加済みのTextLayerのBoundsを取ろうとして

layer_get_bounds(text_layer_get_layer(ampmBox));

とやると座標が(0,0)になっちゃうんですよね。なんでだろ。

角丸バージョン
#include <pebble.h>

#define NUMBER_COUNT 2
  
static void up_click_handler(ClickRecognizerRef recognizer, void *context);
static void select_click_handler(ClickRecognizerRef recognizer, void *context);
static void down_click_handler(ClickRecognizerRef recognizer, void *context);
static void click_config_provider(void *context);

Window *window;

TextLayer *numBox[NUMBER_COUNT];
char c[NUMBER_COUNT][4];
uint8_t boxIndex = 0;

TextLayer *ampmBox;
char ampmChar[8];
uint8_t ampmIndex = 0;

char currencyNameArray[2][6] = {"AM",
                                 "PM",
                                };

Layer *round_rect_layer;
GPath *triangle_up, *triangle_down;

GPathInfo triangleUp01PI = {
      3, (GPoint []){
        {42, 36},
        {56, 36},
        {49, 28}
      }
    };
GPathInfo triangleDown01PI = {
      3, (GPoint []){
        {42, 78},
        {56, 78},
        {49, 86}
      }
    };
GPathInfo triangleUp02PI = {
      3, (GPoint []){
        {42 + 48, 36},
        {56 + 48, 36},
        {49 + 48, 28}
      }
    };
GPathInfo triangleDown02PI = {
      3, (GPoint []){
        {42 + 48, 78},
        {56 + 48, 78},
        {49 + 48, 86}
      }
    };
GPathInfo triangleUp03PI = {
      3, (GPoint []){
        {42 + 24, 36 + 47},
        {56 + 24, 36 + 47},
        {49 + 24, 28 + 47}
      }
    };
GPathInfo triangleDown03PI = {
      3, (GPoint []){
        {42 + 24, 78 + 47},
        {56 + 24, 78 + 47},
        {49 + 24, 86 + 47}
      }
    };

static void draw_roundrect_and_triangle(Layer *layer, GContext *ctx) {
  
  if(boxIndex < 2) {
    graphics_fill_rect(ctx, GRect(29 + (boxIndex * 48), 40 , 40, 35), 4, GCornersAll);
    text_layer_set_text_color(numBox[boxIndex], GColorWhite);
    
    if(boxIndex == 0) {
      triangle_up = gpath_create(&triangleUp01PI);
      triangle_down = gpath_create(&triangleDown01PI);
    } else {
      triangle_up = gpath_create(&triangleUp02PI);
      triangle_down = gpath_create(&triangleDown02PI);
    }
    
    gpath_draw_filled(ctx, triangle_up);
    gpath_draw_filled(ctx, triangle_down);

  } else {
    //graphics_fill_rect(ctx, layer_get_bounds(text_layer_get_layer(ampmBox)), 4, GCornersAll); // 座標が(0,0)になる
    graphics_fill_rect(ctx, GRect(47, 87, 50, 35), 4, GCornersAll);
    text_layer_set_text_color(numBox[boxIndex], GColorWhite);
    
    triangle_up = gpath_create(&triangleUp03PI);
    triangle_down = gpath_create(&triangleDown03PI);
    
    gpath_draw_filled(ctx, triangle_up);
    gpath_draw_filled(ctx, triangle_down);
  }
  
}

void window_load(Window *window) {
  
  Layer *window_layer = window_get_root_layer(window);
  GRect bounds = layer_get_bounds(window_layer);
  
  round_rect_layer = layer_create(bounds);
  layer_add_child(window_layer, round_rect_layer);
  
  layer_set_update_proc(round_rect_layer, draw_roundrect_and_triangle);
  
  for (int i = 0; i < NUMBER_COUNT; i++) {
    numBox[i] = text_layer_create(GRect(29 + (i * 48), 40 , 40, 35));
    text_layer_set_background_color(numBox[i], GColorClear);
    text_layer_set_font(numBox[i], fonts_get_system_font(FONT_KEY_GOTHIC_28_BOLD));
    text_layer_set_text_alignment(numBox[i] , GTextAlignmentCenter);
    snprintf(c[i], sizeof(c[i]), "%02d", 0);
    text_layer_set_text(numBox[i], c[i]);
    layer_add_child(window_get_root_layer(window), text_layer_get_layer(numBox[i]));
  }
  
  ampmBox = text_layer_create(GRect(47, 87, 50, 35));
  text_layer_set_background_color(ampmBox, GColorClear);
  text_layer_set_font(ampmBox, fonts_get_system_font(FONT_KEY_GOTHIC_24_BOLD));
  text_layer_set_text_alignment(ampmBox , GTextAlignmentCenter);
  snprintf(ampmChar, sizeof(ampmChar), "%s", currencyNameArray[0]);
  text_layer_set_text(ampmBox, ampmChar);
  layer_add_child(window_get_root_layer(window), text_layer_get_layer(ampmBox));
  
  
}

void window_unload(Window *window) {

}

void init(void) {
  window = window_create();
  window_set_background_color(window, GColorWhite);
  window_set_window_handlers(window, (WindowHandlers) {
      .load = window_load,
      .unload = window_unload
      });

  window_stack_push(window, true);
  
  window_set_click_config_provider(window, click_config_provider);
}

void deinit(void) {
  window_destroy(window);
}

int main(void) {
  init();
  app_event_loop();
  deinit();
}

static void click_config_provider(void *context) {
  window_single_click_subscribe(BUTTON_ID_UP, up_click_handler);
  window_single_click_subscribe(BUTTON_ID_SELECT, select_click_handler);
  window_single_click_subscribe(BUTTON_ID_DOWN, down_click_handler);
}

static void select_click_handler(ClickRecognizerRef recognizer, void *context) {
  
  boxIndex++;
  
  layer_set_update_proc(round_rect_layer, draw_roundrect_and_triangle);
  
  if(boxIndex < 2) {
    for (int i = 0; i < NUMBER_COUNT; i++) {
      text_layer_set_text_color(numBox[i], GColorBlack);
    }

    text_layer_set_text_color(numBox[boxIndex], GColorWhite);
  }
  else if(boxIndex == 2) {
    
    for (int i = 0; i < NUMBER_COUNT; i++) {
      text_layer_set_text_color(numBox[i], GColorBlack);
    }
    
    text_layer_set_text_color(ampmBox, GColorWhite);
  }
  else {
    boxIndex = 0;
    
    text_layer_set_text_color(numBox[boxIndex], GColorWhite);
    
    text_layer_set_text_color(ampmBox, GColorBlack);
  }
}

static void up_click_handler(ClickRecognizerRef recognizer, void *context) {
  if(boxIndex < 2) {
    int current = atoi(text_layer_get_text(numBox[boxIndex]));
    current = (current < 12) ? current + 1 : 0; 
    snprintf(c[boxIndex], sizeof(c[boxIndex]), "%02d", current);
    text_layer_set_text(numBox[boxIndex], c[boxIndex]);
  }
  else if(boxIndex == 2) {
    ampmIndex = (ampmIndex > 0) ? ampmIndex - 1 : 1; 
    snprintf(ampmChar, sizeof(ampmChar), "%s", currencyNameArray[ampmIndex]);
    text_layer_set_text(ampmBox, ampmChar);
  }
}

static void down_click_handler(ClickRecognizerRef recognizer, void *context) {
  if(boxIndex < 2) {
    int current = atoi(text_layer_get_text(numBox[boxIndex]));
    current = (current  > 0) ? current - 1 : 12; 
    snprintf(c[boxIndex], sizeof(c[boxIndex]), "%02d", current);
    text_layer_set_text(numBox[boxIndex], c[boxIndex]);
  }
  else if(boxIndex == 2) {
    ampmIndex = (ampmIndex < 1) ? ampmIndex + 1 : 0; 
    snprintf(ampmChar, sizeof(ampmChar), "%s", currencyNameArray[ampmIndex]);
    text_layer_set_text(ampmBox, ampmChar);
  }
}

【Pebble】公式アラームアプリのようなUIを作成する

f:id:shotsep:20151001165635p:plain

以前に紹介したCurrency Converter。海外から買い物をする機会が多いので重宝してます。

そもそも作ろうと思ったきっかけは、ストアにいいUIのコンバーターがなかったからでした。

Currency ConverterのUI

f:id:shotsep:20150929110345g:plain

公式のアラームアプリのような、中心のボタンで桁を選択、上ボタン/下ボタンで加算/減算をするUIです。

ご存知の通りPebbleにはタッチスクリーンが搭載されていないので、UIは他のデバイスと違う方法でアプローチしないといけません。

調べていたところ、Number Windowというクラスを使うと実現できそうだったのですが、どうも1桁しかおけないようなので自作しました。

NumberWindow不使用で実現
#include <pebble.h>

#define NUMBER_COUNT 2
  
static void up_click_handler(ClickRecognizerRef recognizer, void *context);
static void select_click_handler(ClickRecognizerRef recognizer, void *context);
static void down_click_handler(ClickRecognizerRef recognizer, void *context);
static void click_config_provider(void *context);

Window *window;

TextLayer *numBox[NUMBER_COUNT];
char c[NUMBER_COUNT][4];
uint8_t boxIndex = 0;

TextLayer *ampmBox;
char ampmChar[8];
uint8_t ampmIndex = 0;

char currencyNameArray[2][6] = {"AM",
                                 "PM",
                                };

void window_load(Window *window) {
  
  for (int i = 0; i < NUMBER_COUNT; i++) {
    numBox[i] = text_layer_create(GRect(29 + (i * 48), 40 , 40, 35));
    text_layer_set_font(numBox[i], fonts_get_system_font(FONT_KEY_GOTHIC_28_BOLD));
    text_layer_set_text_alignment(numBox[i] , GTextAlignmentCenter);
    snprintf(c[i], sizeof(c[i]), "%02d", 0);
    text_layer_set_text(numBox[i], c[i]);
    layer_add_child(window_get_root_layer(window), text_layer_get_layer(numBox[i]));
  }
  
  text_layer_set_background_color(numBox[boxIndex], GColorBlack);
  text_layer_set_text_color(numBox[boxIndex], GColorWhite);
  
  ampmBox = text_layer_create(GRect(47, 87, 50, 30));
  text_layer_set_font(ampmBox, fonts_get_system_font(FONT_KEY_GOTHIC_24_BOLD));
  text_layer_set_text_alignment(ampmBox , GTextAlignmentCenter);
  snprintf(ampmChar, sizeof(ampmChar), "%s", currencyNameArray[0]);
  text_layer_set_text(ampmBox, ampmChar);
  layer_add_child(window_get_root_layer(window), text_layer_get_layer(ampmBox));
}

void window_unload(Window *window) {

}

void init(void) {
  window = window_create();
  window_set_background_color(window, GColorWhite);
  window_set_window_handlers(window, (WindowHandlers) {
      .load = window_load,
      .unload = window_unload
      });

  window_stack_push(window, true);
  
  window_set_click_config_provider(window, click_config_provider);
}

void deinit(void) {
  window_destroy(window);
}

int main(void) {
  init();
  app_event_loop();
  deinit();
}

static void click_config_provider(void *context) {
  window_single_click_subscribe(BUTTON_ID_UP, up_click_handler);
  window_single_click_subscribe(BUTTON_ID_SELECT, select_click_handler);
  window_single_click_subscribe(BUTTON_ID_DOWN, down_click_handler);
}

static void select_click_handler(ClickRecognizerRef recognizer, void *context) {
  
  boxIndex++;
  
  if(boxIndex < 2) {
    for (int i = 0; i < NUMBER_COUNT; i++) {
      text_layer_set_background_color(numBox[i], GColorClear);
      text_layer_set_text_color(numBox[i], GColorBlack);
    }

    text_layer_set_background_color(numBox[boxIndex], GColorBlack);
    text_layer_set_text_color(numBox[boxIndex], GColorWhite);
  }
  else if(boxIndex == 2) {
    
    for (int i = 0; i < NUMBER_COUNT; i++) {
      text_layer_set_background_color(numBox[i], GColorClear);
      text_layer_set_text_color(numBox[i], GColorBlack);
    }
    
    text_layer_set_background_color(ampmBox, GColorBlack);
    text_layer_set_text_color(ampmBox, GColorWhite);
  }
  else {
    boxIndex = 0;
    
    text_layer_set_background_color(numBox[boxIndex], GColorBlack);
    text_layer_set_text_color(numBox[boxIndex], GColorWhite);
    
    text_layer_set_background_color(ampmBox, GColorClear);
    text_layer_set_text_color(ampmBox, GColorBlack);
  }
}

static void up_click_handler(ClickRecognizerRef recognizer, void *context) {
  if(boxIndex < 2) {
    int current = atoi(text_layer_get_text(numBox[boxIndex]));
    current = (current < 12) ? current + 1 : 0; 
    snprintf(c[boxIndex], sizeof(c[boxIndex]), "%02d", current);
    text_layer_set_text(numBox[boxIndex], c[boxIndex]);
  }
  else if(boxIndex == 2) {
    ampmIndex = (ampmIndex > 0) ? ampmIndex - 1 : 1; 
    snprintf(ampmChar, sizeof(ampmChar), "%s", currencyNameArray[ampmIndex]);
    text_layer_set_text(ampmBox, ampmChar);
  }
}

static void down_click_handler(ClickRecognizerRef recognizer, void *context) {
  if(boxIndex < 2) {
    int current = atoi(text_layer_get_text(numBox[boxIndex]));
    current = (current  > 0) ? current - 1 : 12; 
    snprintf(c[boxIndex], sizeof(c[boxIndex]), "%02d", current);
    text_layer_set_text(numBox[boxIndex], c[boxIndex]);
  }
  else if(boxIndex == 2) {
    ampmIndex = (ampmIndex < 1) ? ampmIndex + 1 : 0; 
    snprintf(ampmChar, sizeof(ampmChar), "%s", currencyNameArray[ampmIndex]);
    text_layer_set_text(ampmBox, ampmChar);
  }
}

【Pebble】atofを使うとビルドできなくなる問題の対処法

atof使うとビルドが通らないので自分で作ります。

atof2.h
float atof2(const char* s);
atof2.c
float atof2(const char* s){
  float rez = 0, fact = 1;
  if (*s == '-'){
    s++;
    fact = -1;
  };
  for (int point_seen = 0; *s; s++){
    if (*s == '.'){
      point_seen = 1; 
      continue;
    };
    int d = *s - '0';
    if (d >= 0 && d <= 9){
      if (point_seen) fact /= 10.0f;
      rez = rez * 10.0f + (float)d;
    };
  };
  return rez * fact;
};

これをインクルードしてatof2(cstring)すればビルド通るようになります。

stackoverflow.com

尚、Floatをcstringにしようとしても

Floating point is not supported by snprintf

が出るので、その時は

int d1 = resultFloat;
float f2 = resultFloat - d1;
int d2 = trunc(f2 * 100);
snprintf(resultString, sizeof(resultString), "%d.%02d", d1, d2);


みたいにして回避してやります。