天天看點

awtk-widget-chart-view-mvvm JS版本适配筆記

目錄

    • 一、前言
    • 二、适配 JS 版本的序列點資料(FIFO)
      • 2.1 series_fifo_js 的接口
      • 2.1 series_fifo_js 的實作
    • 三、自定義 binder
    • 四、JS 語言綁定 chart view 控件
      • 4.1 注冊自定義 binder
      • 4.2 建立并綁定 Model
      • 4.3 Model 變化
        • 4.3.1 使用自定義接口
        • 4.3.2 使用原生接口

一、前言

awtk-widget-chart-view 是 AWTK 提供的圖表自定義控件,該控件包含:曲線圖、柱狀圖和餅圖。前段時

間記錄了該控件适配 AWTK-MVVM C版本的過程,具體請參考:awtk-widget-chart-view-mvvm C版本适配筆記,這次記錄一下适配 JS 版本的過程。

AWTK是 ZLG 開發的開源 GUI 引擎,官網位址:https://www.zlg.cn/index/pub/awtk.html。

AWTK GitHub 倉庫:http://github.com/zlgopen/awtk-mvvm

AWTK-MVVM是一套用C語言開發的,專門為嵌入式平台優化的MVVM架構。它實作了資料綁定、指令綁定和視窗導航等基本功能。

AWTK-MVVM GitHub 倉庫:http://github.com/zlgopen/awtk-mvvm

二、适配 JS 版本的序列點資料(FIFO)

chart view 圖表控件的絕大部分屬性都可使用 awtk-mvvm 預設的方法進行綁定,但 series 控件 fifo 屬性比較特殊,它是一個自定義的先進先出隊列,用于儲存圖表的序列點資料。是以,資料綁定時,需要指定一個特殊的 Model。C 語言中可以調用 serties_fifo_default_create() 接口建立該 Model;JS語言中則需使用 JS 的 Array,本項目所實作的自定義 binder 會将 Array 适配為 series 所需的 fifo。

簡單了解,即基于 JS 的原生 Array 資料類型實作一個 C 版本的先進先出隊列 FIFO,也就是 series_fifo_js ,此處基于 JerryScript 實作 JS 與 C 之間的适配。

JerryScript 是一個輕量級的 JavaScript 引擎,它可以運作在受限制的裝置上,官網位址:https://jerryscript.net/。

2.1 series_fifo_js 的接口

series_fifo_js 的接口如下:

#ifndef TK_SERIES_FIFO_JS_H
#define TK_SERIES_FIFO_JS_H

#include "base/series_fifo.h"
#include "mvvm/jerryscript/object_js_default.h"

BEGIN_C_DECLS

struct _series_fifo_js_t;
typedef struct _series_fifo_js_t series_fifo_js_t;

typedef ret_t (*series_fifo_jsobj_set_t)(object_t* obj, jsvalue_t jsindex, jsvalue_t array);

/**
 * @class series_fifo_js_t
 * @parent series_fifo_t
 *
 * 将jerry script Array适配成series_fifo。
 *
 */
struct _series_fifo_js_t {
  series_fifo_t base;
  /**
   * @property {uint32_t} capacity
   * @annotation ["readable"]
   * FIFO的容量大小。
   */
  uint32_t capacity;
  /**
   * @property {uint32_t} unit_size
   * @annotation ["readable"]
   * FIFO中單個元素的大小。
   */
  uint32_t unit_size;
  /**
   * @property {uint32_t} capacity
   * @annotation ["readable"]
   * FIFO的容量大小。
   */
  series_fifo_jsobj_set_t jsobj_set;

  /*private*/
  object_t* native_obj;
  uint8_t* temp;
  uint8_t* event_data;
};

/**
 * @method series_fifo_js_create
 * 建立series_fifo_js對象。
 *
 * @annotation ["constructor"]
 * @param {object_t} native_obj jerry script Array的obj對象。
 * @param {uint32_t} capacity FIFO的初始容量。
 * @param {const char*} type FIFO資料類型。
 *
 * @return {object_t*} 傳回object對象。
 */
object_t* series_fifo_js_create(object_t* native_obj, uint32_t capacity, const char* type);

/* helper function */
ret_t series_fifo_jsobj_set(object_t* obj, jsvalue_t jsindex, jsvalue_t array);
ret_t series_fifo_jsobj_push(object_t* obj, jsvalue_t array);

#define SERIES_FIFO_JS(obj) (series_fifo_js_t*)(obj)

END_C_DECLS

#endif /*TK_SERIES_FIFO_JS_H*/
           

2.1 series_fifo_js 的實作

series_fifo_js 的實作如下:

#include "tkc/mem.h"
#include "tkc/utils.h"
#include "tkc/value.h"
#include "series_fifo_js.h"
#include "base/series_fifo_default.h"

#define FIFO_DATA_VALUE "value"
#define FIFO_DATA_COLOR "color"
#define FIFO_DATA_MIN "min"
#define FIFO_DATA_MAX "max"

static int series_fifo_js_base_compare(object_t* obj, const void* a, const void* b) {
  series_fifo_js_t* fifo = SERIES_FIFO_JS(obj);
  return_value_if_fail(fifo != NULL, RET_BAD_PARAMS);

  return memcmp(a, b, fifo->unit_size);
}

static void* series_fifo_js_default_get(object_t* obj, uint32_t index) {
  series_fifo_js_t* fifo = SERIES_FIFO_JS(obj);
  jsvalue_t jsobj = object_get_prop_uint32(obj, JSOBJ_PROP_NATIVE_OBJ, 0);
  jsvalue_t elem = jsobj_get_prop_value_by_index(jsobj, index);

  if (jsvalue_check(elem) == RET_OK) {
    float_t data = 0;
    jsvalue_t value = jsobj_get_prop_value(elem, FIFO_DATA_VALUE);

    data = jsvalue_to_number(value);
    memcpy(fifo->temp, &data, sizeof(float_t));

    jsvalue_unref(value);
    jsvalue_unref(elem);
  }

  return fifo->temp;
}

static void* series_fifo_js_colorful_get(object_t* obj, uint32_t index) {
  series_fifo_js_t* fifo = SERIES_FIFO_JS(obj);
  jsvalue_t jsobj = object_get_prop_uint32(obj, JSOBJ_PROP_NATIVE_OBJ, 0);
  jsvalue_t elem = jsobj_get_prop_value_by_index(jsobj, index);

  if (jsvalue_check(elem) == RET_OK) {
    series_data_colorful_t data = {0};
    jsvalue_t value = jsobj_get_prop_value(elem, FIFO_DATA_VALUE);
    jsvalue_t color = jsobj_get_prop_value(elem, FIFO_DATA_COLOR);

    data.v = jsvalue_to_number(value);
    data.c.color = jsvalue_to_number(color);
    memcpy(fifo->temp, &data, sizeof(series_data_colorful_t));

    jsvalue_unref(value);
    jsvalue_unref(color);
    jsvalue_unref(elem);
  }

  return fifo->temp;
}

static void* series_fifo_js_minmax_get(object_t* obj, uint32_t index) {
  series_fifo_js_t* fifo = SERIES_FIFO_JS(obj);
  jsvalue_t jsobj = object_get_prop_uint32(obj, JSOBJ_PROP_NATIVE_OBJ, 0);
  jsvalue_t elem = jsobj_get_prop_value_by_index(jsobj, index);

  if (jsvalue_check(elem) == RET_OK) {
    series_data_minmax_t data = {0};
    jsvalue_t min_v = jsobj_get_prop_value(elem, FIFO_DATA_MIN);
    jsvalue_t max_v = jsobj_get_prop_value(elem, FIFO_DATA_MAX);

    data.min = jsvalue_to_number(min_v);
    data.max = jsvalue_to_number(max_v);
    memcpy(fifo->temp, &data, sizeof(series_data_minmax_t));

    jsvalue_unref(min_v);
    jsvalue_unref(max_v);
    jsvalue_unref(elem);
  }

  return fifo->temp;
}

static uint8_t* series_fifo_js_prepare_set(object_t* obj, uint32_t* index, const void* data,
                                           uint32_t* nr) {
  int32_t i = 0;
  int32_t over_nr = 0;
  series_fifo_js_t* fifo = SERIES_FIFO_JS(obj);
  uint32_t unit_size = fifo->unit_size;
  uint8_t* start = (uint8_t*)(data);
  jsvalue_t jsobj = object_get_prop_uint32(obj, JSOBJ_PROP_NATIVE_OBJ, 0);
  jsvalue_t shift = jsobj_get_prop_value(jsobj, "shift");
  jsvalue_t args[1];

  if (*nr > fifo->capacity) {
    if (start != NULL) {
      start += (*nr - fifo->capacity) * unit_size;
    }
    *nr = fifo->capacity;
  }

  over_nr = *index + *nr - fifo->capacity;
  if (over_nr > 0) {
    args[0] = JS_UNDEFINED;
    for (i = 0; i < over_nr; i++) {
      jsvalue_unref(jsfunc_call(shift, jsobj, args, 1));
    }
    *index -= over_nr;
    jsvalue_unref(args[0]);
  }

  jsvalue_unref(shift);

  return start;
}

static ret_t series_fifo_js_default_set(object_t* obj, uint32_t index, const void* data,
                                        uint32_t nr) {
  value_t v;
  int32_t i = 0;
  series_fifo_js_t* fifo = SERIES_FIFO_JS(obj);
  uint32_t unit_size = fifo->unit_size;
  jsvalue_t jsobj = object_get_prop_uint32(obj, JSOBJ_PROP_NATIVE_OBJ, 0);
  jsvalue_t splice = jsobj_get_prop_value(jsobj, "splice");
  uint8_t* start = series_fifo_js_prepare_set(obj, &index, data, &nr);
  jsvalue_t args[3];

  for (i = 0; i < nr; i++) {
    float_t elem = 0;
    if (start != NULL) {
      memcpy(&elem, start + unit_size * i, unit_size);
    }

    args[0] = jsvalue_from_number(index + i);
    args[1] = jsvalue_from_number(1);
    args[2] = JS_EMPTY_OBJ;

    value_set_float(&v, elem);
    jsobj_set_prop(args[2], FIFO_DATA_VALUE, &v, NULL);

    jsvalue_unref(jsfunc_call(splice, jsobj, args, 3));
    jsvalue_unref(args[0]);
    jsvalue_unref(args[1]);
    jsvalue_unref(args[2]);
  }

  jsvalue_unref(splice);

  return RET_OK;
}

static ret_t series_fifo_js_colorful_set(object_t* obj, uint32_t index, const void* data,
                                         uint32_t nr) {
  value_t v;
  int32_t i = 0;
  series_fifo_js_t* fifo = SERIES_FIFO_JS(obj);
  uint32_t unit_size = fifo->unit_size;
  jsvalue_t jsobj = object_get_prop_uint32(obj, JSOBJ_PROP_NATIVE_OBJ, 0);
  jsvalue_t splice = jsobj_get_prop_value(jsobj, "splice");
  uint8_t* start = series_fifo_js_prepare_set(obj, &index, data, &nr);
  jsvalue_t args[3];

  for (i = 0; i < nr; i++) {
    series_data_colorful_t elem = {0};
    if (start != NULL) {
      memcpy(&elem, start + unit_size * i, unit_size);
    }

    args[0] = jsvalue_from_number(index + i);
    args[1] = jsvalue_from_number(1);
    args[2] = JS_EMPTY_OBJ;

    value_set_float(&v, elem.v);
    jsobj_set_prop(args[2], FIFO_DATA_VALUE, &v, NULL);
    value_set_uint32(&v, elem.c.color);
    jsobj_set_prop(args[2], FIFO_DATA_COLOR, &v, NULL);

    jsvalue_unref(jsfunc_call(splice, jsobj, args, 3));
    jsvalue_unref(args[0]);
    jsvalue_unref(args[1]);
    jsvalue_unref(args[2]);
  }

  jsvalue_unref(splice);

  return RET_OK;
}

static ret_t series_fifo_js_minmax_set(object_t* obj, uint32_t index, const void* data,
                                       uint32_t nr) {
  value_t v;
  int32_t i = 0;
  series_fifo_js_t* fifo = SERIES_FIFO_JS(obj);
  uint32_t unit_size = fifo->unit_size;
  jsvalue_t jsobj = object_get_prop_uint32(obj, JSOBJ_PROP_NATIVE_OBJ, 0);
  jsvalue_t splice = jsobj_get_prop_value(jsobj, "splice");
  uint8_t* start = series_fifo_js_prepare_set(obj, &index, data, &nr);
  jsvalue_t args[3];

  for (i = 0; i < nr; i++) {
    series_data_minmax_t elem = {0};
    if (start != NULL) {
      memcpy(&elem, start + unit_size * i, unit_size);
    }

    args[0] = jsvalue_from_number(index + i);
    args[1] = jsvalue_from_number(1);
    args[2] = JS_EMPTY_OBJ;

    value_set_float(&v, elem.min);
    jsobj_set_prop(args[2], FIFO_DATA_MIN, &v, NULL);
    value_set_float(&v, elem.max);
    jsobj_set_prop(args[2], FIFO_DATA_MAX, &v, NULL);

    jsvalue_unref(jsfunc_call(splice, jsobj, args, 3));
    jsvalue_unref(args[0]);
    jsvalue_unref(args[1]);
    jsvalue_unref(args[2]);
  }

  jsvalue_unref(splice);

  return RET_OK;
}

static object_t* series_fifo_js_base_part_clone(object_t* obj, uint32_t index, uint32_t nr) {
  return_value_if_fail(obj != NULL, NULL);

  object_t* clone = NULL;
  series_fifo_js_t* fifo = SERIES_FIFO_JS(obj);
  return_value_if_fail(fifo != NULL, NULL);

  clone = series_fifo_default_create(nr, fifo->unit_size);
  series_fifo_default_t* fifo_clone = SERIES_FIFO_DEFAULT(clone);
  uint32_t unit_size = fifo->unit_size;

  if (fifo_clone) {
    uint8_t* data = fifo_clone->buffer;

    for (int32_t i = 0; i < nr; i++) {
      void* iter = fifo->base.vt->get(obj, index + i);
      memcpy(data + i * unit_size, iter, unit_size);
    }

    fifo_clone->size = nr;
    fifo_clone->cursor = fifo_clone->cursor + nr - 1;
  }

  return clone;
}

static ret_t series_fifo_js_base_set_capacity(object_t* obj, uint32_t capacity) {
  series_fifo_js_t* fifo = SERIES_FIFO_JS(obj);
  return_value_if_fail(fifo != NULL && capacity > 0, RET_BAD_PARAMS);

  fifo->capacity = capacity;

  return RET_OK;
}

static uint32_t series_fifo_jsobj_get_length(jsvalue_t jsobj) {
  jsvalue_t value = jsobj_get_prop_value(jsobj, "length");
  uint32_t length = jsvalue_to_number(value);
  jsvalue_unref(value);
  return length;
}

static ret_t series_fifo_jsobj_prepare_set(object_t* obj, jsvalue_t array, uint32_t* index,
                                           uint32_t* nr) {
  uint32_t i = 0;
  jsvalue_t args[2];
  jsvalue_t jsobj = object_get_prop_uint32(obj, JSOBJ_PROP_NATIVE_OBJ, 0);
  uint32_t capacity = object_get_prop_uint32(obj, SERIES_FIFO_PROP_CAPACITY, 0);
  uint32_t size = object_get_prop_uint32(obj, SERIES_FIFO_PROP_SIZE, 0);
  jsvalue_t push = jsobj_get_prop_value(jsobj, "push");
  jsvalue_t shift = jsobj_get_prop_value(jsobj, "shift");
  return_value_if_fail(jsvalue_check(jsobj) == RET_OK, RET_BAD_PARAMS);

  args[0] = JS_UNDEFINED;
  args[1] = JS_EMPTY_OBJ;

  if (*index + *nr > size) {
    for (i = 0; i < *index + *nr - size; i++) {
      if (series_fifo_jsobj_get_length(jsobj) >= capacity) {
        jsvalue_unref(jsfunc_call(shift, array, args, 1));
      }
      jsvalue_unref(jsfunc_call(push, jsobj, &args[1], 1));
    }

    if (*nr > capacity) {
      *index = 0;
      for (i = 0; i < *nr - capacity; i++) {
        jsvalue_unref(jsfunc_call(shift, array, args, 1));
      }
      *nr = capacity;
    }
  }
  jsvalue_unref(args[0]);
  jsvalue_unref(args[1]);
  jsvalue_unref(push);
  jsvalue_unref(shift);

  return RET_OK;
}

static ret_t series_fifo_jsobj_default_set(object_t* obj, jsvalue_t jsindex, jsvalue_t array) {
  uint32_t i = 0;
  uint32_t unit_size = sizeof(float_t);
  series_fifo_js_t* fifo = SERIES_FIFO_JS(obj);
  uint32_t index = jsvalue_to_number(jsindex);
  uint32_t nr = series_fifo_jsobj_get_length(array);
  jsvalue_t jsobj = object_get_prop_uint32(obj, JSOBJ_PROP_NATIVE_OBJ, 0);
  return_value_if_fail(jsvalue_check(jsobj) == RET_OK, RET_BAD_PARAMS);

  series_fifo_jsobj_prepare_set(obj, array, &index, &nr);
  if (fifo->event_data != NULL) {
    TKMEM_FREE(fifo->event_data);
  }

  series_fifo_set_event_t e;
  fifo->event_data = TKMEM_ZALLOCN(uint8_t, nr * unit_size);
  series_fifo_set_event_init(&e, EVT_SERIES_FIFO_WILL_SET, obj);
  e.index = index;
  e.nr = nr;

  for (i = 0; i < nr; i++) {
    float_t data = 0;
    jsvalue_t elem = jsobj_get_prop_value_by_index(array, i);
    jsvalue_t value = jsobj_get_prop_value(elem, FIFO_DATA_VALUE);

    data = jsvalue_to_number(value);
    memcpy(fifo->event_data + unit_size * i, &data, unit_size);

    jsvalue_unref(value);
    jsvalue_unref(elem);
  }

  e.data = (void*)fifo->event_data;
  emitter_dispatch(EMITTER(obj), (event_t*)&e);

  return RET_OK;
}

static ret_t series_fifo_jsobj_colorful_set(object_t* obj, jsvalue_t jsindex, jsvalue_t array) {
  uint32_t i = 0;
  uint32_t unit_size = sizeof(series_data_colorful_t);
  series_fifo_js_t* fifo = SERIES_FIFO_JS(obj);
  uint32_t index = jsvalue_to_number(jsindex);
  uint32_t nr = series_fifo_jsobj_get_length(array);
  jsvalue_t jsobj = object_get_prop_uint32(obj, JSOBJ_PROP_NATIVE_OBJ, 0);
  return_value_if_fail(jsvalue_check(jsobj) == RET_OK, RET_BAD_PARAMS);

  series_fifo_jsobj_prepare_set(obj, array, &index, &nr);
  if (fifo->event_data != NULL) {
    TKMEM_FREE(fifo->event_data);
  }

  series_fifo_set_event_t e;
  fifo->event_data = TKMEM_ZALLOCN(uint8_t, nr * unit_size);
  series_fifo_set_event_init(&e, EVT_SERIES_FIFO_WILL_SET, obj);
  e.index = index;
  e.nr = nr;

  for (i = 0; i < nr; i++) {
    series_data_colorful_t data = {0};
    jsvalue_t elem = jsobj_get_prop_value_by_index(array, i);
    jsvalue_t value = jsobj_get_prop_value(elem, FIFO_DATA_VALUE);
    jsvalue_t color = jsobj_get_prop_value(elem, FIFO_DATA_COLOR);

    data.v = jsvalue_to_number(value);
    data.c.color = jsvalue_to_number(color);
    memcpy(fifo->event_data + unit_size * i, &data, unit_size);

    jsvalue_unref(value);
    jsvalue_unref(color);
    jsvalue_unref(elem);
  }

  e.data = (void*)fifo->event_data;
  emitter_dispatch(EMITTER(obj), (event_t*)&e);

  return RET_OK;
}

static ret_t series_fifo_jsobj_minmax_set(object_t* obj, jsvalue_t jsindex, jsvalue_t array) {
  uint32_t i = 0;
  uint32_t unit_size = sizeof(series_data_minmax_t);
  series_fifo_js_t* fifo = SERIES_FIFO_JS(obj);
  uint32_t index = jsvalue_to_number(jsindex);
  uint32_t nr = series_fifo_jsobj_get_length(array);
  jsvalue_t jsobj = object_get_prop_uint32(obj, JSOBJ_PROP_NATIVE_OBJ, 0);
  return_value_if_fail(jsvalue_check(jsobj) == RET_OK, RET_BAD_PARAMS);

  series_fifo_jsobj_prepare_set(obj, array, &index, &nr);
  if (fifo->event_data != NULL) {
    TKMEM_FREE(fifo->event_data);
  }

  series_fifo_set_event_t e;
  fifo->event_data = TKMEM_ZALLOCN(uint8_t, nr * unit_size);
  series_fifo_set_event_init(&e, EVT_SERIES_FIFO_WILL_SET, obj);
  e.index = index;
  e.nr = nr;

  for (i = 0; i < nr; i++) {
    series_data_minmax_t data = {0};
    jsvalue_t elem = jsobj_get_prop_value_by_index(array, i);
    jsvalue_t min_v = jsobj_get_prop_value(elem, FIFO_DATA_MIN);
    jsvalue_t max_v = jsobj_get_prop_value(elem, FIFO_DATA_MAX);

    data.min = jsvalue_to_number(min_v);
    data.max = jsvalue_to_number(max_v);
    memcpy(fifo->event_data + unit_size * i, &data, unit_size);

    jsvalue_unref(min_v);
    jsvalue_unref(max_v);
    jsvalue_unref(elem);
  }

  e.data = (void*)fifo->event_data;
  emitter_dispatch(EMITTER(obj), (event_t*)&e);

  return RET_OK;
}

ret_t series_fifo_jsobj_set(object_t* obj, jsvalue_t jsindex, jsvalue_t array) {
  series_fifo_js_t* fifo = SERIES_FIFO_JS(obj);
  return_value_if_fail(fifo != NULL, RET_BAD_PARAMS);

  return fifo->jsobj_set(obj, jsindex, array);
}

ret_t series_fifo_jsobj_push(object_t* obj, jsvalue_t array) {
  jsvalue_t args[1];
  jsvalue_t jsobj = object_get_prop_uint32(obj, JSOBJ_PROP_NATIVE_OBJ, 0);
  return_value_if_fail(jsvalue_check(jsobj) == RET_OK, RET_BAD_PARAMS);

  jsvalue_t push = jsobj_get_prop_value(jsobj, "push");
  jsvalue_t shift = jsobj_get_prop_value(jsobj, "shift");
  uint32_t capacity = object_get_prop_uint32(obj, SERIES_FIFO_PROP_CAPACITY, 0);

  args[0] = JS_UNDEFINED;
  for (uint32_t i = 0; i < series_fifo_jsobj_get_length(array); i++) {
    if (series_fifo_jsobj_get_length(jsobj) >= capacity) {
      jsvalue_unref(jsfunc_call(shift, jsobj, args, 1));
    }
    jsvalue_t elem = jsobj_get_prop_value_by_index(array, i);

    jsvalue_unref(jsfunc_call(push, jsobj, &elem, 1));
    jsvalue_unref(elem);
  }
  jsvalue_unref(args[0]);
  jsvalue_unref(push);
  jsvalue_unref(shift);

  series_fifo_push_event_t e;
  series_fifo_push_event_init(&e, EVT_SERIES_FIFO_PUSH, obj);
  e.nr = series_fifo_jsobj_get_length(array);
  emitter_dispatch(EMITTER(obj), (event_t*)&e);

  return RET_OK;
}

static ret_t series_fifo_js_on_destroy(object_t* obj) {
  series_fifo_js_t* fifo = SERIES_FIFO_JS(obj);
  return_value_if_fail(fifo != NULL, RET_BAD_PARAMS);

  OBJECT_UNREF(fifo->native_obj);

  if (fifo->temp) {
    TKMEM_FREE(fifo->temp);
  }

  if (fifo->event_data) {
    TKMEM_FREE(fifo->event_data);
  }

  return RET_OK;
}

static ret_t series_fifo_js_get_prop(object_t* obj, const char* name, value_t* v) {
  series_fifo_js_t* fifo = SERIES_FIFO_JS(obj);
  return_value_if_fail(fifo != NULL, RET_BAD_PARAMS);

  jsvalue_t jsobj = (OBJECT_JS_BASE(fifo->native_obj))->jsobj;

  if (tk_str_eq(name, JSOBJ_PROP_NATIVE_OBJ)) {
    value_set_uint32(v, jsobj);
    return RET_OK;
  } else if (tk_str_eq(name, SERIES_FIFO_PROP_CAPACITY)) {
    value_set_uint32(v, fifo->capacity);
    return RET_OK;
  } else if (tk_str_eq(name, SERIES_FIFO_PROP_SIZE)) {
    value_set_uint32(v, series_fifo_jsobj_get_length(jsobj));
    return RET_OK;
  } else if (tk_str_eq(name, SERIES_FIFO_PROP_CURSOR)) {
    value_set_uint32(v, series_fifo_jsobj_get_length(jsobj) - 1);
    return RET_OK;
  } else if (tk_str_eq(name, SERIES_FIFO_PROP_UNIT_SIZE)) {
    value_set_uint32(v, fifo->unit_size);
    return RET_OK;
  }

  return RET_NOT_FOUND;
}

static ret_t series_fifo_js_set_prop(object_t* obj, const char* name, const value_t* v) {
  series_fifo_js_t* fifo = SERIES_FIFO_JS(obj);
  return_value_if_fail(fifo != NULL, RET_BAD_PARAMS);

  if (tk_str_eq(name, SERIES_FIFO_PROP_CAPACITY)) {
    series_fifo_js_base_set_capacity(obj, value_uint32(v));
    return RET_OK;
  } else if (tk_str_eq(name, SERIES_FIFO_PROP_SIZE)) {
    return RET_OK;
  } else if (tk_str_eq(name, SERIES_FIFO_PROP_CURSOR)) {
    return RET_OK;
  }

  return RET_NOT_FOUND;
}

static const object_vtable_t s_object_vtable = {.type = "series_fifo_js",
                                                .desc = "series_fifo_js",
                                                .size = sizeof(series_fifo_js_t),
                                                .is_collection = FALSE,
                                                .on_destroy = series_fifo_js_on_destroy,
                                                .get_prop = series_fifo_js_get_prop,
                                                .set_prop = series_fifo_js_set_prop};

static const series_fifo_vtable_t s_series_fifo_js_default_vtable = {
    .part_clone = series_fifo_js_base_part_clone,
    .get = series_fifo_js_default_get,
    .set = series_fifo_js_default_set,
    .compare = series_fifo_js_base_compare,
    .set_capacity = series_fifo_js_base_set_capacity,
};

static const series_fifo_vtable_t s_series_fifo_js_colorful_vtable = {
    .part_clone = series_fifo_js_base_part_clone,
    .get = series_fifo_js_colorful_get,
    .set = series_fifo_js_colorful_set,
    .compare = series_fifo_js_base_compare,
    .set_capacity = series_fifo_js_base_set_capacity,
};

static const series_fifo_vtable_t s_series_fifo_js_minmax_vtable = {
    .part_clone = series_fifo_js_base_part_clone,
    .get = series_fifo_js_minmax_get,
    .set = series_fifo_js_minmax_set,
    .compare = series_fifo_js_base_compare,
    .set_capacity = series_fifo_js_base_set_capacity,
};

static object_t* series_fifo_js_create_internal(object_t* native_obj, uint32_t capacity,
                                                uint32_t unit_size, const series_fifo_vtable_t* vt,
                                                series_fifo_jsobj_set_t jsobj_set) {
  object_t* obj = NULL;
  series_fifo_t* series_fifo = NULL;
  series_fifo_js_t* fifo = NULL;

  obj = object_create(&s_object_vtable);
  series_fifo = SERIES_FIFO(obj);
  fifo = SERIES_FIFO_JS(obj);
  return_value_if_fail(obj != NULL && series_fifo != NULL && fifo != NULL, NULL);

  series_fifo->vt = vt;
  OBJECT_REF(native_obj);

  fifo->capacity = capacity;
  fifo->unit_size = unit_size;
  fifo->jsobj_set = jsobj_set;
  fifo->native_obj = native_obj;
  fifo->temp = TKMEM_ZALLOCN(uint8_t, unit_size * 1);
  fifo->event_data = NULL;

  return obj;
}

object_t* series_fifo_js_create(object_t* native_obj, uint32_t capacity, const char* type) {
  uint32_t unit_size = sizeof(float_t);
  series_fifo_jsobj_set_t jsobj_set = series_fifo_jsobj_default_set;
  const series_fifo_vtable_t* vt = &s_series_fifo_js_default_vtable;
  return_value_if_fail(native_obj != NULL && capacity > 0 && type != NULL, NULL);

  if (strstr(type, "colorful")) {
    vt = &s_series_fifo_js_colorful_vtable;
    jsobj_set = series_fifo_jsobj_colorful_set;
    unit_size = sizeof(series_data_colorful_t);
  } else if (strstr(type, "minmax")) {
    vt = &s_series_fifo_js_minmax_vtable;
    jsobj_set = series_fifo_jsobj_minmax_set;
    unit_size = sizeof(series_data_minmax_t);
  }

  return series_fifo_js_create_internal(native_obj, capacity, unit_size, vt, jsobj_set);
}
           

三、自定義 binder

awtk-widget-chart-view-mvvm 的自定義 binder 需要将 JS ViewModel 中指定的數組資料與 series 控件的 fifo 屬性綁定起來,并且當數組對象被修改時,需要重新綁定,代碼如下:

#include "awtk.h"
#include "mvvm/mvvm.h"
#include "chart_view/series.h"
#include "chart_view_custom_binder.h"
#include "jerryscript/series_fifo_js.h"
#include "mvvm/jerryscript/jsobj_4_mvvm.h"

#ifdef WITH_JERRYSCRIPT
#define VIEW_MODEL_FUNC_FIFO_SET "requestFifoSet"
#define VIEW_MODEL_FUNC_FIFO_PUSH "requestFifoPush"

static JSFUNC_DECL(chart_view_series_fifo_set) {
  view_model_t* vm = VIEW_MODEL(js_view_model_get_native_ptr(call_info_p->this_value));
  return_value_if_fail(vm != NULL && args_count >= 3, JS_UNDEFINED);

  int32_t i = 0;
  series_fifo_set_event_t e;
  series_fifo_set_event_init(&e, EVT_SERIES_FIFO_SET, (void*)vm);

  e.ctx = (void*)args_p;
  emitter_dispatch(EMITTER(vm), (event_t*)&e);

  return jsvalue_from_number(RET_OK);
}

static JSFUNC_DECL(chart_view_series_fifo_push) {
  view_model_t* vm = VIEW_MODEL(js_view_model_get_native_ptr(call_info_p->this_value));
  return_value_if_fail(vm != NULL && args_count >= 2, JS_UNDEFINED);

  series_fifo_push_event_t e;
  series_fifo_push_event_init(&e, EVT_SERIES_FIFO_PUSH, (void*)vm);

  e.ctx = (void*)args_p;
  emitter_dispatch(EMITTER(vm), (event_t*)&e);

  return jsvalue_from_number(RET_OK);
}

static bool_t chart_view_series_is_skip_event(data_binding_t* rule, jsvalue_t jsname) {
  str_t temp;
  bool_t ret = false;
  char* target = NULL;

  str_init(&temp, 0);
  target = jsvalue_to_utf8(jsname, &temp);
  if (!tk_str_eq(target, rule->path)) {
    ret = TRUE;
  }
  str_reset(&temp);

  return ret;
}

static ret_t chart_view_series_fifo_on_set(void* ctx, event_t* e) {
  data_binding_t* rule = DATA_BINDING(ctx);
  series_fifo_set_event_t* evt = (series_fifo_set_event_t*)e;
  jsvalue_t* args = (jsvalue_t*)evt->ctx;
  return_value_if_fail(rule != NULL && args != NULL, RET_BAD_PARAMS);

  if (!chart_view_series_is_skip_event(rule, args[0])) {
    value_t v;
    view_model_t* vm = VIEW_MODEL(e->target);
    return_value_if_fail(vm != NULL, RET_BAD_PARAMS);

    if (view_model_get_prop(vm, rule->path, &v) == RET_OK && v.type == VALUE_TYPE_OBJECT) {
      object_t* fifo = NULL;
      object_t* native_obj = value_object(&v);
      widget_t* widget = BINDING_RULE_WIDGET(rule);
      series_t* series = SERIES(widget);
      return_value_if_fail(series != NULL, RET_BAD_PARAMS);

      fifo = series->fifo;
      if ((SERIES_FIFO_JS(fifo))->native_obj != native_obj) {
        series_set_fifo(widget, native_obj);
        fifo = series->fifo;
      }
      series_fifo_jsobj_set(fifo, args[1], args[2]);
    }
  }

  return RET_OK;
}

static ret_t chart_view_series_fifo_on_push(void* ctx, event_t* e) {
  data_binding_t* rule = DATA_BINDING(ctx);
  series_fifo_push_event_t* evt = (series_fifo_push_event_t*)e;
  jsvalue_t* args = (jsvalue_t*)evt->ctx;
  return_value_if_fail(rule != NULL && args != NULL, RET_BAD_PARAMS);

  if (!chart_view_series_is_skip_event(rule, args[0])) {
    value_t v;
    view_model_t* vm = VIEW_MODEL(e->target);
    return_value_if_fail(vm != NULL, RET_BAD_PARAMS);

    if (view_model_get_prop(vm, rule->path, &v) == RET_OK && v.type == VALUE_TYPE_OBJECT) {
      object_t* fifo = NULL;
      object_t* native_obj = value_object(&v);
      widget_t* widget = BINDING_RULE_WIDGET(rule);
      series_t* series = SERIES(widget);
      return_value_if_fail(series != NULL, RET_BAD_PARAMS);

      fifo = series->fifo;
      if ((SERIES_FIFO_JS(fifo))->native_obj != native_obj) {
        series_set_fifo(widget, native_obj);
        fifo = series->fifo;
      }
      series_fifo_jsobj_push(fifo, args[1]);
    }
  }

  return RET_OK;
}

static ret_t chart_view_series_set_view_model_func(view_model_t* vm) {
  jsvalue_t jsvm = object_get_prop_uint32(OBJECT(vm), JSOBJ_PROP_NATIVE_OBJ, 0);
  return_value_if_fail(jsvalue_check(jsvm) == RET_OK, RET_BAD_PARAMS);

  if (!jsobj_has_prop_func(jsvm, VIEW_MODEL_FUNC_FIFO_SET)) {
    jsobj_set_prop_func(jsvm, VIEW_MODEL_FUNC_FIFO_SET, chart_view_series_fifo_set);
  }

  if (!jsobj_has_prop_func(jsvm, VIEW_MODEL_FUNC_FIFO_PUSH)) {
    jsobj_set_prop_func(jsvm, VIEW_MODEL_FUNC_FIFO_PUSH, chart_view_series_fifo_push);
  }

  return RET_OK;
}

static object_t* chart_view_series_prepare_fifo_mvvm(widget_t* widget, void* ctx, object_t* obj) {
  value_t v;
  object_t* curr = NULL;
  object_t* fifo = NULL;
  uint32_t capacity = 0;
  const char* curr_type = NULL;
  binding_rule_t* rule = BINDING_RULE(ctx);
  return_value_if_fail(rule != NULL, NULL);

  if (widget_get_prop(widget, SERIES_PROP_FIFO, &v) == RET_OK) {
    curr = value_object(&v);
    curr_type = object_get_type(curr);
    if (tk_str_eq(curr_type, "series_fifo_js") && (SERIES_FIFO_JS(curr))->native_obj == obj) {
      return curr;
    }
  }

  capacity = widget_get_prop_int(widget, SERIES_PROP_CAPACITY, 0);
  fifo = series_fifo_js_create(obj, capacity, widget_get_type(widget));

  return fifo;
}

static ret_t chart_view_series_bind(binding_context_t* ctx, binding_rule_t* rule) {
  if (binding_rule_is_data_binding(rule)) {
    widget_t* widget = BINDING_RULE_WIDGET(rule);
    data_binding_t* data_rule = DATA_BINDING(rule);
    return_value_if_fail(widget != NULL && data_rule != NULL && ctx != NULL, RET_BAD_PARAMS);

    if (tk_str_eq(data_rule->prop, SERIES_PROP_FIFO) && widget_is_series(widget)) {
      view_model_t* vm = ctx->view_model;
      return_value_if_fail(vm != NULL, RET_BAD_PARAMS);

      if (strstr(object_get_type(OBJECT(vm)), "jerryscript")) {
        series_set_prepare_fifo(widget, chart_view_series_prepare_fifo_mvvm, (void*)rule);
        chart_view_series_set_view_model_func(vm);

        emitter_on(EMITTER(vm), EVT_SERIES_FIFO_SET, chart_view_series_fifo_on_set, rule);
        emitter_on(EMITTER(vm), EVT_SERIES_FIFO_PUSH, chart_view_series_fifo_on_push, rule);
      }
    }
  }

  return RET_OK;
}
#else
static ret_t chart_view_series_bind(binding_context_t* ctx, binding_rule_t* rule) {
  return RET_OK;
}
#endif /*WITH_JERRYSCRIPT*/

ret_t chart_view_custom_binder_register(void) {
  custom_binder_register(WIDGET_TYPE_LINE_SERIES, chart_view_series_bind);
  custom_binder_register(WIDGET_TYPE_LINE_SERIES_COLORFUL, chart_view_series_bind);
  custom_binder_register(WIDGET_TYPE_BAR_SERIES, chart_view_series_bind);
  custom_binder_register(WIDGET_TYPE_BAR_SERIES_MINMAX, chart_view_series_bind);
  return RET_OK;
}
           

四、JS 語言綁定 chart view 控件

4.1 注冊自定義 binder

  • JS 語言綁定 chart view 控件需要注冊自定義 binder,比如,在初始化時注冊自定義 binder:
// demos\jsdemo\application.c
#include "awtk.h"
#include "mvvm/mvvm.h"
#include "chart_view_register.h"
#include "chart_view_custom_binder.h"

ret_t application_init() {
  chart_view_register();
  chart_view_custom_binder_register();

  navigator_to("system_bar");
  navigator_to("home_page");

  return RET_OK;
}
           

4.2 建立并綁定 Model

在 JS 中,使用 Array 代替 FIFO 資料類型,比如建立數組 fifo1 作為 Model:

// design\default\scripts\line_normal.js
ViewModel('line_normal', {
    data: {
        fifo1: [],
        ...
    }
    ...
});
           

在 XML 的界面描述檔案中,通過 v-data 屬性來指定 Model 即可:

<!-- design\default\ui\window_line_series.xml -->
<chart_view name="chartview" x="6%" y="13%" w="80%" h="80%">
    ...
    <line_series style="s1" v-data:fifo="{fifo1}"/>
    <line_series style="s2" v-data:fifo="{fifo2}"/>
    <tooltip/>
    ...
</chart_view>
           

4.3 Model 變化

當 Model 的資料需要變化并通知 View 進行更新時,有以下幾種方式:

4.3.1 使用自定義接口

MVVM 綁定過程中,自定義 binder 會在 ViewModel 上新增以下接口:

注意:以下新增接口隻能在 ViewModel 中調用,且這些接口預設會觸發 series 的序列點變化動畫。
  1. requestFifoSet 接口

聲明如下:

/**
 * @method requestFifoSet
 * 從特定位置開始設定資料。
 * 
 * @param {string} name Model名稱。
 * @param {number} index 位置索引。
 * @param {array} data 資料數組。
 */
requestFifoSet(name: string, index: number, data: []);
           

示例用法:

// design\default\scripts\line_normal.js
ViewModel('line_normal', {
  methods: {
        newGraph: function () {
            var rand_data1 = this._randDataGen(this.capacity);
            var rand_data2 = this._randDataGen(this.capacity);
            this.requestFifoSet('fifo1', 0, rand_data1);
            this.requestFifoSet('fifo2', 0, rand_data2);
        },
  }
});
           
  1. requestFifoPush 接口

聲明如下:

/**
 * @method requestFifoPush
 * 在尾部追加資料。
 * 
 * @param {string} name Model名稱。
 * @param {array} data 資料數組。
 */
requestFifoPush(name: string, data: []);
           

示例用法:

// design\default\scripts\line_normal.js
ViewModel('line_normal', {
  methods: {
        onTimer: function () {
            var rand_data1 = this._randDataGen(5);
            var rand_data2 = this._randDataGen(5);
            this.requestFifoPush('fifo1', rand_data1);
            this.requestFifoPush('fifo2', rand_data2);
            return RET_REPEAT;
        }
  }
});
           

4.3.2 使用原生接口

由于 Model 本身是一個數組,是以也可以直接用 JS Array 的原生接口修改 Model 資料,比如,push、shift等接口,但修改後資料并不會馬上更新到界面,需要調用 ViewModel 提供的 notifyPropsChanged() 接口通知界面更新。

注意:如果使用 Array 原生接口修改 Model 資料時改變了 Model 對象,界面更新時将自動重新綁定新的 Model 對象。

繼續閱讀