mirror of
https://gitee.com/zlgopen/awtk.git
synced 2024-11-30 02:58:26 +08:00
make candidates scollable
This commit is contained in:
parent
a4f349c414
commit
4a79ec7bd1
@ -3,6 +3,7 @@
|
||||
* 增加颜色格式描述文档。
|
||||
* FAQ增加半透明相关的问答。
|
||||
* tab button放在scrollview中,激活时自动滚动到可见区域。
|
||||
* 候选字支持左右滚动。
|
||||
|
||||
* 2019/05/29
|
||||
* 增加overlay窗口
|
||||
|
327
src/base/hscrollable.c
Normal file
327
src/base/hscrollable.c
Normal file
@ -0,0 +1,327 @@
|
||||
/**
|
||||
* File: hscrollable.c
|
||||
* Author: AWTK Develop Team
|
||||
* Brief: hscrollable
|
||||
*
|
||||
* Copyright (c) 2018 - 2019 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:
|
||||
* ================================================================
|
||||
* 2019-05-30 Li XianJing <xianjimli@hotmail.com> created
|
||||
*
|
||||
*/
|
||||
|
||||
#include "tkc/mem.h"
|
||||
#include "tkc/utf8.h"
|
||||
#include "tkc/utils.h"
|
||||
#include "base/hscrollable.h"
|
||||
#include "base/widget_vtable.h"
|
||||
#include "widget_animators/widget_animator_scroll.h"
|
||||
|
||||
static widget_t* hscrollable_get_widget(hscrollable_t* hscrollable) {
|
||||
return hscrollable != NULL ? hscrollable->widget : NULL;
|
||||
}
|
||||
|
||||
static ret_t hscrollable_invalidate_self(hscrollable_t* hscrollable) {
|
||||
rect_t r;
|
||||
widget_t* widget = hscrollable_get_widget(hscrollable);
|
||||
return_value_if_fail(hscrollable != NULL && widget != NULL, RET_BAD_PARAMS);
|
||||
|
||||
widget->dirty = FALSE;
|
||||
r = rect_init(widget->x, widget->y, widget->w, widget->h);
|
||||
return widget_invalidate(widget->parent, &r);
|
||||
}
|
||||
|
||||
static ret_t hscrollable_on_pointer_down(hscrollable_t* hscrollable, pointer_event_t* e) {
|
||||
velocity_t* v = &(hscrollable->velocity);
|
||||
|
||||
velocity_reset(v);
|
||||
hscrollable->down.x = e->x;
|
||||
hscrollable->down.y = e->y;
|
||||
hscrollable->xoffset_save = hscrollable->xoffset;
|
||||
|
||||
velocity_update(v, e->e.time, e->x, e->y);
|
||||
|
||||
return RET_OK;
|
||||
}
|
||||
|
||||
static ret_t hscrollable_on_pointer_move(hscrollable_t* hscrollable, pointer_event_t* e) {
|
||||
velocity_t* v = &(hscrollable->velocity);
|
||||
int32_t dx = e->x - hscrollable->down.x;
|
||||
velocity_update(v, e->e.time, e->x, e->y);
|
||||
|
||||
hscrollable->xoffset = hscrollable->xoffset_save - dx;
|
||||
|
||||
return RET_OK;
|
||||
}
|
||||
|
||||
static ret_t hscrollable_on_scroll_done(void* ctx, event_t* e) {
|
||||
hscrollable_t* hscrollable = (hscrollable_t*)(ctx);
|
||||
return_value_if_fail(hscrollable != NULL, RET_BAD_PARAMS);
|
||||
|
||||
hscrollable->wa = NULL;
|
||||
hscrollable_invalidate_self(hscrollable);
|
||||
|
||||
return RET_REMOVE;
|
||||
}
|
||||
|
||||
static ret_t hscrollable_fix_end_offset_default(hscrollable_t* hscrollable) {
|
||||
int32_t xoffset_end = 0;
|
||||
widget_t* widget = hscrollable_get_widget(hscrollable);
|
||||
return_value_if_fail(hscrollable != NULL && widget != NULL, RET_BAD_PARAMS);
|
||||
|
||||
xoffset_end = hscrollable->xoffset_end;
|
||||
xoffset_end = tk_max(xoffset_end, 0);
|
||||
xoffset_end = tk_min(xoffset_end, (hscrollable->virtual_w - widget->w));
|
||||
|
||||
if (hscrollable->virtual_w <= widget->w) {
|
||||
xoffset_end = 0;
|
||||
}
|
||||
|
||||
hscrollable->xoffset_end = xoffset_end;
|
||||
|
||||
return RET_OK;
|
||||
}
|
||||
|
||||
ret_t hscrollable_scroll_to(hscrollable_t* hscrollable, int32_t xoffset_end, int32_t duration) {
|
||||
int32_t xoffset = 0;
|
||||
widget_t* widget = hscrollable_get_widget(hscrollable);
|
||||
return_value_if_fail(hscrollable != NULL && widget != NULL, RET_FAIL);
|
||||
|
||||
hscrollable_fix_end_offset_default(hscrollable);
|
||||
xoffset_end = hscrollable->xoffset_end;
|
||||
xoffset = hscrollable->xoffset;
|
||||
if (xoffset == xoffset_end) {
|
||||
return RET_OK;
|
||||
}
|
||||
|
||||
hscrollable->xoffset_end = xoffset_end;
|
||||
xoffset_end = hscrollable->xoffset_end;
|
||||
|
||||
hscrollable->wa = widget_animator_scroll_create(widget, TK_ANIMATING_TIME, 0, EASING_SIN_INOUT);
|
||||
return_value_if_fail(hscrollable->wa != NULL, RET_OOM);
|
||||
|
||||
widget_animator_scroll_set_params(hscrollable->wa, xoffset, 0, xoffset_end, 0);
|
||||
widget_animator_on(hscrollable->wa, EVT_ANIM_END, hscrollable_on_scroll_done, hscrollable);
|
||||
widget_animator_start(hscrollable->wa);
|
||||
|
||||
return RET_OK;
|
||||
}
|
||||
|
||||
#define SPEED_SCALE 2
|
||||
#define MIN_DELTA 10
|
||||
|
||||
static ret_t hscrollable_on_pointer_up(hscrollable_t* hscrollable, pointer_event_t* e) {
|
||||
velocity_t* v = &(hscrollable->velocity);
|
||||
int32_t move_dx = e->x - hscrollable->down.x;
|
||||
|
||||
velocity_update(v, e->e.time, e->x, e->y);
|
||||
if (move_dx) {
|
||||
int xv = tk_min(v->xv, 100);
|
||||
|
||||
hscrollable->xoffset_end = hscrollable->xoffset - xv;
|
||||
hscrollable_scroll_to(hscrollable, hscrollable->xoffset_end, 300);
|
||||
}
|
||||
|
||||
return RET_OK;
|
||||
}
|
||||
|
||||
ret_t hscrollable_on_event(hscrollable_t* hscrollable, event_t* e) {
|
||||
ret_t ret = RET_OK;
|
||||
uint16_t type = e->type;
|
||||
widget_t* widget = hscrollable_get_widget(hscrollable);
|
||||
return_value_if_fail(hscrollable != NULL && widget != NULL && e != NULL, RET_BAD_PARAMS);
|
||||
|
||||
if (hscrollable->wa != NULL) {
|
||||
log_debug("animating ignore event\n");
|
||||
|
||||
return RET_STOP;
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
case EVT_POINTER_DOWN:
|
||||
hscrollable->dragged = FALSE;
|
||||
widget_grab(widget->parent, widget);
|
||||
hscrollable_on_pointer_down(hscrollable, (pointer_event_t*)e);
|
||||
break;
|
||||
case EVT_POINTER_UP: {
|
||||
pointer_event_t* evt = (pointer_event_t*)e;
|
||||
int32_t dx = evt->x - hscrollable->down.x;
|
||||
if (dx) {
|
||||
hscrollable_on_pointer_up(hscrollable, (pointer_event_t*)e);
|
||||
}
|
||||
hscrollable->dragged = FALSE;
|
||||
widget_ungrab(widget->parent, widget);
|
||||
break;
|
||||
}
|
||||
case EVT_POINTER_MOVE: {
|
||||
pointer_event_t* evt = (pointer_event_t*)e;
|
||||
if (!evt->pressed) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (hscrollable->dragged) {
|
||||
hscrollable_on_pointer_move(hscrollable, evt);
|
||||
hscrollable_invalidate_self(hscrollable);
|
||||
} else {
|
||||
int32_t delta = evt->x - hscrollable->down.x;
|
||||
|
||||
if (tk_abs(delta) >= TK_DRAG_THRESHOLD) {
|
||||
pointer_event_t abort = *evt;
|
||||
|
||||
abort.e.type = EVT_POINTER_DOWN_ABORT;
|
||||
widget_dispatch_event_to_target_recursive(widget, (event_t*)(&abort));
|
||||
|
||||
hscrollable->dragged = TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
ret = hscrollable->dragged ? RET_STOP : RET_OK;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret_t hscrollable_invalidate(hscrollable_t* hscrollable, rect_t* r) {
|
||||
rect_t r_self;
|
||||
widget_t* widget = hscrollable_get_widget(hscrollable);
|
||||
return_value_if_fail(hscrollable != NULL && widget != NULL, RET_BAD_PARAMS);
|
||||
|
||||
r_self = rect_init(0, 0, widget->w, widget->h);
|
||||
|
||||
r->x += widget->x;
|
||||
r->x -= hscrollable->xoffset;
|
||||
|
||||
*r = rect_intersect(r, &r_self);
|
||||
|
||||
if (r->w <= 0 || r->h <= 0) {
|
||||
return RET_OK;
|
||||
}
|
||||
|
||||
if (widget->parent) {
|
||||
widget_invalidate(widget->parent, r);
|
||||
}
|
||||
|
||||
return RET_OK;
|
||||
}
|
||||
|
||||
ret_t hscrollable_on_paint_children(hscrollable_t* hscrollable, canvas_t* c) {
|
||||
rect_t r_save;
|
||||
widget_t* widget = hscrollable_get_widget(hscrollable);
|
||||
return_value_if_fail(hscrollable != NULL && c != NULL && widget != NULL, RET_BAD_PARAMS);
|
||||
|
||||
rect_t r = rect_init(c->ox, c->oy, widget->w, widget->h);
|
||||
|
||||
int32_t xoffset = -hscrollable->xoffset;
|
||||
|
||||
canvas_translate(c, xoffset, 0);
|
||||
canvas_get_clip_rect(c, &r_save);
|
||||
|
||||
r = rect_intersect(&r, &r_save);
|
||||
canvas_set_clip_rect(c, &r);
|
||||
widget_on_paint_children_default(widget, c);
|
||||
canvas_set_clip_rect(c, &r_save);
|
||||
canvas_untranslate(c, xoffset, 0);
|
||||
|
||||
return RET_OK;
|
||||
}
|
||||
|
||||
ret_t hscrollable_get_prop(hscrollable_t* hscrollable, const char* name, value_t* v) {
|
||||
widget_t* widget = hscrollable_get_widget(hscrollable);
|
||||
return_value_if_fail(widget != NULL && hscrollable != NULL && name != NULL && v != NULL,
|
||||
RET_BAD_PARAMS);
|
||||
|
||||
if (tk_str_eq(name, WIDGET_PROP_VIRTUAL_W) || tk_str_eq(name, WIDGET_PROP_LAYOUT_W)) {
|
||||
value_set_int(v, tk_max(widget->w, hscrollable->virtual_w));
|
||||
return RET_OK;
|
||||
} else if (tk_str_eq(name, WIDGET_PROP_VIRTUAL_H) || tk_str_eq(name, WIDGET_PROP_LAYOUT_H)) {
|
||||
value_set_int(v, widget->h);
|
||||
return RET_OK;
|
||||
} else if (tk_str_eq(name, WIDGET_PROP_XOFFSET)) {
|
||||
value_set_int(v, hscrollable->xoffset);
|
||||
return RET_OK;
|
||||
} else if (tk_str_eq(name, WIDGET_PROP_YOFFSET)) {
|
||||
value_set_int(v, 0);
|
||||
return RET_OK;
|
||||
} else if (tk_str_eq(name, WIDGET_PROP_XSLIDABLE)) {
|
||||
value_set_bool(v, TRUE);
|
||||
return RET_OK;
|
||||
} else if (tk_str_eq(name, WIDGET_PROP_YSLIDABLE)) {
|
||||
value_set_bool(v, FALSE);
|
||||
return RET_OK;
|
||||
}
|
||||
|
||||
return RET_NOT_FOUND;
|
||||
}
|
||||
|
||||
ret_t hscrollable_set_prop(hscrollable_t* hscrollable, const char* name, const value_t* v) {
|
||||
return_value_if_fail(hscrollable != NULL && name != NULL && v != NULL, RET_BAD_PARAMS);
|
||||
|
||||
if (tk_str_eq(name, WIDGET_PROP_VIRTUAL_W)) {
|
||||
hscrollable->virtual_w = value_int(v);
|
||||
return RET_OK;
|
||||
} else if (tk_str_eq(name, WIDGET_PROP_XSLIDABLE)) {
|
||||
return RET_OK;
|
||||
} else if (tk_str_eq(name, WIDGET_PROP_XOFFSET)) {
|
||||
hscrollable->xoffset = value_int(v);
|
||||
hscrollable_invalidate_self(hscrollable);
|
||||
return RET_OK;
|
||||
} else if (tk_str_eq(name, WIDGET_PROP_YOFFSET)) {
|
||||
return RET_OK;
|
||||
}
|
||||
|
||||
return RET_NOT_FOUND;
|
||||
}
|
||||
|
||||
hscrollable_t* hscrollable_create(widget_t* widget) {
|
||||
hscrollable_t* hscrollable = NULL;
|
||||
return_value_if_fail(widget != NULL, NULL);
|
||||
|
||||
hscrollable = TKMEM_ZALLOC(hscrollable_t);
|
||||
return_value_if_fail(hscrollable != NULL, NULL);
|
||||
|
||||
hscrollable->widget = widget;
|
||||
|
||||
return hscrollable;
|
||||
}
|
||||
|
||||
ret_t hscrollable_set_always_scrollable(hscrollable_t* hscrollable, bool_t always_scrollable) {
|
||||
return_value_if_fail(hscrollable != NULL, RET_BAD_PARAMS);
|
||||
hscrollable->always_scrollable = always_scrollable;
|
||||
|
||||
return RET_OK;
|
||||
}
|
||||
|
||||
ret_t hscrollable_set_xoffset(hscrollable_t* hscrollable, int32_t xoffset) {
|
||||
return_value_if_fail(hscrollable != NULL, RET_BAD_PARAMS);
|
||||
hscrollable->xoffset = xoffset;
|
||||
|
||||
return RET_OK;
|
||||
}
|
||||
|
||||
ret_t hscrollable_set_virtual_w(hscrollable_t* hscrollable, int32_t virtual_w) {
|
||||
return_value_if_fail(hscrollable != NULL, RET_BAD_PARAMS);
|
||||
hscrollable->virtual_w = virtual_w;
|
||||
|
||||
return RET_OK;
|
||||
}
|
||||
|
||||
ret_t hscrollable_destroy(hscrollable_t* hscrollable) {
|
||||
return_value_if_fail(hscrollable != NULL, RET_BAD_PARAMS);
|
||||
|
||||
TKMEM_FREE(hscrollable);
|
||||
|
||||
return RET_OK;
|
||||
}
|
65
src/base/hscrollable.h
Normal file
65
src/base/hscrollable.h
Normal file
@ -0,0 +1,65 @@
|
||||
/**
|
||||
* File: hscrollable.h
|
||||
* Author: AWTK Develop Team
|
||||
* Brief: input method text hscrollable
|
||||
*
|
||||
* Copyright (c) 2018 - 2019 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:
|
||||
* ================================================================
|
||||
* 2019-05-30 Li XianJing <xianjimli@hotmail.com> created
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef TK_HSCROLLABLE_H
|
||||
#define TK_HSCROLLABLE_H
|
||||
|
||||
#include "tkc/str.h"
|
||||
#include "base/widget.h"
|
||||
#include "base/velocity.h"
|
||||
#include "base/widget_animator.h"
|
||||
|
||||
BEGIN_C_DECLS
|
||||
|
||||
/**
|
||||
* @class hscrollable_t
|
||||
* 帮助实现控件左右滚动的效果。
|
||||
*/
|
||||
typedef struct _hscrollable_t {
|
||||
point_t down;
|
||||
bool_t dragged;
|
||||
int32_t xoffset;
|
||||
int32_t xoffset_end;
|
||||
int32_t xoffset_save;
|
||||
int32_t virtual_w;
|
||||
|
||||
widget_t* widget;
|
||||
velocity_t velocity;
|
||||
widget_animator_t* wa;
|
||||
bool_t always_scrollable;
|
||||
} hscrollable_t;
|
||||
|
||||
hscrollable_t* hscrollable_create(widget_t* widget);
|
||||
ret_t hscrollable_set_xoffset(hscrollable_t* hscrollable, int32_t xoffset);
|
||||
ret_t hscrollable_set_virtual_w(hscrollable_t* hscrollable, int32_t virtual_w);
|
||||
ret_t hscrollable_set_always_scrollable(hscrollable_t* hscrollable, bool_t always_scrollable);
|
||||
|
||||
ret_t hscrollable_invalidate(hscrollable_t* hscrollable, rect_t* r);
|
||||
ret_t hscrollable_on_event(hscrollable_t* hscrollable, event_t* e);
|
||||
ret_t hscrollable_get_prop(hscrollable_t* hscrollable, const char* name, value_t* v);
|
||||
ret_t hscrollable_set_prop(hscrollable_t* hscrollable, const char* name, const value_t* v);
|
||||
ret_t hscrollable_on_paint_children(hscrollable_t* hscrollable, canvas_t* c);
|
||||
|
||||
ret_t hscrollable_destroy(hscrollable_t* hscrollable);
|
||||
|
||||
END_C_DECLS
|
||||
|
||||
#endif /*TK_HSCROLLABLE_H*/
|
@ -104,17 +104,19 @@ static ret_t candidates_relayout_children(widget_t* widget) {
|
||||
for (i = 0; i < nr; i++) {
|
||||
iter = children[i];
|
||||
child_w = candidates_calc_child_width(candidates->canvas, iter);
|
||||
if ((child_x + child_w + margin) < w && iter->text.size > 0) {
|
||||
if (iter->text.size) {
|
||||
widget_set_visible(iter, TRUE, FALSE);
|
||||
widget_move_resize(iter, child_x, child_y, child_w, child_h);
|
||||
} else {
|
||||
child_w = 0;
|
||||
widget_set_visible(iter, FALSE, FALSE);
|
||||
widget_move_resize(iter, 0, 0, 0, 0);
|
||||
}
|
||||
|
||||
widget_move_resize(iter, child_x, child_y, child_w, child_h);
|
||||
child_x += child_w + margin;
|
||||
}
|
||||
|
||||
hscrollable_set_xoffset(candidates->hscrollable, 0);
|
||||
hscrollable_set_virtual_w(candidates->hscrollable, child_x);
|
||||
|
||||
return RET_OK;
|
||||
}
|
||||
|
||||
@ -150,6 +152,7 @@ static ret_t candidates_on_destroy_default(widget_t* widget) {
|
||||
candidates_t* candidates = CANDIDATES(widget);
|
||||
return_value_if_fail(widget != NULL && candidates != NULL, RET_BAD_PARAMS);
|
||||
|
||||
hscrollable_destroy(candidates->hscrollable);
|
||||
input_method_off(input_method(), candidates->event_id);
|
||||
|
||||
return RET_OK;
|
||||
@ -163,11 +166,52 @@ static ret_t candidates_on_paint_self(widget_t* widget, canvas_t* c) {
|
||||
return widget_paint_helper(widget, c, NULL, NULL);
|
||||
}
|
||||
|
||||
static ret_t candidates_on_event(widget_t* widget, event_t* e) {
|
||||
candidates_t* candidates = CANDIDATES(widget);
|
||||
return_value_if_fail(candidates != NULL, RET_BAD_PARAMS);
|
||||
|
||||
return hscrollable_on_event(candidates->hscrollable, e);
|
||||
}
|
||||
|
||||
static ret_t candidates_invalidate(widget_t* widget, rect_t* r) {
|
||||
candidates_t* candidates = CANDIDATES(widget);
|
||||
return_value_if_fail(candidates != NULL, RET_BAD_PARAMS);
|
||||
|
||||
return hscrollable_invalidate(candidates->hscrollable, r);
|
||||
}
|
||||
|
||||
static ret_t candidates_on_paint_children(widget_t* widget, canvas_t* c) {
|
||||
candidates_t* candidates = CANDIDATES(widget);
|
||||
return_value_if_fail(candidates != NULL, RET_BAD_PARAMS);
|
||||
|
||||
return hscrollable_on_paint_children(candidates->hscrollable, c);
|
||||
}
|
||||
|
||||
static ret_t candidates_get_prop(widget_t* widget, const char* name, value_t* v) {
|
||||
candidates_t* candidates = CANDIDATES(widget);
|
||||
return_value_if_fail(candidates != NULL, RET_BAD_PARAMS);
|
||||
|
||||
return hscrollable_get_prop(candidates->hscrollable, name, v);
|
||||
}
|
||||
|
||||
static ret_t candidates_set_prop(widget_t* widget, const char* name, const value_t* v) {
|
||||
candidates_t* candidates = CANDIDATES(widget);
|
||||
return_value_if_fail(candidates != NULL, RET_BAD_PARAMS);
|
||||
|
||||
return hscrollable_set_prop(candidates->hscrollable, name, v);
|
||||
}
|
||||
|
||||
TK_DECL_VTABLE(candidates) = {.size = sizeof(candidates_t),
|
||||
.scrollable = TRUE,
|
||||
.type = WIDGET_TYPE_CANDIDATES,
|
||||
.parent = TK_PARENT_VTABLE(widget),
|
||||
.create = candidates_create,
|
||||
.on_event = candidates_on_event,
|
||||
.invalidate = candidates_invalidate,
|
||||
.on_paint_self = candidates_on_paint_self,
|
||||
.on_paint_children = candidates_on_paint_children,
|
||||
.get_prop = candidates_get_prop,
|
||||
.set_prop = candidates_set_prop,
|
||||
.on_destroy = candidates_on_destroy_default};
|
||||
|
||||
static ret_t candidates_on_im_candidates_event(void* ctx, event_t* e) {
|
||||
@ -182,9 +226,12 @@ widget_t* candidates_create(widget_t* parent, xy_t x, xy_t y, wh_t w, wh_t h) {
|
||||
candidates_t* candidates = CANDIDATES(widget);
|
||||
return_value_if_fail(candidates != NULL, NULL);
|
||||
|
||||
candidates->hscrollable = hscrollable_create(widget);
|
||||
candidates->event_id = input_method_on(input_method(), EVT_IM_SHOW_CANDIDATES,
|
||||
candidates_on_im_candidates_event, candidates);
|
||||
|
||||
ENSURE(candidates->hscrollable != NULL);
|
||||
|
||||
return widget;
|
||||
}
|
||||
|
||||
|
@ -24,6 +24,7 @@
|
||||
|
||||
#include "tkc/str.h"
|
||||
#include "base/widget.h"
|
||||
#include "base/hscrollable.h"
|
||||
|
||||
BEGIN_C_DECLS
|
||||
|
||||
@ -41,6 +42,7 @@ typedef struct _candidates_t {
|
||||
wh_t normal_h;
|
||||
uint32_t event_id;
|
||||
canvas_t* canvas;
|
||||
hscrollable_t* hscrollable;
|
||||
} candidates_t;
|
||||
|
||||
/**
|
||||
|
Loading…
Reference in New Issue
Block a user