diff --git a/design/default/ui/vpage_vtrans.xml b/design/default/ui/vpage_vtrans.xml
new file mode 100644
index 000000000..73b76e995
--- /dev/null
+++ b/design/default/ui/vpage_vtrans.xml
@@ -0,0 +1,65 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/docs/changes.md b/docs/changes.md
index 8cf32dd20..897dd5983 100644
--- a/docs/changes.md
+++ b/docs/changes.md
@@ -4,6 +4,7 @@
* 重构pages。
* 修复ui\_builder不能重入的问题。
* 增加window\_manager\_set\_ignore\_input\_events。
+ * 增加vpage扩展控件(配合pages控件使用,可以自动加载/卸载页面, 并提供入场/出场动画)
2021/06/08
* 完善编译脚本(感谢兆坤提供补丁)
diff --git a/src/awtk_ext_widgets.h b/src/awtk_ext_widgets.h
index f40a87860..b5bf0b725 100644
--- a/src/awtk_ext_widgets.h
+++ b/src/awtk_ext_widgets.h
@@ -22,6 +22,7 @@
#ifndef AWTK_EXT_WIDGETS_H
#define AWTK_EXT_WIDGETS_H
+#include "vpage/vpage.h"
#include "switch/switch.h"
#include "gauge/gauge.h"
#include "gauge/gauge_pointer.h"
diff --git a/src/base/window_manager.c b/src/base/window_manager.c
index ac99b0938..387faf559 100644
--- a/src/base/window_manager.c
+++ b/src/base/window_manager.c
@@ -277,8 +277,6 @@ ret_t window_manager_dispatch_input_event(widget_t* widget, event_t* e) {
return_value_if_fail(wm->vt->dispatch_input_event != NULL, RET_BAD_PARAMS);
if (wm->ignore_input_events) {
- log_debug("waiting cursort, ignore input events");
-
return RET_STOP;
}
diff --git a/src/ext_widgets/ext_widgets.c b/src/ext_widgets/ext_widgets.c
index 24610d054..4c0f39cb9 100644
--- a/src/ext_widgets/ext_widgets.c
+++ b/src/ext_widgets/ext_widgets.c
@@ -19,7 +19,16 @@
*
*/
+#if defined(WITH_SDL)
+
+#ifndef WITH_WIDGET_VPAGE
+#define WITH_WIDGET_VPAGE
+#endif /*WITH_WIDGET_VPAGE*/
+
+#endif /*WITH_SDL*/
+
#include "ext_widgets.h"
+#include "vpage/vpage.h"
#include "switch/switch.h"
#include "gauge/gauge.h"
#include "mledit/mledit.h"
@@ -110,6 +119,11 @@ ret_t tk_ext_widgets_init(void) {
widget_factory_register(f, "guage", gauge_create);
widget_factory_register(f, "guage_pointer", gauge_pointer_create);
+
+#ifdef WITH_WIDGET_VPAGE
+ widget_factory_register(f, WIDGET_TYPE_VPAGE, vpage_create);
+#endif /*WITH_WIDGET_VPAGE*/
+
#ifdef TK_FILE_BROWSER_VIEW_H
widget_factory_register(f, WIDGET_TYPE_FILE_BROWSER_VIEW, file_browser_view_create);
#endif /*TK_FILE_BROWSER_VIEW_H*/
diff --git a/src/ext_widgets/vpage/vpage.c b/src/ext_widgets/vpage/vpage.c
new file mode 100644
index 000000000..85072a971
--- /dev/null
+++ b/src/ext_widgets/vpage/vpage.c
@@ -0,0 +1,259 @@
+/**
+ * File: vpage.c
+ * Author: AWTK Develop Team
+ * Brief: 虚拟页面(根据情况自动加载/卸载页面)。
+ *
+ * Copyright (c) 2021 - 2021 Guangzhou ZHIYUAN Electronics Co.,Ltd.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * License file for more details.
+ *
+ */
+
+/**
+ * History:
+ * ================================================================
+ * 2021-06-09 Li XianJing created
+ *
+ */
+
+#include "tkc/mem.h"
+#include "tkc/utils.h"
+#include "base/ui_loader.h"
+#include "base/window_manager.h"
+#include "ui_loader/ui_serializer.h"
+#include "widget_animators/widget_animator_prop.h"
+
+#include "vpage.h"
+
+ret_t vpage_set_ui_asset(widget_t* widget, const char* ui_asset) {
+ vpage_t* vpage = VPAGE(widget);
+ return_value_if_fail(vpage != NULL, RET_BAD_PARAMS);
+
+ vpage->ui_asset = tk_str_copy(vpage->ui_asset, ui_asset);
+
+ return RET_OK;
+}
+
+ret_t vpage_set_anim_hint(widget_t* widget, const char* anim_hint) {
+ vpage_t* vpage = VPAGE(widget);
+ return_value_if_fail(vpage != NULL, RET_BAD_PARAMS);
+
+ vpage->anim_hint = tk_str_copy(vpage->anim_hint, anim_hint);
+
+ return RET_OK;
+}
+
+static ret_t vpage_get_prop(widget_t* widget, const char* name, value_t* v) {
+ vpage_t* vpage = VPAGE(widget);
+ return_value_if_fail(vpage != NULL && name != NULL && v != NULL, RET_BAD_PARAMS);
+
+ if (tk_str_eq(VPAGE_PROP_UI_ASSET, name)) {
+ value_set_str(v, vpage->ui_asset);
+ return RET_OK;
+ } else if (tk_str_eq(WIDGET_PROP_ANIM_HINT, name)) {
+ value_set_str(v, vpage->anim_hint);
+ return RET_OK;
+ }
+
+ return RET_NOT_FOUND;
+}
+
+static ret_t vpage_set_prop(widget_t* widget, const char* name, const value_t* v) {
+ return_value_if_fail(widget != NULL && name != NULL && v != NULL, RET_BAD_PARAMS);
+
+ if (tk_str_eq(VPAGE_PROP_UI_ASSET, name)) {
+ vpage_set_ui_asset(widget, value_str(v));
+ return RET_OK;
+ } else if (tk_str_eq(WIDGET_PROP_ANIM_HINT, name)) {
+ vpage_set_anim_hint(widget, value_str(v));
+ return RET_OK;
+ }
+
+ return RET_NOT_FOUND;
+}
+
+static ret_t vpage_on_destroy(widget_t* widget) {
+ vpage_t* vpage = VPAGE(widget);
+ return_value_if_fail(widget != NULL && vpage != NULL, RET_BAD_PARAMS);
+
+ TKMEM_FREE(vpage->ui_asset);
+ TKMEM_FREE(vpage->anim_hint);
+
+ return RET_OK;
+}
+
+static ret_t vpage_dispatch_event(widget_t* widget, int32_t etype) {
+ event_t e = event_init(etype, widget);
+
+ return widget_dispatch_async(widget, &e);
+}
+
+static ret_t vpage_on_enter_done(widget_t* widget) {
+ vpage_dispatch_event(widget, EVT_VPAGE_OPEN);
+ return RET_OK;
+}
+
+static ret_t vpage_on_enter_animation_done(void* ctx, event_t* e) {
+ return vpage_on_enter_done(WIDGET(ctx));
+}
+
+static ret_t vpage_on_enter(widget_t* widget, uint32_t index, uint32_t old_index) {
+ vpage_t* vpage = VPAGE(widget);
+ widget_animator_t* am = NULL;
+ uint32_t nr = widget_count_children(widget->parent);
+ bool_t can_animate = old_index < nr && widget_is_window_opened(widget);
+
+ if (vpage->ui_asset != NULL) {
+ widget_t* children = ui_loader_load_widget(vpage->ui_asset);
+ event_t will_open = event_init(EVT_WINDOW_WILL_OPEN, children);
+ return_value_if_fail(children != NULL, RET_BAD_PARAMS);
+
+ widget_add_child(widget, children);
+ /*some widget depends on will open to initialize*/
+ widget_dispatch_recursive(children, &will_open);
+ }
+
+ widget_set_visible(widget, TRUE);
+ vpage_dispatch_event(widget, EVT_VPAGE_WILL_OPEN);
+ window_manager_set_ignore_input_events(window_manager(), FALSE);
+
+ if (vpage->anim_hint != NULL && can_animate) {
+ if (tk_str_eq(vpage->anim_hint, "htranslate")) {
+ am = widget_animator_prop_create(widget, TK_ANIMATING_TIME, 0, EASING_SIN_INOUT, "x");
+ if (old_index < index) {
+ widget_animator_prop_set_params(am, widget->w, 0);
+ } else {
+ widget_animator_prop_set_params(am, -widget->w, 0);
+ }
+ } else {
+ am = widget_animator_prop_create(widget, TK_ANIMATING_TIME, 0, EASING_SIN_INOUT, "y");
+ if (old_index < index) {
+ widget_animator_prop_set_params(am, widget->h, 0);
+ } else {
+ widget_animator_prop_set_params(am, -widget->h, 0);
+ }
+ }
+ if (am != NULL) {
+ widget_animator_start(am);
+ widget_animator_on(am, EVT_ANIM_END, vpage_on_enter_animation_done, widget);
+ } else {
+ vpage_on_enter_done(widget);
+ }
+ } else {
+ vpage_on_enter_done(widget);
+ }
+
+ return RET_OK;
+}
+
+static ret_t vpage_on_leave_done(widget_t* widget) {
+ vpage_t* vpage = VPAGE(widget);
+
+ if (vpage->ui_asset != NULL) {
+ vpage_dispatch_event(widget, EVT_VPAGE_CLOSE);
+ widget_destroy_children(widget);
+ }
+
+ widget_set_visible(widget, FALSE);
+ window_manager_set_ignore_input_events(window_manager(), FALSE);
+
+ return RET_OK;
+}
+
+static ret_t vpage_on_leave_animation_done(void* ctx, event_t* e) {
+ vpage_on_leave_done(WIDGET(ctx));
+ return RET_OK;
+}
+
+static ret_t vpage_on_leave(widget_t* widget, uint32_t index, uint32_t new_index) {
+ vpage_t* vpage = VPAGE(widget);
+
+ if (vpage->anim_hint != NULL) {
+ widget_animator_t* am = NULL;
+
+ if (tk_str_eq(vpage->anim_hint, "htranslate")) {
+ am = widget_animator_prop_create(widget, TK_ANIMATING_TIME, 0, EASING_SIN_INOUT, "x");
+ if (new_index < index) {
+ widget_animator_prop_set_params(am, 0, widget->w);
+ } else {
+ widget_animator_prop_set_params(am, 0, -widget->w);
+ }
+ } else {
+ am = widget_animator_prop_create(widget, TK_ANIMATING_TIME, 0, EASING_SIN_INOUT, "y");
+ if (new_index < index) {
+ widget_animator_prop_set_params(am, 0, widget->h);
+ } else {
+ widget_animator_prop_set_params(am, 0, -widget->h);
+ }
+ }
+
+ if (am != NULL) {
+ widget_animator_start(am);
+ widget_set_visible(widget, TRUE);
+ window_manager_set_ignore_input_events(window_manager(), TRUE);
+ widget_animator_on(am, EVT_ANIM_END, vpage_on_leave_animation_done, widget);
+ } else {
+ vpage_on_leave_done(widget);
+ }
+ } else {
+ vpage_on_leave_done(widget);
+ }
+
+ return RET_OK;
+}
+
+static ret_t vpage_on_current_page_changed(void* ctx, event_t* e) {
+ widget_t* widget = WIDGET(ctx);
+ value_change_event_t* evt = value_change_event_cast(e);
+ uint32_t old_index = value_uint32(&(evt->old_value));
+ uint32_t new_index = value_uint32(&(evt->new_value));
+ uint32_t index = widget_index_of(widget);
+
+ log_debug("%u -> %u %u\n", old_index, new_index, index);
+ if (old_index == index) {
+ vpage_on_leave(widget, index, new_index);
+ } else if (new_index == index) {
+ vpage_on_enter(widget, index, old_index);
+ }
+
+ return RET_OK;
+}
+
+static ret_t vpage_on_attach_parent(widget_t* widget, widget_t* parent) {
+ return widget_on(parent, EVT_VALUE_CHANGED, vpage_on_current_page_changed, widget);
+}
+
+static ret_t vpage_on_detach_parent(widget_t* widget, widget_t* parent) {
+ return widget_off_by_ctx(parent, widget);
+}
+
+const char* s_vpage_properties[] = {VPAGE_PROP_UI_ASSET, WIDGET_PROP_ANIM_HINT, NULL};
+
+TK_DECL_VTABLE(vpage) = {.size = sizeof(vpage_t),
+ .type = WIDGET_TYPE_VPAGE,
+ .clone_properties = s_vpage_properties,
+ .persistent_properties = s_vpage_properties,
+ .parent = TK_PARENT_VTABLE(widget),
+ .create = vpage_create,
+ .set_prop = vpage_set_prop,
+ .get_prop = vpage_get_prop,
+ .on_attach_parent = vpage_on_attach_parent,
+ .on_detach_parent = vpage_on_detach_parent,
+ .on_destroy = vpage_on_destroy};
+
+widget_t* vpage_create(widget_t* parent, xy_t x, xy_t y, wh_t w, wh_t h) {
+ widget_t* widget = widget_create(parent, TK_REF_VTABLE(vpage), x, y, w, h);
+ vpage_t* vpage = VPAGE(widget);
+ return_value_if_fail(vpage != NULL, NULL);
+
+ return widget;
+}
+
+widget_t* vpage_cast(widget_t* widget) {
+ return_value_if_fail(WIDGET_IS_INSTANCE_OF(widget, vpage), NULL);
+
+ return widget;
+}
diff --git a/src/ext_widgets/vpage/vpage.h b/src/ext_widgets/vpage/vpage.h
new file mode 100644
index 000000000..d56d54e3f
--- /dev/null
+++ b/src/ext_widgets/vpage/vpage.h
@@ -0,0 +1,178 @@
+/**
+ * File: vpage.h
+ * Author: AWTK Develop Team
+ * Brief: 虚拟页面(根据情况自动加载/卸载页面, 并提供入场/出场动画)。
+ *
+ * Copyright (c) 2021 - 2021 Guangzhou ZHIYUAN Electronics Co.,Ltd.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * License file for more details.
+ *
+ */
+
+/**
+ * History:
+ * ================================================================
+ * 2021-06-09 Li XianJing created
+ *
+ */
+
+#ifndef TK_VPAGE_H
+#define TK_VPAGE_H
+
+#include "base/widget.h"
+
+BEGIN_C_DECLS
+/**
+ * @class vpage_t
+ * @parent widget_t
+ * @annotation ["scriptable","design","widget"]
+ * 虚拟页面(根据情况自动加载/卸载页面,并提供入场/出场动画)。
+ *
+ * > 虚拟页面只能作为pages的直接子控件使用。
+ *
+ * 如果指定了ui_asset:
+ *
+ * * 当页面切换到后台时自动卸载,并触发EVT\_VPAGE\_CLOSE消息。
+ * * 当页面切换到前台时自动加载,在动画前出发EVT\_VPAGE\_WILL\_OPEN消息,在动画完成时触发 EVT\_VPAGE\_CLOSE消息。
+ *
+ * vpage\_t也可以当作普通view嵌入到pages中,让tab控件在切换时具有动画效果。
+ *
+ * 在xml中使用"vpage"标签创建控件。如:
+ *
+ * ```xml
+ *
+ *
+ * ```
+ *
+ * 可用通过style来设置控件的显示风格,如字体的大小和颜色等等(一般无需指定)。如:
+ *
+ * ```xml
+ *
+ *
+ *
+ *
+ * ```
+ */
+typedef struct _vpage_t {
+ widget_t widget;
+
+ /**
+ * @property {char*} ui_asset
+ * @annotation ["set_prop","get_prop","readable","persitent","design","scriptable"]
+ * UI资源名称。
+ */
+ char* ui_asset;
+
+ /**
+ * @property {char*} anim_hint
+ * @annotation ["set_prop","get_prop","readable","persitent","design","scriptable"]
+ * 动画类型(目前支持:vtranslate: 垂直平移,htranslate: 水平平移)。
+ */
+ char* anim_hint;
+} vpage_t;
+
+/**
+ * @enum vpage_event_t
+ * @annotation ["scriptable"]
+ * @prefix EVT_
+ * 虚拟页面的事件。
+ */
+typedef enum _vpage_event_t {
+ /**
+ * @const EVT_VPAGE_WILL_OPEN
+ * 页面即将打开(动画前)。
+ */
+ EVT_VPAGE_WILL_OPEN = EVT_USER_START + 100,
+ /**
+ * @const EVT_VPAGE_OPEN
+ * 页面打开完成(动画后)。
+ */
+ EVT_VPAGE_OPEN,
+ /**
+ * @const EVT_VPAGE_CLOSE
+ * 页面已经关闭(动画后)。
+ */
+ EVT_VPAGE_CLOSE
+} vpage_event_t;
+
+/**
+ * @event {event_t} EVT_VPAGE_WILL_OPEN
+ * 页面即将打开(动画前)。
+ */
+
+/**
+ * @event {event_t} EVT_VPAGE_OPEN
+ * 页面打开完成(动画后)。
+ */
+
+/**
+ * @event {event_t} EVT_VPAGE_CLOSE
+ * 页面已经关闭(动画后)。
+ */
+
+/**
+ * @method vpage_create
+ * @annotation ["constructor", "scriptable"]
+ * 创建vpage对象
+ * @param {widget_t*} parent 父控件
+ * @param {xy_t} x x坐标
+ * @param {xy_t} y y坐标
+ * @param {wh_t} w 宽度
+ * @param {wh_t} h 高度
+ *
+ * @return {widget_t*} vpage对象。
+ */
+widget_t* vpage_create(widget_t* parent, xy_t x, xy_t y, wh_t w, wh_t h);
+
+/**
+ * @method vpage_cast
+ * 转换为vpage对象(供脚本语言使用)。
+ * @annotation ["cast", "scriptable"]
+ * @param {widget_t*} widget vpage对象。
+ *
+ * @return {widget_t*} vpage对象。
+ */
+widget_t* vpage_cast(widget_t* widget);
+
+/**
+ * @method vpage_set_ui_asset
+ * 设置 UI资源名称。
+ * @annotation ["scriptable"]
+ * @param {widget_t*} widget widget对象。
+ * @param {const char*} ui_asset UI资源名称。
+ *
+ * @return {ret_t} 返回RET_OK表示成功,否则表示失败。
+ */
+ret_t vpage_set_ui_asset(widget_t* widget, const char* ui_asset);
+
+/**
+ * @method vpage_set_anim_hint
+ * 设置动画类型(vtranslate: 垂直平移,htranslate: 水平平移)。
+ * @annotation ["scriptable"]
+ * @param {widget_t*} widget widget对象。
+ * @param {const char*} anim_hint 动画类型。
+ *
+ * @return {ret_t} 返回RET_OK表示成功,否则表示失败。
+ */
+ret_t vpage_set_anim_hint(widget_t* widget, const char* anim_hint);
+
+#define VPAGE_PROP_UI_ASSET "ui_asset"
+
+#define VPAGE_ANIM_VTRANSLATE "vtranslate"
+#define VPAGE_ANIM_HTRANSLATE "htranslate"
+
+#define WIDGET_TYPE_VPAGE "vpage"
+
+#define VPAGE(widget) ((vpage_t*)(vpage_cast(WIDGET(widget))))
+
+/*public for subclass and runtime type check*/
+TK_EXTERN_VTABLE(vpage);
+
+END_C_DECLS
+
+#endif /*TK_VPAGE_H*/