diff --git a/CHANGELOG.md b/CHANGELOG.md index bf5f88de7..2add41cd1 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ All notable changes to this project will be documented in this file. - Berry `bytes` methods `setbits`/`getbits` transposed to native and support for big endian (#24857) - HASPmota ability to set default screen background on `p0b0` object (#24874) - Matter virtual IR HVAC thermostat support (#24821) +- HASPmota and LVGL `stripes` widget ### Breaking Changed diff --git a/lib/libesp32_lvgl/lv_binding_berry/generate/LVGL_API_Reference.md b/lib/libesp32_lvgl/lv_binding_berry/generate/LVGL_API_Reference.md index 0f3cdf29d..bcf6f58b4 100644 --- a/lib/libesp32_lvgl/lv_binding_berry/generate/LVGL_API_Reference.md +++ b/lib/libesp32_lvgl/lv_binding_berry/generate/LVGL_API_Reference.md @@ -1811,3 +1811,20 @@ set_mode|int||[lv_colorwheel_set_mode](https://docs.lvgl.io/9.0/search.html?q=lv set_mode_fixed|bool||[lv_colorwheel_set_mode_fixed](https://docs.lvgl.io/9.0/search.html?q=lv_colorwheel_set_mode_fixed) set_rgb|lv.color|bool|[lv_colorwheel_set_rgb](https://docs.lvgl.io/9.0/search.html?q=lv_colorwheel_set_rgb) +### widget `lv.stripes` + +Method|Arguments|Return type|LVGL equivalent +:---|:---|:---|:--- +get_angle||int|[lv_stripes_get_angle](https://docs.lvgl.io/9.0/search.html?q=lv_stripes_get_angle) +get_gap_width||int|[lv_stripes_get_gap_width](https://docs.lvgl.io/9.0/search.html?q=lv_stripes_get_gap_width) +get_offset||int|[lv_stripes_get_offset](https://docs.lvgl.io/9.0/search.html?q=lv_stripes_get_offset) +get_stripe_color||lv.color|[lv_stripes_get_stripe_color](https://docs.lvgl.io/9.0/search.html?q=lv_stripes_get_stripe_color) +get_stripe_opa||int|[lv_stripes_get_stripe_opa](https://docs.lvgl.io/9.0/search.html?q=lv_stripes_get_stripe_opa) +get_stripe_width||int|[lv_stripes_get_stripe_width](https://docs.lvgl.io/9.0/search.html?q=lv_stripes_get_stripe_width) +set_angle|int||[lv_stripes_set_angle](https://docs.lvgl.io/9.0/search.html?q=lv_stripes_set_angle) +set_gap_width|int||[lv_stripes_set_gap_width](https://docs.lvgl.io/9.0/search.html?q=lv_stripes_set_gap_width) +set_offset|int||[lv_stripes_set_offset](https://docs.lvgl.io/9.0/search.html?q=lv_stripes_set_offset) +set_stripe_color|lv.color||[lv_stripes_set_stripe_color](https://docs.lvgl.io/9.0/search.html?q=lv_stripes_set_stripe_color) +set_stripe_opa|int||[lv_stripes_set_stripe_opa](https://docs.lvgl.io/9.0/search.html?q=lv_stripes_set_stripe_opa) +set_stripe_width|int||[lv_stripes_set_stripe_width](https://docs.lvgl.io/9.0/search.html?q=lv_stripes_set_stripe_width) + diff --git a/lib/libesp32_lvgl/lv_binding_berry/generate/be_lv_c_mapping.h b/lib/libesp32_lvgl/lv_binding_berry/generate/be_lv_c_mapping.h index 12f75fe5a..6fbd08f08 100644 --- a/lib/libesp32_lvgl/lv_binding_berry/generate/be_lv_c_mapping.h +++ b/lib/libesp32_lvgl/lv_binding_berry/generate/be_lv_c_mapping.h @@ -10,6 +10,7 @@ extern "C" { #include "be_ctypes.h" #include "be_mapping.h" #include "../src/lv_colorwheel.h" +#include "../src/lv_stripes.h" /* `lv_style` methods */ const be_ntv_func_def_t lv_style_func[] = { @@ -1646,6 +1647,24 @@ const be_ntv_func_def_t lv_colorwheel_func[] = { }; #endif // BE_LV_WIDGET_COLORWHEEL +/* `lv_stripes` methods */ +#ifdef BE_LV_WIDGET_STRIPES +const be_ntv_func_def_t lv_stripes_func[] = { + { "get_angle", { (const void*) &lv_stripes_get_angle, "i", "(lv.obj)" } }, + { "get_gap_width", { (const void*) &lv_stripes_get_gap_width, "i", "(lv.obj)" } }, + { "get_offset", { (const void*) &lv_stripes_get_offset, "i", "(lv.obj)" } }, + { "get_stripe_color", { (const void*) &lv_stripes_get_stripe_color, "lv.color", "(lv.obj)" } }, + { "get_stripe_opa", { (const void*) &lv_stripes_get_stripe_opa, "i", "(lv.obj)" } }, + { "get_stripe_width", { (const void*) &lv_stripes_get_stripe_width, "i", "(lv.obj)" } }, + { "set_angle", { (const void*) &lv_stripes_set_angle, "", "(lv.obj)i" } }, + { "set_gap_width", { (const void*) &lv_stripes_set_gap_width, "", "(lv.obj)i" } }, + { "set_offset", { (const void*) &lv_stripes_set_offset, "", "(lv.obj)i" } }, + { "set_stripe_color", { (const void*) &lv_stripes_set_stripe_color, "", "(lv.obj)(lv.color)" } }, + { "set_stripe_opa", { (const void*) &lv_stripes_set_stripe_opa, "", "(lv.obj)i" } }, + { "set_stripe_width", { (const void*) &lv_stripes_set_stripe_width, "", "(lv.obj)i" } }, +}; +#endif // BE_LV_WIDGET_STRIPES + extern const bclass be_class_lv_anim; extern const bclass be_class_lv_animimg; extern const bclass be_class_lv_arc; @@ -1688,6 +1707,7 @@ extern const bclass be_class_lv_span; extern const bclass be_class_lv_spangroup; extern const bclass be_class_lv_spinbox; extern const bclass be_class_lv_spinner; +extern const bclass be_class_lv_stripes; extern const bclass be_class_lv_style; extern const bclass be_class_lv_switch; extern const bclass be_class_lv_table; @@ -1812,6 +1832,9 @@ const be_ntv_class_def_t lv_classes[] = { #ifdef BE_LV_WIDGET_SPINNER { "lv_spinner", &be_class_lv_spinner, lv_spinner_func, sizeof(lv_spinner_func) / sizeof(lv_spinner_func[0]) }, #endif // BE_LV_WIDGET_SPINNER +#ifdef BE_LV_WIDGET_STRIPES + { "lv_stripes", &be_class_lv_stripes, lv_stripes_func, sizeof(lv_stripes_func) / sizeof(lv_stripes_func[0]) }, +#endif // BE_LV_WIDGET_STRIPES { "lv_style", &be_class_lv_style, lv_style_func, sizeof(lv_style_func) / sizeof(lv_style_func[0]) }, #ifdef BE_LV_WIDGET_SWITCH { "lv_switch", &be_class_lv_switch, lv_switch_func, sizeof(lv_switch_func) / sizeof(lv_switch_func[0]) }, @@ -2004,6 +2027,10 @@ const size_t lv_classes_size = sizeof(lv_classes) / sizeof(lv_classes[0]); #ifdef BE_LV_WIDGET_COLORWHEEL int be_ntv_lv_colorwheel_init(bvm *vm) { return be_call_c_func(vm, (void*) &lv_colorwheel_create, "+_p", "(lv.obj)b"); } #endif // BE_LV_WIDGET_COLORWHEEL + /* `lv_stripes` methods */ +#ifdef BE_LV_WIDGET_STRIPES + int be_ntv_lv_stripes_init(bvm *vm) { return be_call_c_func(vm, (void*) &lv_stripes_create, "+_p", "(lv.obj)"); } +#endif // BE_LV_WIDGET_STRIPES // create font either empty or from parameter on stack int lvbe_font_create(bvm *vm) { return be_call_c_func(vm, NULL, "+_p", ""); } diff --git a/lib/libesp32_lvgl/lv_binding_berry/generate/be_lvgl_widgets_lib.c b/lib/libesp32_lvgl/lv_binding_berry/generate/be_lvgl_widgets_lib.c index 2244456cc..3761354d3 100644 --- a/lib/libesp32_lvgl/lv_binding_berry/generate/be_lvgl_widgets_lib.c +++ b/lib/libesp32_lvgl/lv_binding_berry/generate/be_lvgl_widgets_lib.c @@ -32,6 +32,9 @@ extern int lvbe_theme_create(bvm *vm); // adding ad-hoc colorwheel from LVGL8 to LVGL9 extern const lv_obj_class_t lv_colorwheel_class; +// custom Tasmota widget: rectangle filled with an angled stripe pattern +extern const lv_obj_class_t lv_stripes_class; + extern int be_ntv_lv_style_init(bvm *vm); extern int be_ntv_lv_font_init(bvm *vm); @@ -83,6 +86,7 @@ extern int be_ntv_lv_tabview_init(bvm *vm); extern int be_ntv_lv_textarea_init(bvm *vm); extern int be_ntv_lv_tileview_init(bvm *vm); extern int be_ntv_lv_colorwheel_init(bvm *vm); +extern int be_ntv_lv_stripes_init(bvm *vm); extern const bclass be_class_lv_anim; extern const bclass be_class_lv_animimg; @@ -126,6 +130,7 @@ extern const bclass be_class_lv_span; extern const bclass be_class_lv_spangroup; extern const bclass be_class_lv_spinbox; extern const bclass be_class_lv_spinner; +extern const bclass be_class_lv_stripes; extern const bclass be_class_lv_style; extern const bclass be_class_lv_switch; extern const bclass be_class_lv_table; @@ -714,3 +719,14 @@ class be_class_lv_colorwheel (scope: global, name: lv_colorwheel, super: be_clas } @const_object_info_end */ +/******************************************************************** +** Solidified class: lv_stripes +********************************************************************/ +#include "be_fixed_be_class_lv_stripes.h" +/* @const_object_info_begin +class be_class_lv_stripes (scope: global, name: lv_stripes, super: be_class_lv_obj, strings: weak) { + init, func(be_ntv_lv_stripes_init) + _class, comptr(&lv_stripes_class) +} +@const_object_info_end */ + diff --git a/lib/libesp32_lvgl/lv_binding_berry/mapping/lv_funcs.h b/lib/libesp32_lvgl/lv_binding_berry/mapping/lv_funcs.h index 99ed35c92..9d1a12964 100644 --- a/lib/libesp32_lvgl/lv_binding_berry/mapping/lv_funcs.h +++ b/lib/libesp32_lvgl/lv_binding_berry/mapping/lv_funcs.h @@ -1744,3 +1744,18 @@ lv_color_t lv_colorwheel_get_rgb(lv_obj_t * obj) lv_colorwheel_mode_t lv_colorwheel_get_color_mode(lv_obj_t * obj) bool lv_colorwheel_get_color_mode_fixed(lv_obj_t * obj) +// ../src/lv_stripes.h +lv_obj_t * lv_stripes_create(lv_obj_t * parent) +void lv_stripes_set_stripe_color(lv_obj_t * obj, lv_color_t color) +void lv_stripes_set_stripe_opa(lv_obj_t * obj, lv_opa_t opa) +void lv_stripes_set_stripe_width(lv_obj_t * obj, int32_t width) +void lv_stripes_set_gap_width(lv_obj_t * obj, int32_t width) +void lv_stripes_set_angle(lv_obj_t * obj, int32_t angle) +void lv_stripes_set_offset(lv_obj_t * obj, int32_t offset) +lv_color_t lv_stripes_get_stripe_color(lv_obj_t * obj) +lv_opa_t lv_stripes_get_stripe_opa(lv_obj_t * obj) +int32_t lv_stripes_get_stripe_width(lv_obj_t * obj) +int32_t lv_stripes_get_gap_width(lv_obj_t * obj) +int32_t lv_stripes_get_angle(lv_obj_t * obj) +int32_t lv_stripes_get_offset(lv_obj_t * obj) + diff --git a/lib/libesp32_lvgl/lv_binding_berry/src/lv_stripes.c b/lib/libesp32_lvgl/lv_binding_berry/src/lv_stripes.c new file mode 100644 index 000000000..c0f4d371f --- /dev/null +++ b/lib/libesp32_lvgl/lv_binding_berry/src/lv_stripes.c @@ -0,0 +1,418 @@ +/** + * @file lv_stripes.c + * + * A rectangle widget decorated with a repeating pattern of diagonal + * (or any angle) stripes, drawn as thick lines on top of the normal + * `lv_obj` background. + * + * Part of the Tasmota project (https://github.com/arendst/Tasmota). + * Author: Stephan Hadinger + */ + +/********************* + * INCLUDES + *********************/ +#include "lv_stripes.h" + +#include "../../lvgl/src/core/lv_obj_class_private.h" +#include "../../lvgl/src/core/lv_obj_private.h" +#include "../../lvgl/src/misc/lv_area_private.h" +#include "../../lvgl/src/misc/lv_math.h" +#include "../../lvgl/src/core/lv_obj_scroll.h" +#include "../../lvgl/src/draw/lv_draw.h" +#include "../../lvgl/src/draw/lv_draw_mask.h" +#include "../../lvgl/src/draw/lv_draw_image.h" +#include "../../lvgl/src/draw/lv_draw_line.h" + +/********************* + * DEFINES + *********************/ +#define MY_CLASS &lv_stripes_class + +/********************** + * TYPEDEFS + **********************/ + +/*Data of the stripes widget*/ +typedef struct { + lv_obj_t obj; + lv_color_t stripe_color; + lv_opa_t stripe_opa; + int32_t stripe_width; + int32_t gap_width; + int32_t angle; /*in degrees, 0..359*/ + int32_t offset; /*pattern shift in pixels along the normal*/ +} lv_stripes_t; + +/********************** + * STATIC PROTOTYPES + **********************/ +static void lv_stripes_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj); +static void lv_stripes_event(const lv_obj_class_t * class_p, lv_event_t * e); +static void draw_stripes(lv_event_t * e); +static void draw_stripes_pattern(lv_stripes_t * stripes, lv_layer_t * target_layer, const lv_area_t * coords); +static void draw_stripes_masked_band(lv_stripes_t * stripes, lv_layer_t * layer, const lv_area_t * coords, + int32_t radius, const lv_area_t * band); + +/********************** + * STATIC VARIABLES + **********************/ +const lv_obj_class_t lv_stripes_class = { + .constructor_cb = lv_stripes_constructor, + .event_cb = lv_stripes_event, + .instance_size = sizeof(lv_stripes_t), + .base_class = &lv_obj_class, + .width_def = LV_DPI_DEF, + .height_def = LV_DPI_DEF, +}; + +/********************** + * MACROS + **********************/ + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +lv_obj_t * lv_stripes_create(lv_obj_t * parent) +{ + LV_LOG_INFO("begin"); + lv_obj_t * obj = lv_obj_class_create_obj(MY_CLASS, parent); + lv_obj_class_init_obj(obj); + return obj; +} + +/*===================== + * Setter functions + *====================*/ + +void lv_stripes_set_stripe_color(lv_obj_t * obj, lv_color_t color) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_stripes_t * stripes = (lv_stripes_t *)obj; + + if(lv_color_eq(stripes->stripe_color, color)) return; + stripes->stripe_color = color; + lv_obj_invalidate(obj); +} + +void lv_stripes_set_stripe_opa(lv_obj_t * obj, lv_opa_t opa) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_stripes_t * stripes = (lv_stripes_t *)obj; + + if(stripes->stripe_opa == opa) return; + stripes->stripe_opa = opa; + lv_obj_invalidate(obj); +} + +void lv_stripes_set_stripe_width(lv_obj_t * obj, int32_t width) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_stripes_t * stripes = (lv_stripes_t *)obj; + + if(width < 1) width = 1; + if(stripes->stripe_width == width) return; + stripes->stripe_width = width; + lv_obj_invalidate(obj); +} + +void lv_stripes_set_gap_width(lv_obj_t * obj, int32_t width) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_stripes_t * stripes = (lv_stripes_t *)obj; + + if(width < 0) width = 0; + if(stripes->gap_width == width) return; + stripes->gap_width = width; + lv_obj_invalidate(obj); +} + +void lv_stripes_set_angle(lv_obj_t * obj, int32_t angle) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_stripes_t * stripes = (lv_stripes_t *)obj; + + angle %= 360; + if(angle < 0) angle += 360; + if(stripes->angle == angle) return; + stripes->angle = angle; + lv_obj_invalidate(obj); +} + +void lv_stripes_set_offset(lv_obj_t * obj, int32_t offset) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_stripes_t * stripes = (lv_stripes_t *)obj; + + if(stripes->offset == offset) return; + stripes->offset = offset; + lv_obj_invalidate(obj); +} + +/*===================== + * Getter functions + *====================*/ + +lv_color_t lv_stripes_get_stripe_color(lv_obj_t * obj) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_stripes_t * stripes = (lv_stripes_t *)obj; + return stripes->stripe_color; +} + +lv_opa_t lv_stripes_get_stripe_opa(lv_obj_t * obj) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_stripes_t * stripes = (lv_stripes_t *)obj; + return stripes->stripe_opa; +} + +int32_t lv_stripes_get_stripe_width(lv_obj_t * obj) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_stripes_t * stripes = (lv_stripes_t *)obj; + return stripes->stripe_width; +} + +int32_t lv_stripes_get_gap_width(lv_obj_t * obj) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_stripes_t * stripes = (lv_stripes_t *)obj; + return stripes->gap_width; +} + +int32_t lv_stripes_get_angle(lv_obj_t * obj) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_stripes_t * stripes = (lv_stripes_t *)obj; + return stripes->angle; +} + +int32_t lv_stripes_get_offset(lv_obj_t * obj) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_stripes_t * stripes = (lv_stripes_t *)obj; + return stripes->offset; +} + +/********************** + * STATIC FUNCTIONS + **********************/ + +static void lv_stripes_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj) +{ + LV_UNUSED(class_p); + lv_stripes_t * stripes = (lv_stripes_t *)obj; + + stripes->stripe_color = lv_color_black(); + stripes->stripe_opa = LV_OPA_COVER; + stripes->stripe_width = 10; + stripes->gap_width = 10; + stripes->angle = 45; + stripes->offset = 0; + + /*Sensible default background so the widget is visible out of the box*/ + lv_obj_set_style_bg_color(obj, lv_color_white(), LV_PART_MAIN); + lv_obj_set_style_bg_opa(obj, LV_OPA_COVER, LV_PART_MAIN); + + /*Draw the border after the main content (our stripes), so it stays on + *top of them. This is LVGL's built-in mechanism for "border above + *content" and avoids drawing the border twice.*/ + lv_obj_set_style_border_post(obj, true, LV_PART_MAIN); + + /*Clip children (e.g. an oversized animated stripe child) to the + *rounded-corner shape, hide scrollbars that would otherwise appear when a + *child is larger than the container.*/ + lv_obj_set_style_clip_corner(obj, true, LV_PART_MAIN); + lv_obj_set_scrollbar_mode(obj, LV_SCROLLBAR_MODE_OFF); +} + +/** + * Draw the stripe pattern onto `target_layer`. + * + * The pattern is a family of parallel stripes, each drawn as a single thick + * line `stripe_width` pixels wide, spaced every `stripe_width + gap_width` + * pixels along the direction perpendicular to the stripe orientation. Each + * line runs the full diagonal of the widget (extended past the edges), so + * rotation never leaves gaps at the corners. Drawing one line per stripe - + * rather than a pair of triangles - avoids any internal anti-aliasing seam + * along the stripe. The renderer clips everything to `target_layer->_clip_area`. + */ +static void draw_stripes_pattern(lv_stripes_t * stripes, lv_layer_t * target_layer, const lv_area_t * coords) +{ + int32_t w = lv_area_get_width(coords); + int32_t h = lv_area_get_height(coords); + int32_t cx = coords->x1 + w / 2; + int32_t cy = coords->y1 + h / 2; + + int32_t period = stripes->stripe_width + stripes->gap_width; + if(period < 1) period = 1; + + /*Half-diagonal: long enough so stripe lines always cover the full widget + *once rotated to any angle, plus a small margin.*/ + int32_t half_diag = lv_sqrt32((uint32_t)((int64_t)w * w + (int64_t)h * h)) / 2 + period; + + /*Direction vector along the stripe length, and its normal (the direction + *along which stripes are spaced).*/ + int32_t dx = lv_trigo_sin(stripes->angle + 90); + int32_t dy = lv_trigo_sin(stripes->angle); + int32_t nx = lv_trigo_sin(stripes->angle); + int32_t ny = lv_trigo_sin(stripes->angle - 90); + + lv_draw_line_dsc_t line_dsc; + lv_draw_line_dsc_init(&line_dsc); + line_dsc.color = stripes->stripe_color; + line_dsc.opa = stripes->stripe_opa; + line_dsc.width = stripes->stripe_width; + + /*Offset from a stripe center to its two ends (half diagonal along the + *stripe direction). Same for every stripe.*/ + int32_t lx = ((int64_t)half_diag * dx) / LV_TRIGO_SIN_MAX; + int32_t ly = ((int64_t)half_diag * dy) / LV_TRIGO_SIN_MAX; + + /*Pattern shift along the normal, wrapped to one period so any offset + *value (e.g. an ever-increasing animation counter) stays in range.*/ + int32_t phase = stripes->offset % period; + + int32_t n_stripes = half_diag / period + 1; + + for(int32_t i = -n_stripes; i <= n_stripes; i++) { + /*Center of this stripe, offset from widget center along the normal, + *plus the animation phase.*/ + int32_t d = i * period + phase; + int32_t bcx = cx + ((int64_t)d * nx) / LV_TRIGO_SIN_MAX; + int32_t bcy = cy + ((int64_t)d * ny) / LV_TRIGO_SIN_MAX; + + line_dsc.p1.x = bcx - lx; + line_dsc.p1.y = bcy - ly; + line_dsc.p2.x = bcx + lx; + line_dsc.p2.y = bcy + ly; + lv_draw_line(target_layer, &line_dsc); + } +} + +/** + * Draw the stripe pattern into an offscreen ARGB layer covering only + * `band`, mask it to the object's rounded rectangle, then composite it + * back. Used only for the two thin corner strips (top and bottom) where + * rounded corners actually exist. + */ +static void draw_stripes_masked_band(lv_stripes_t * stripes, lv_layer_t * layer, const lv_area_t * coords, + int32_t radius, const lv_area_t * band) +{ + lv_layer_t * layer_band = lv_draw_layer_create(layer, LV_COLOR_FORMAT_ARGB8888, band); + if(layer_band == NULL) { + /*Out of memory: draw directly, corners of this strip may overflow*/ + const lv_area_t clip_ori = layer->_clip_area; + layer->_clip_area = *band; + draw_stripes_pattern(stripes, layer, coords); + layer->_clip_area = clip_ori; + return; + } + + draw_stripes_pattern(stripes, layer_band, coords); + + lv_draw_mask_rect_dsc_t mask_dsc; + lv_draw_mask_rect_dsc_init(&mask_dsc); + mask_dsc.area = *coords; + mask_dsc.radius = radius; + lv_draw_mask_rect(layer_band, &mask_dsc); + + lv_draw_image_dsc_t img_dsc; + lv_draw_image_dsc_init(&img_dsc); + img_dsc.src = layer_band; + lv_draw_layer(layer, &img_dsc, band); +} + +/** + * Draw the stripes for LV_EVENT_DRAW_MAIN. + * + * Fast path (no rounded corners): the object is a plain rectangle, so the + * stripes are drawn directly onto the main layer. LVGL already restricted + * `layer->_clip_area` to the object's rectangular bounds, which is all the + * clipping we need, so this costs nothing more than the line draws. + * + * Rounded path (radius > 0): rounded corners only exist in the top and + * bottom `radius`-high strips of the widget. So, following LVGL's own + * `clip_corner` implementation in `lv_refr.c`, the widget is split into + * three horizontal bands: + * - the middle band (the bulk of the widget) is drawn directly with a + * plain rectangular clip - no offscreen buffer, full speed; + * - the top and bottom strips (each only `radius` pixels high) are each + * rendered into a small ARGB layer, masked to the rounded rectangle, + * and composited back. + * This keeps the expensive offscreen path limited to the small corner + * regions instead of the whole widget. + */ +static void draw_stripes(lv_event_t * e) +{ + lv_obj_t * obj = lv_event_get_current_target(e); + lv_layer_t * layer = lv_event_get_layer(e); + lv_stripes_t * stripes = (lv_stripes_t *)obj; + + lv_area_t coords; + lv_area_copy(&coords, &obj->coords); + + int32_t w = lv_area_get_width(&coords); + int32_t h = lv_area_get_height(&coords); + + int32_t radius = lv_obj_get_style_radius(obj, LV_PART_MAIN); + int32_t short_side = LV_MIN(w, h); + int32_t rout = radius; + if(rout > short_side >> 1) rout = short_side >> 1; + + /*Fast path: plain rectangle, draw straight onto the main layer.*/ + if(rout <= 0) { + draw_stripes_pattern(stripes, layer, &coords); + return; + } + + const lv_area_t clip_area_ori = layer->_clip_area; + + /*Middle band: no rounded corners here, plain rectangular clip, drawn + *directly onto the main layer (this is the bulk of the widget).*/ + lv_area_t mid = coords; + mid.y1 += rout; + mid.y2 -= rout; + lv_area_t mid_clip; + if(lv_area_intersect(&mid_clip, &mid, &clip_area_ori)) { + layer->_clip_area = mid_clip; + draw_stripes_pattern(stripes, layer, &coords); + layer->_clip_area = clip_area_ori; + } + + /*Top strip: contains the two top rounded corners.*/ + lv_area_t top = coords; + top.y2 = top.y1 + rout - 1; + lv_area_t top_clip; + if(lv_area_intersect(&top_clip, &top, &clip_area_ori)) { + draw_stripes_masked_band(stripes, layer, &coords, radius, &top_clip); + } + + /*Bottom strip: contains the two bottom rounded corners.*/ + lv_area_t bottom = coords; + bottom.y1 = bottom.y2 - rout + 1; + lv_area_t bottom_clip; + if(lv_area_intersect(&bottom_clip, &bottom, &clip_area_ori)) { + draw_stripes_masked_band(stripes, layer, &coords, radius, &bottom_clip); + } + + layer->_clip_area = clip_area_ori; +} + +static void lv_stripes_event(const lv_obj_class_t * class_p, lv_event_t * e) +{ + LV_UNUSED(class_p); + + /*Call the ancestor's event handler. For LV_EVENT_DRAW_MAIN it draws the + *standard lv_obj background and shadow (the border is deferred to + *LV_EVENT_DRAW_POST because we set `border_post`), and it handles + *LV_EVENT_COVER_CHECK, so the stripes' coverage matches the background.*/ + lv_result_t res = lv_obj_event_base(MY_CLASS, e); + if(res != LV_RESULT_OK) return; + + if(lv_event_get_code(e) == LV_EVENT_DRAW_MAIN) { + draw_stripes(e); + } +} diff --git a/lib/libesp32_lvgl/lv_binding_berry/src/lv_stripes.h b/lib/libesp32_lvgl/lv_binding_berry/src/lv_stripes.h new file mode 100644 index 000000000..61b06f256 --- /dev/null +++ b/lib/libesp32_lvgl/lv_binding_berry/src/lv_stripes.h @@ -0,0 +1,156 @@ +/** + * @file lv_stripes.h + * + * A rectangle widget filled with a repeating pattern of straight + * stripes drawn at an arbitrary angle, on top of the normal `lv_obj` + * background. Stripe color, opacity, width and inter-stripe gap width + * are all configurable, as is the pattern angle. + * + * The background (color/opacity/radius/border/...) is the regular + * `LV_PART_MAIN` style of `lv_obj`, so it composes with the normal + * LVGL style system. Stripes are drawn as a set of thick lines, + * clipped to the object's bounds. + * + * Part of the Tasmota project (https://github.com/arendst/Tasmota). + * Author: Stephan Hadinger + */ + +#ifndef LV_STRIPES_H +#define LV_STRIPES_H + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * INCLUDES + *********************/ +#include "lvgl.h" + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +extern const lv_obj_class_t lv_stripes_class; + +/********************** + * GLOBAL PROTOTYPES + **********************/ + +/** + * Create a stripes object. + * @param parent pointer to an object, it will be the parent of the new stripes object + * @return pointer to the created stripes object + */ +lv_obj_t * lv_stripes_create(lv_obj_t * parent); + +/*===================== + * Setter functions + *====================*/ + +/** + * Set the color of the stripes. + * @param obj pointer to a stripes object + * @param color color of the stripes + */ +void lv_stripes_set_stripe_color(lv_obj_t * obj, lv_color_t color); + +/** + * Set the opacity of the stripes. + * @param obj pointer to a stripes object + * @param opa opacity 0 (transparent) .. 255 (opaque). `LV_OPA_*` constants can be used. + */ +void lv_stripes_set_stripe_opa(lv_obj_t * obj, lv_opa_t opa); + +/** + * Set the width (thickness) of each stripe, in pixels. + * @param obj pointer to a stripes object + * @param width width of a stripe in pixels, minimum 1 + */ +void lv_stripes_set_stripe_width(lv_obj_t * obj, int32_t width); + +/** + * Set the width of the gap between two consecutive stripes, in pixels. + * @param obj pointer to a stripes object + * @param width width of the gap in pixels, 0 means stripes are contiguous + */ +void lv_stripes_set_gap_width(lv_obj_t * obj, int32_t width); + +/** + * Set the angle of the stripes. + * @param obj pointer to a stripes object + * @param angle angle in degrees, 0 is horizontal stripes, 90 is vertical + * stripes. Any value is accepted and normalized to 0..359. + */ +void lv_stripes_set_angle(lv_obj_t * obj, int32_t angle); + +/** + * Set the offset of the pattern. + * The pattern is shifted by `offset` pixels along the direction perpendicular + * to the stripes (the direction along which stripes are spaced). Because the + * pattern repeats every `stripe_width + gap_width` pixels, the offset wraps + * seamlessly, so it can be freely incremented to animate a scrolling motion. + * @param obj pointer to a stripes object + * @param offset offset in pixels, any value is accepted (wrapped internally) + */ +void lv_stripes_set_offset(lv_obj_t * obj, int32_t offset); + +/*===================== + * Getter functions + *====================*/ + +/** + * Get the color of the stripes. + * @param obj pointer to a stripes object + * @return color of the stripes + */ +lv_color_t lv_stripes_get_stripe_color(lv_obj_t * obj); + +/** + * Get the opacity of the stripes. + * @param obj pointer to a stripes object + * @return opacity 0..255 + */ +lv_opa_t lv_stripes_get_stripe_opa(lv_obj_t * obj); + +/** + * Get the width of each stripe. + * @param obj pointer to a stripes object + * @return width of a stripe in pixels + */ +int32_t lv_stripes_get_stripe_width(lv_obj_t * obj); + +/** + * Get the width of the gap between two consecutive stripes. + * @param obj pointer to a stripes object + * @return width of the gap in pixels + */ +int32_t lv_stripes_get_gap_width(lv_obj_t * obj); + +/** + * Get the angle of the stripes. + * @param obj pointer to a stripes object + * @return angle in degrees (0..359) + */ +int32_t lv_stripes_get_angle(lv_obj_t * obj); + +/** + * Get the offset of the pattern. + * @param obj pointer to a stripes object + * @return offset in pixels (as last set, not wrapped) + */ +int32_t lv_stripes_get_offset(lv_obj_t * obj); + +/********************** + * MACROS + **********************/ + +#ifdef __cplusplus +} /*extern "C"*/ +#endif + +#endif /*LV_STRIPES_H*/ diff --git a/lib/libesp32_lvgl/lv_binding_berry/tools/convert.py b/lib/libesp32_lvgl/lv_binding_berry/tools/convert.py index c41fbf5e9..7d54ee4a0 100644 --- a/lib/libesp32_lvgl/lv_binding_berry/tools/convert.py +++ b/lib/libesp32_lvgl/lv_binding_berry/tools/convert.py @@ -588,6 +588,7 @@ extern "C" { #include "be_ctypes.h" #include "be_mapping.h" #include "../src/lv_colorwheel.h" +#include "../src/lv_stripes.h" """ C_CLASSES_MAP_OPEN = """ @@ -712,6 +713,9 @@ extern int lvbe_theme_create(bvm *vm); // adding ad-hoc colorwheel from LVGL8 to LVGL9 extern const lv_obj_class_t lv_colorwheel_class; +// custom Tasmota widget: rectangle filled with an angled stripe pattern +extern const lv_obj_class_t lv_stripes_class; + """ WIDGETS_LIB_CORE_CLASSES = """ diff --git a/lib/libesp32_lvgl/lv_binding_berry/tools/lvgl_config.py b/lib/libesp32_lvgl/lv_binding_berry/tools/lvgl_config.py index 6f7cd75ee..aa6a4e65d 100644 --- a/lib/libesp32_lvgl/lv_binding_berry/tools/lvgl_config.py +++ b/lib/libesp32_lvgl/lv_binding_berry/tools/lvgl_config.py @@ -49,6 +49,8 @@ LV_WIDGETS = [ 'qrcode', # ad-hoc colorwheel back-ported from LVGL8 to LVGL9 'colorwheel', + # custom Tasmota widget: rectangle filled with an angled stripe pattern + 'stripes', ] # Widgets that do not have a backing ``lv__class`` C object. @@ -578,6 +580,7 @@ FUNCTION_HEADER_EXTRA_PATHS = [ "../../LVGL_assets/src/lv_theme_haspmota.h", "../src/lv_berry.h", "../src/lv_colorwheel.h", + "../src/lv_stripes.h", ] # Glob patterns (relative to LV_SRC_PREFIX) for headers scanned for enums. diff --git a/lib/libesp32_lvgl/lv_haspmota/src/embedded/lv_haspmota.be b/lib/libesp32_lvgl/lv_haspmota/src/embedded/lv_haspmota.be index 624f69b3c..1862512f9 100644 --- a/lib/libesp32_lvgl/lv_haspmota/src/embedded/lv_haspmota.be +++ b/lib/libesp32_lvgl/lv_haspmota/src/embedded/lv_haspmota.be @@ -3648,7 +3648,17 @@ class HASPmota end end - # Step 3.d. if not found, try to load a module with the name of the class + # Step 3.d. if not found, try `lv.` as direct mapping of LVGL class + if obj_class == nil + # if not found, check if a LVGL class with name `lv.` exists + var lv_cl = introspect.get(lv, obj_type) + if (lv_cl != nil) && (type(lv_cl) == 'class') + lv_instance = lv_cl(parent_lvgl) + obj_class = self.lvh_obj # use the basic lvh_obj component to encapsulate + end + end + + # Step 3.e. if not found, try to load a module with the name of the class if obj_class == nil var lv_cl = introspect.module(obj_type) if lv_cl != nil && type(lv_cl) == 'class' diff --git a/tasmota/berry/haspmota_src/pages.jsonl b/tasmota/berry/haspmota_src/pages.jsonl index 3d50c35a1..d8f7071bd 100644 --- a/tasmota/berry/haspmota_src/pages.jsonl +++ b/tasmota/berry/haspmota_src/pages.jsonl @@ -273,3 +273,43 @@ {"id":71,"obj":"dropdown","x":15,"y":10,"w":110,"h":30,"parentid":52,"options":"Apple\nBanana\nOrange\nMelon"} {"id":72,"obj":"dropdown_list","parentid":71} {"id":81,"obj":"checkbox","x":15,"y":10,"w":150,"h":30,"parentid":53,"text":"Nice tabview"} + +{"page":26,"comment":"---------- Page 26 Stripes angles ----------"} + +{"id":1,"obj":"label","x":0,"y":30,"w%":100,"h":24,"text":"[26. Stripes angles]","text_font":"montserrat-20"} + +{"id":21,"obj":"label","x":10,"y":56,"w":145,"h":14,"text":"0deg 10/10","align":1,"text_font":"montserrat-14"} +{"id":11,"obj":"stripes","x":10,"y":71,"w":145,"h":50,"radius":6,"border_side":0,"bg_color":"#FFFFFF","stripe_color":"#1FA3EC","stripe_opa":255,"stripe_width":10,"gap_width":10,"angle":0} + +{"id":22,"obj":"label","x":165,"y":56,"w":145,"h":14,"text":"45deg 10/10","align":1,"text_font":"montserrat-14"} +{"id":12,"obj":"stripes","x":165,"y":71,"w":145,"h":50,"radius":6,"border_side":0,"bg_color":"#FFFFFF","stripe_color":"#1FA3EC","stripe_opa":255,"stripe_width":10,"gap_width":10,"angle":45} + +{"id":23,"obj":"label","x":10,"y":126,"w":145,"h":14,"text":"45deg wide 20/6","align":1,"text_font":"montserrat-14"} +{"id":13,"obj":"stripes","x":10,"y":141,"w":145,"h":50,"radius":6,"border_side":0,"bg_color":"#FFFFFF","stripe_color":"#1FA3EC","stripe_opa":255,"stripe_width":20,"gap_width":6,"angle":45} + +{"id":24,"obj":"label","x":165,"y":126,"w":145,"h":14,"text":"45deg thin 4/16","align":1,"text_font":"montserrat-14"} +{"id":14,"obj":"stripes","x":165,"y":141,"w":145,"h":50,"radius":6,"border_side":0,"bg_color":"#FFFFFF","stripe_color":"#1FA3EC","stripe_opa":255,"stripe_width":4,"gap_width":16,"angle":45} + +{"page":27,"comment":"---------- Page 27 Stripes styles ----------"} + +{"id":1,"obj":"label","x":0,"y":30,"w%":100,"h":24,"text":"[27. Stripes styles]","text_font":"montserrat-20"} + +{"id":21,"obj":"label","x":10,"y":56,"w":145,"h":14,"text":"Colored","align":1,"text_font":"montserrat-14"} +{"id":11,"obj":"stripes","x":10,"y":71,"w":145,"h":50,"radius":6,"border_side":0,"bg_color":"#2C3E50","stripe_color":"#F1C40F","stripe_opa":255,"stripe_width":12,"gap_width":10,"angle":45} + +{"id":22,"obj":"label","x":165,"y":56,"w":145,"h":14,"text":"Opacity 128","align":1,"text_font":"montserrat-14"} +{"id":12,"obj":"stripes","x":165,"y":71,"w":145,"h":50,"radius":6,"border_side":0,"bg_color":"#F1C40F","stripe_color":"#2C3E50","stripe_opa":128,"stripe_width":14,"gap_width":8,"angle":20} + +{"id":23,"obj":"label","x":10,"y":126,"w":145,"h":14,"text":"Radius 22","align":1,"text_font":"montserrat-14"} +{"id":13,"obj":"stripes","x":10,"y":141,"w":145,"h":50,"radius":22,"border_side":0,"bg_color":"#000044","stripe_color":"#00C853","stripe_opa":255,"stripe_width":12,"gap_width":10,"angle":135} + +{"id":24,"obj":"label","x":165,"y":126,"w":145,"h":14,"text":"Radius + border","align":1,"text_font":"montserrat-14"} +{"id":14,"obj":"stripes","x":165,"y":141,"w":145,"h":50,"radius":22,"border_side":15,"border_width":4,"border_color":"#FFFFFF","bg_color":"#000044","stripe_color":"#00C853","stripe_opa":230,"stripe_width":12,"gap_width":10,"angle":45} + +{"page":28,"comment":"---------- Page 28 Stripes warning ----------"} + +{"id":1,"obj":"label","x":0,"y":30,"w%":100,"h":24,"text":"[28. Stripes warning]","text_font":"montserrat-20"} + +{"id":11,"obj":"stripes","x":20,"y":80,"w":280,"h":80,"radius":10,"bg_color":"#FFD500","stripe_color":"#000000","stripe_opa":255,"stripe_width":12,"gap_width":12,"angle":45,"border_color":"#000000","border_width":6} + +{"comment":"--- Scroll the pattern by animating the offset; wraps seamlessly every period ---","berry_run":"def warn_scroll() if global.p28b11 global.p28b11.offset += 2 end tasmota.set_timer(60, warn_scroll) end warn_scroll()"} diff --git a/tasmota/berry/haspmota_src/pages_screenshot.be b/tasmota/berry/haspmota_src/pages_screenshot.be index 1797b234d..6716c1af6 100644 --- a/tasmota/berry/haspmota_src/pages_screenshot.be +++ b/tasmota/berry/haspmota_src/pages_screenshot.be @@ -1,4 +1,5 @@ # simple script to make screenshots of HASPmota demo +# backlog time 1704112620; sleep 3; br lv.screenshot() def pageshot(p, cb) global.(f"p{p}").show() diff --git a/tasmota/my_user_config.h b/tasmota/my_user_config.h index c8bd340ee..aad1e769a 100644 --- a/tasmota/my_user_config.h +++ b/tasmota/my_user_config.h @@ -1324,6 +1324,9 @@ // adding ad-hoc colorwheel from LVGL8 to LVGL9 #define BE_LV_WIDGET_COLORWHEEL + // custom Tasmota widget: rectangle filled with an angled stripe pattern + #define BE_LV_WIDGET_STRIPES + #define BE_LV_WIDGET_ANIMIMG #define BE_LV_WIDGET_CHART #define BE_LV_WIDGET_IMGBTN // LVGL 8