Files
lvgl/src/widgets/scale/lv_scale.c

1656 lines
62 KiB
C

/**
* @file lv_scale.c
*
*/
/*********************
* INCLUDES
*********************/
#include "lv_scale_private.h"
#include "../../core/lv_obj_private.h"
#include "../../core/lv_obj_class_private.h"
#if LV_USE_SCALE != 0
#include "../../core/lv_group.h"
#include "../../misc/lv_assert.h"
#include "../../misc/lv_math.h"
#include "../../draw/lv_draw_arc.h"
/*********************
* DEFINES
*********************/
#define MY_CLASS (&lv_scale_class)
#define LV_SCALE_LABEL_TXT_LEN (20U)
#define LV_SCALE_DEFAULT_ANGLE_RANGE ((uint32_t) 270U)
#define LV_SCALE_DEFAULT_ROTATION ((int32_t) 135U)
#define LV_SCALE_TICK_IDX_DEFAULT_ID ((uint32_t) 255U)
#define LV_SCALE_DEFAULT_LABEL_GAP ((uint32_t) 15U)
/**********************
* TYPEDEFS
**********************/
/**********************
* STATIC PROTOTYPES
**********************/
static void lv_scale_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj);
static void lv_scale_destructor(const lv_obj_class_t * class_p, lv_obj_t * obj);
static void lv_scale_event(const lv_obj_class_t * class_p, lv_event_t * event);
static void scale_draw_main(lv_obj_t * obj, lv_event_t * event);
static void scale_draw_indicator(lv_obj_t * obj, lv_event_t * event);
static void scale_draw_label(lv_obj_t * obj, lv_event_t * event, lv_draw_label_dsc_t * label_dsc,
const uint32_t major_tick_idx, const int32_t tick_value, lv_point_t * tick_point_b, const uint32_t tick_idx);
static void scale_calculate_main_compensation(lv_obj_t * obj);
static void scale_get_center(const lv_obj_t * obj, lv_point_t * center, int32_t * arc_r);
static void scale_get_tick_points(lv_obj_t * obj, const uint32_t tick_idx, bool is_major_tick,
lv_point_t * tick_point_a, lv_point_t * tick_point_b);
static void scale_get_label_coords(lv_obj_t * obj, lv_draw_label_dsc_t * label_dsc, lv_point_t * tick_point,
lv_area_t * label_coords);
static void scale_set_indicator_label_properties(lv_obj_t * obj, lv_draw_label_dsc_t * label_dsc,
const lv_style_t * indicator_section_style);
static void scale_set_line_properties(lv_obj_t * obj, lv_draw_line_dsc_t * line_dsc, const lv_style_t * section_style,
lv_part_t part);
static void scale_set_arc_properties(lv_obj_t * obj, lv_draw_arc_dsc_t * arc_dsc, const lv_style_t * section_style);
/* Helpers */
static void scale_find_section_tick_idx(lv_obj_t * obj);
static void scale_store_main_line_tick_width_compensation(lv_obj_t * obj, const uint32_t tick_idx,
const bool is_major_tick, const int32_t major_tick_width, const int32_t minor_tick_width);
static void scale_store_section_line_tick_width_compensation(lv_obj_t * obj, const bool is_major_tick,
lv_draw_line_dsc_t * major_tick_dsc, lv_draw_line_dsc_t * minor_tick_dsc,
const int32_t tick_value, const uint8_t tick_idx, lv_point_t * tick_point_a);
static void scale_build_custom_label_text(lv_obj_t * obj, lv_draw_label_dsc_t * label_dsc,
const uint16_t major_tick_idx);
static void scale_free_line_needle_points_cb(lv_event_t * e);
/**********************
* STATIC VARIABLES
**********************/
const lv_obj_class_t lv_scale_class = {
.constructor_cb = lv_scale_constructor,
.destructor_cb = lv_scale_destructor,
.event_cb = lv_scale_event,
.instance_size = sizeof(lv_scale_t),
.editable = LV_OBJ_CLASS_EDITABLE_TRUE,
.base_class = &lv_obj_class,
.name = "lv_scale",
};
/**********************
* MACROS
**********************/
/**********************
* GLOBAL FUNCTIONS
**********************/
lv_obj_t * lv_scale_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;
}
/*======================
* Add/remove functions
*=====================*/
/*
* New object specific "add" or "remove" functions come here
*/
/*=====================
* Setter functions
*====================*/
void lv_scale_set_mode(lv_obj_t * obj, lv_scale_mode_t mode)
{
LV_ASSERT_OBJ(obj, MY_CLASS);
lv_scale_t * scale = (lv_scale_t *)obj;
scale->mode = mode;
lv_obj_invalidate(obj);
}
void lv_scale_set_total_tick_count(lv_obj_t * obj, uint32_t total_tick_count)
{
LV_ASSERT_OBJ(obj, MY_CLASS);
lv_scale_t * scale = (lv_scale_t *)obj;
scale->total_tick_count = total_tick_count;
lv_obj_invalidate(obj);
}
void lv_scale_set_major_tick_every(lv_obj_t * obj, uint32_t major_tick_every)
{
LV_ASSERT_OBJ(obj, MY_CLASS);
lv_scale_t * scale = (lv_scale_t *)obj;
scale->major_tick_every = major_tick_every;
lv_obj_invalidate(obj);
}
void lv_scale_set_label_show(lv_obj_t * obj, bool show_label)
{
LV_ASSERT_OBJ(obj, MY_CLASS);
lv_scale_t * scale = (lv_scale_t *)obj;
scale->label_enabled = show_label;
lv_obj_invalidate(obj);
}
void lv_scale_set_range(lv_obj_t * obj, int32_t min, int32_t max)
{
LV_ASSERT_OBJ(obj, MY_CLASS);
lv_scale_t * scale = (lv_scale_t *)obj;
scale->range_min = min;
scale->range_max = max;
lv_obj_invalidate(obj);
}
void lv_scale_set_angle_range(lv_obj_t * obj, uint32_t angle_range)
{
LV_ASSERT_OBJ(obj, MY_CLASS);
lv_scale_t * scale = (lv_scale_t *)obj;
scale->angle_range = angle_range;
lv_obj_invalidate(obj);
}
void lv_scale_set_rotation(lv_obj_t * obj, int32_t rotation)
{
LV_ASSERT_OBJ(obj, MY_CLASS);
lv_scale_t * scale = (lv_scale_t *)obj;
int32_t normalized_angle = rotation;
if(normalized_angle < 0 || normalized_angle > 360) {
normalized_angle = rotation % 360;
if(normalized_angle < 0) {
normalized_angle += 360;
}
}
scale->rotation = normalized_angle;
lv_obj_invalidate(obj);
}
void lv_scale_set_line_needle_value(lv_obj_t * obj, lv_obj_t * needle_line, int32_t needle_length,
int32_t value)
{
int32_t angle;
int32_t scale_width, scale_height;
int32_t actual_needle_length;
int32_t needle_length_x, needle_length_y;
lv_point_precise_t * needle_line_points = NULL;
LV_ASSERT_OBJ(obj, MY_CLASS);
lv_scale_t * scale = (lv_scale_t *)obj;
if((scale->mode != LV_SCALE_MODE_ROUND_INNER) &&
(scale->mode != LV_SCALE_MODE_ROUND_OUTER)) {
return;
}
lv_obj_align(needle_line, LV_ALIGN_TOP_LEFT, 0, 0);
scale_width = lv_obj_get_style_width(obj, LV_PART_MAIN);
scale_height = lv_obj_get_style_height(obj, LV_PART_MAIN);
if(scale_width != scale_height) {
return;
}
if(needle_length >= scale_width / 2) {
actual_needle_length = scale_width / 2;
}
else if(needle_length >= 0) {
actual_needle_length = needle_length;
}
else if(needle_length + scale_width / 2 < 0) {
actual_needle_length = 0;
}
else {
actual_needle_length = scale_width / 2 + needle_length;
}
if(value < scale->range_min) {
angle = 0;
}
else if(value > scale->range_max) {
angle = scale->angle_range;
}
else {
angle = scale->angle_range * (value - scale->range_min) / (scale->range_max - scale->range_min);
}
needle_length_x = (actual_needle_length * lv_trigo_cos(scale->rotation + angle)) >> LV_TRIGO_SHIFT;
needle_length_y = (actual_needle_length * lv_trigo_sin(scale->rotation + angle)) >> LV_TRIGO_SHIFT;
if(lv_line_is_point_array_mutable(needle_line) && lv_line_get_point_count(needle_line) >= 2) {
needle_line_points = lv_line_get_points_mutable(needle_line);
}
if(needle_line_points == NULL) {
uint32_t i;
uint32_t line_event_cnt = lv_obj_get_event_count(needle_line);
for(i = 0; i < line_event_cnt; i--) {
lv_event_dsc_t * dsc = lv_obj_get_event_dsc(needle_line, i);
if(lv_event_dsc_get_cb(dsc) == scale_free_line_needle_points_cb) {
needle_line_points = lv_event_dsc_get_user_data(dsc);
break;
}
}
}
if(needle_line_points == NULL) {
needle_line_points = lv_malloc(sizeof(lv_point_precise_t) * 2);
LV_ASSERT_MALLOC(needle_line_points);
if(needle_line_points == NULL) return;
lv_obj_add_event_cb(needle_line, scale_free_line_needle_points_cb, LV_EVENT_DELETE, needle_line_points);
}
needle_line_points[0].x = scale_width / 2;
needle_line_points[0].y = scale_height / 2;
needle_line_points[1].x = scale_width / 2 + needle_length_x;
needle_line_points[1].y = scale_height / 2 + needle_length_y;
lv_line_set_points_mutable(needle_line, needle_line_points, 2);
}
void lv_scale_set_image_needle_value(lv_obj_t * obj, lv_obj_t * needle_img, int32_t value)
{
int32_t angle;
LV_ASSERT_OBJ(obj, MY_CLASS);
lv_scale_t * scale = (lv_scale_t *)obj;
if((scale->mode != LV_SCALE_MODE_ROUND_INNER) &&
(scale->mode != LV_SCALE_MODE_ROUND_OUTER)) {
return;
}
if(value < scale->range_min) {
angle = 0;
}
else if(value > scale->range_max) {
angle = scale->angle_range;
}
else {
angle = scale->angle_range * (value - scale->range_min) / (scale->range_max - scale->range_min);
}
lv_image_set_rotation(needle_img, (scale->rotation + angle) * 10);
}
void lv_scale_set_text_src(lv_obj_t * obj, const char * txt_src[])
{
LV_ASSERT_OBJ(obj, MY_CLASS);
lv_scale_t * scale = (lv_scale_t *)obj;
scale->txt_src = txt_src;
scale->custom_label_cnt = 0;
if(scale->txt_src) {
int32_t idx;
for(idx = 0; txt_src[idx]; ++idx) {
scale->custom_label_cnt++;
}
}
lv_obj_invalidate(obj);
}
void lv_scale_set_post_draw(lv_obj_t * obj, bool en)
{
LV_ASSERT_OBJ(obj, MY_CLASS);
lv_scale_t * scale = (lv_scale_t *)obj;
scale->post_draw = en;
lv_obj_invalidate(obj);
}
void lv_scale_set_draw_ticks_on_top(lv_obj_t * obj, bool en)
{
LV_ASSERT_OBJ(obj, MY_CLASS);
lv_scale_t * scale = (lv_scale_t *)obj;
scale->draw_ticks_on_top = en;
lv_obj_invalidate(obj);
}
lv_scale_section_t * lv_scale_add_section(lv_obj_t * obj)
{
LV_ASSERT_OBJ(obj, MY_CLASS);
lv_scale_t * scale = (lv_scale_t *)obj;
lv_scale_section_t * section = lv_ll_ins_head(&scale->section_ll);
LV_ASSERT_MALLOC(section);
if(section == NULL) return NULL;
/* Section default values */
lv_memzero(section, sizeof(lv_scale_section_t));
section->first_tick_idx_in_section = LV_SCALE_TICK_IDX_DEFAULT_ID;
section->last_tick_idx_in_section = LV_SCALE_TICK_IDX_DEFAULT_ID;
/* Initial range is [0..-1] to make it "neutral" (i.e. will not be drawn until user
* sets a different range). `range_min` is already 0 from `lv_memzero()` above. */
section->range_max = -1;
return section;
}
void lv_scale_set_section_range(lv_obj_t * scale, lv_scale_section_t * section, int32_t min, int32_t max)
{
LV_ASSERT_OBJ(scale, MY_CLASS);
LV_ASSERT_NULL(section);
section->range_min = min;
section->range_max = max;
lv_obj_invalidate(scale);
}
void lv_scale_section_set_range(lv_scale_section_t * section, int32_t min, int32_t max)
{
if(NULL == section) return;
section->range_min = min;
section->range_max = max;
}
void lv_scale_set_section_style_main(lv_obj_t * scale, lv_scale_section_t * section, const lv_style_t * style)
{
LV_ASSERT_OBJ(scale, MY_CLASS);
LV_ASSERT_NULL(section);
section->main_style = style;
lv_obj_invalidate(scale);
}
void lv_scale_set_section_style_indicator(lv_obj_t * scale, lv_scale_section_t * section, const lv_style_t * style)
{
LV_ASSERT_OBJ(scale, MY_CLASS);
LV_ASSERT_NULL(section);
section->indicator_style = style;
lv_obj_invalidate(scale);
}
void lv_scale_set_section_style_items(lv_obj_t * scale, lv_scale_section_t * section, const lv_style_t * style)
{
LV_ASSERT_OBJ(scale, MY_CLASS);
LV_ASSERT_NULL(section);
section->items_style = style;
lv_obj_invalidate(scale);
}
void lv_scale_section_set_style(lv_scale_section_t * section, lv_part_t part, lv_style_t * section_part_style)
{
LV_LOG_WARN("Deprecated, use lv_scale_set_section_style_main/indicator/items instead");
if(NULL == section) return;
switch(part) {
case LV_PART_MAIN:
section->main_style = section_part_style;
break;
case LV_PART_INDICATOR:
section->indicator_style = section_part_style;
break;
case LV_PART_ITEMS:
section->items_style = section_part_style;
break;
default:
/* Invalid part */
break;
}
}
/*=====================
* Getter functions
*====================*/
lv_scale_mode_t lv_scale_get_mode(lv_obj_t * obj)
{
lv_scale_t * scale = (lv_scale_t *)obj;
return scale->mode;
}
int32_t lv_scale_get_total_tick_count(lv_obj_t * obj)
{
lv_scale_t * scale = (lv_scale_t *)obj;
return scale->total_tick_count;
}
int32_t lv_scale_get_major_tick_every(lv_obj_t * obj)
{
lv_scale_t * scale = (lv_scale_t *)obj;
return scale->major_tick_every;
}
int32_t lv_scale_get_rotation(lv_obj_t * obj)
{
lv_scale_t * scale = (lv_scale_t *)obj;
return scale->rotation;
}
bool lv_scale_get_label_show(lv_obj_t * obj)
{
lv_scale_t * scale = (lv_scale_t *)obj;
return scale->label_enabled;
}
uint32_t lv_scale_get_angle_range(lv_obj_t * obj)
{
lv_scale_t * scale = (lv_scale_t *)obj;
return scale->angle_range;
}
int32_t lv_scale_get_range_min_value(lv_obj_t * obj)
{
lv_scale_t * scale = (lv_scale_t *)obj;
return scale->range_min;
}
int32_t lv_scale_get_range_max_value(lv_obj_t * obj)
{
lv_scale_t * scale = (lv_scale_t *)obj;
return scale->range_max;
}
/*=====================
* Other functions
*====================*/
/**********************
* STATIC FUNCTIONS
**********************/
static void lv_scale_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj)
{
LV_UNUSED(class_p);
LV_TRACE_OBJ_CREATE("begin");
lv_scale_t * scale = (lv_scale_t *)obj;
lv_ll_init(&scale->section_ll, sizeof(lv_scale_section_t));
scale->total_tick_count = LV_SCALE_TOTAL_TICK_COUNT_DEFAULT;
scale->major_tick_every = LV_SCALE_MAJOR_TICK_EVERY_DEFAULT;
scale->mode = LV_SCALE_MODE_HORIZONTAL_BOTTOM;
scale->label_enabled = LV_SCALE_LABEL_ENABLED_DEFAULT;
scale->angle_range = LV_SCALE_DEFAULT_ANGLE_RANGE;
scale->rotation = LV_SCALE_DEFAULT_ROTATION;
scale->range_min = 0;
scale->range_max = 100;
scale->last_tick_width = 0;
scale->first_tick_width = 0;
scale->post_draw = false;
scale->draw_ticks_on_top = false;
scale->custom_label_cnt = 0;
scale->txt_src = NULL;
lv_obj_remove_flag(obj, LV_OBJ_FLAG_SCROLLABLE);
LV_TRACE_OBJ_CREATE("finished");
}
static void lv_scale_destructor(const lv_obj_class_t * class_p, lv_obj_t * obj)
{
LV_UNUSED(class_p);
LV_TRACE_OBJ_CREATE("begin");
lv_scale_t * scale = (lv_scale_t *)obj;
lv_scale_section_t * section;
while(scale->section_ll.head) {
section = lv_ll_get_head(&scale->section_ll);
lv_ll_remove(&scale->section_ll, section);
lv_free(section);
}
lv_ll_clear(&scale->section_ll);
LV_TRACE_OBJ_CREATE("finished");
}
static void lv_scale_event(const lv_obj_class_t * class_p, lv_event_t * event)
{
LV_UNUSED(class_p);
/*Call the ancestor's event handler*/
lv_result_t res = lv_obj_event_base(MY_CLASS, event);
if(res != LV_RESULT_OK) return;
lv_event_code_t event_code = lv_event_get_code(event);
lv_obj_t * obj = lv_event_get_current_target(event);
lv_scale_t * scale = (lv_scale_t *) obj;
LV_UNUSED(scale);
if(event_code == LV_EVENT_DRAW_MAIN) {
if(scale->post_draw == false) {
scale_find_section_tick_idx(obj);
scale_calculate_main_compensation(obj);
if(scale->draw_ticks_on_top) {
scale_draw_main(obj, event);
scale_draw_indicator(obj, event);
}
else {
scale_draw_indicator(obj, event);
scale_draw_main(obj, event);
}
}
}
if(event_code == LV_EVENT_DRAW_POST) {
if(scale->post_draw == true) {
scale_find_section_tick_idx(obj);
scale_calculate_main_compensation(obj);
if(scale->draw_ticks_on_top) {
scale_draw_main(obj, event);
scale_draw_indicator(obj, event);
}
else {
scale_draw_indicator(obj, event);
scale_draw_main(obj, event);
}
}
}
else if(event_code == LV_EVENT_REFR_EXT_DRAW_SIZE) {
/* NOTE: Extend scale draw size so the first tick label can be shown */
lv_event_set_ext_draw_size(event, 100);
}
else {
/* Nothing to do. Invalid event */
}
}
static void scale_draw_indicator(lv_obj_t * obj, lv_event_t * event)
{
lv_scale_t * scale = (lv_scale_t *)obj;
lv_layer_t * layer = lv_event_get_layer(event);
if(scale->total_tick_count <= 1) return;
lv_draw_label_dsc_t label_dsc;
lv_draw_label_dsc_init(&label_dsc);
label_dsc.base.layer = layer;
/* Formatting the labels with the configured style for LV_PART_INDICATOR */
lv_obj_init_draw_label_dsc(obj, LV_PART_INDICATOR, &label_dsc);
/* Major tick style */
lv_draw_line_dsc_t major_tick_dsc;
lv_draw_line_dsc_init(&major_tick_dsc);
major_tick_dsc.base.layer = layer;
lv_obj_init_draw_line_dsc(obj, LV_PART_INDICATOR, &major_tick_dsc);
if(LV_SCALE_MODE_ROUND_OUTER == scale->mode || LV_SCALE_MODE_ROUND_INNER == scale->mode) {
major_tick_dsc.raw_end = 0;
}
/* Configure line draw descriptor for the minor tick drawing */
lv_draw_line_dsc_t minor_tick_dsc;
lv_draw_line_dsc_init(&minor_tick_dsc);
minor_tick_dsc.base.layer = layer;
lv_obj_init_draw_line_dsc(obj, LV_PART_ITEMS, &minor_tick_dsc);
/* Main line style */
lv_draw_line_dsc_t main_line_dsc;
lv_draw_line_dsc_init(&main_line_dsc);
main_line_dsc.base.layer = layer;
lv_obj_init_draw_line_dsc(obj, LV_PART_MAIN, &main_line_dsc);
/* These 2 values need to be signed since they are being passed
* to `lv_map()` which expects signed integers. */
const int32_t total_tick_count = scale->total_tick_count;
int32_t tick_idx = 0;
uint32_t major_tick_idx = 0U;
for(tick_idx = 0; tick_idx < total_tick_count; tick_idx++) {
/* A major tick is the one which has a label in it */
bool is_major_tick = false;
if(tick_idx % scale->major_tick_every == 0) is_major_tick = true;
if(is_major_tick) major_tick_idx++;
const int32_t tick_value = lv_map(tick_idx, 0, total_tick_count - 1, scale->range_min, scale->range_max);
label_dsc.base.id1 = tick_idx;
label_dsc.base.id2 = tick_value;
label_dsc.base.layer = layer;
/* Overwrite label and tick properties if tick value is within section range */
lv_scale_section_t * section;
LV_LL_READ_BACK(&scale->section_ll, section) {
if(section->range_min <= tick_value && section->range_max >= tick_value) {
if(is_major_tick) {
scale_set_indicator_label_properties(obj, &label_dsc, section->indicator_style);
scale_set_line_properties(obj, &major_tick_dsc, section->indicator_style, LV_PART_INDICATOR);
}
else {
scale_set_line_properties(obj, &minor_tick_dsc, section->items_style, LV_PART_ITEMS);
}
break;
}
else {
/* Tick is not in section, get the proper styles */
lv_obj_init_draw_label_dsc(obj, LV_PART_INDICATOR, &label_dsc);
lv_obj_init_draw_line_dsc(obj, LV_PART_INDICATOR, &major_tick_dsc);
lv_obj_init_draw_line_dsc(obj, LV_PART_ITEMS, &minor_tick_dsc);
}
}
/* The tick is represented by a line. We need two points to draw it */
lv_point_t tick_point_a;
lv_point_t tick_point_b;
scale_get_tick_points(obj, tick_idx, is_major_tick, &tick_point_a, &tick_point_b);
/* Setup a label if they're enabled and we're drawing a major tick */
if(scale->label_enabled && is_major_tick) {
scale_draw_label(obj, event, &label_dsc, major_tick_idx, tick_value, &tick_point_b, tick_idx);
}
if(is_major_tick) {
major_tick_dsc.p1 = lv_point_to_precise(&tick_point_a);
major_tick_dsc.p2 = lv_point_to_precise(&tick_point_b);
lv_draw_line(layer, &major_tick_dsc);
}
else {
minor_tick_dsc.p1 = lv_point_to_precise(&tick_point_a);
minor_tick_dsc.p2 = lv_point_to_precise(&tick_point_b);
lv_draw_line(layer, &minor_tick_dsc);
}
}
}
static void scale_draw_label(lv_obj_t * obj, lv_event_t * event, lv_draw_label_dsc_t * label_dsc,
const uint32_t major_tick_idx, const int32_t tick_value, lv_point_t * tick_point_b,
const uint32_t tick_idx)
{
lv_scale_t * scale = (lv_scale_t *)obj;
lv_layer_t * layer = lv_event_get_layer(event);
/* Label text setup */
char text_buffer[LV_SCALE_LABEL_TXT_LEN] = {0};
lv_area_t label_coords;
/* Check if the custom text array has element for this major tick index */
if(scale->txt_src) {
scale_build_custom_label_text(obj, label_dsc, major_tick_idx);
}
else { /* Add label with mapped values */
lv_snprintf(text_buffer, sizeof(text_buffer), "%" LV_PRId32, tick_value);
label_dsc->text = text_buffer;
label_dsc->text_local = 1;
}
int32_t translate_x = lv_obj_get_style_translate_x(obj, LV_PART_INDICATOR);
int32_t translate_y = lv_obj_get_style_translate_y(obj, LV_PART_INDICATOR);
int32_t label_rotation = lv_obj_get_style_transform_rotation(obj, LV_PART_INDICATOR);
int32_t translate_rotation = 0;
if((LV_SCALE_MODE_VERTICAL_LEFT == scale->mode || LV_SCALE_MODE_VERTICAL_RIGHT == scale->mode)
|| (LV_SCALE_MODE_HORIZONTAL_BOTTOM == scale->mode || LV_SCALE_MODE_HORIZONTAL_TOP == scale->mode)) {
lv_point_t label_origin;
label_origin.x = tick_point_b->x + translate_x;
label_origin.y = tick_point_b->y + translate_y;
scale_get_label_coords(obj, label_dsc, &label_origin, &label_coords);
label_rotation = (label_rotation & LV_SCALE_ROTATION_ANGLE_MASK);
}
else if(LV_SCALE_MODE_ROUND_OUTER == scale->mode || LV_SCALE_MODE_ROUND_INNER == scale->mode) {
translate_rotation = lv_obj_get_style_translate_radial(obj, LV_PART_INDICATOR);
uint32_t label_gap = lv_obj_get_style_pad_radial(obj, LV_PART_INDICATOR) + LV_SCALE_DEFAULT_LABEL_GAP;
lv_area_t scale_area;
lv_obj_get_content_coords(obj, &scale_area);
/* Find the center of the scale */
lv_point_t center_point;
int32_t radius_edge = LV_MIN(lv_area_get_width(&scale_area) / 2, lv_area_get_height(&scale_area) / 2);
center_point.x = scale_area.x1 + radius_edge;
center_point.y = scale_area.y1 + radius_edge;
const int32_t major_len = lv_obj_get_style_length(obj, LV_PART_INDICATOR);
/* Also take into consideration the letter space of the style */
int32_t angle_upscale = ((tick_idx * scale->angle_range) * 10U) / (scale->total_tick_count - 1U) +
(translate_rotation * 10);
angle_upscale += scale->rotation * 10;
uint32_t radius_text = 0;
if(LV_SCALE_MODE_ROUND_INNER == scale->mode) {
radius_text = (radius_edge - major_len) - (label_gap + label_dsc->letter_space);
}
else if(LV_SCALE_MODE_ROUND_OUTER == scale->mode) {
radius_text = (radius_edge + major_len) + (label_gap + label_dsc->letter_space);
}
else { /* Nothing to do */ }
lv_point_t point;
point.x = center_point.x + radius_text + translate_x;
point.y = center_point.y + translate_y;
int32_t label_rotation_temp = 0;
if(label_rotation & LV_SCALE_LABEL_ROTATE_MATCH_TICKS) {
label_rotation_temp = (label_rotation & LV_SCALE_ROTATION_ANGLE_MASK) + angle_upscale;
/* keep text upright if the user asked for it, otherwise it will be upside-down on half the dial */
if(label_rotation & LV_SCALE_LABEL_ROTATE_KEEP_UPRIGHT) {
while(label_rotation_temp > 3600) {
label_rotation_temp -= 3600;
}
if(label_rotation_temp > 900 && label_rotation_temp < 2400) {
label_rotation_temp += 1800;
}
}
label_rotation = label_rotation_temp;
}
else {
label_rotation = label_rotation & LV_SCALE_ROTATION_ANGLE_MASK;
}
lv_point_transform(&point, angle_upscale, LV_SCALE_NONE, LV_SCALE_NONE, &center_point, false);
scale_get_label_coords(obj, label_dsc, &point, &label_coords);
}
/* Invalid mode */
else {
return;
}
if(label_rotation > 0) {
/*Draw the label to a new layer and draw the layer rotated*/
lv_layer_t * layer_label = lv_draw_layer_create(layer, LV_COLOR_FORMAT_ARGB8888, &label_coords);
lv_draw_label(layer_label, label_dsc, &label_coords);
lv_point_t pivot_point;
/* Set pivot point to the center of the label so it matches the scale curve */
pivot_point.x = lv_area_get_width(&label_coords) / 2;
pivot_point.y = lv_area_get_height(&label_coords) / 2;
lv_draw_image_dsc_t layer_draw_dsc;
lv_draw_image_dsc_init(&layer_draw_dsc);
layer_draw_dsc.src = layer_label;
layer_draw_dsc.rotation = label_rotation;
layer_draw_dsc.pivot = pivot_point;
lv_draw_layer(layer, &layer_draw_dsc, &label_coords);
}
else {
lv_draw_label(layer, label_dsc, &label_coords);
}
}
static void scale_calculate_main_compensation(lv_obj_t * obj)
{
lv_scale_t * scale = (lv_scale_t *)obj;
const uint32_t total_tick_count = scale->total_tick_count;
if(total_tick_count <= 1) return;
/* Not supported in round modes */
if(LV_SCALE_MODE_ROUND_OUTER == scale->mode || LV_SCALE_MODE_ROUND_INNER == scale->mode) return;
/* Major tick style */
lv_draw_line_dsc_t major_tick_dsc;
lv_draw_line_dsc_init(&major_tick_dsc);
lv_obj_init_draw_line_dsc(obj, LV_PART_INDICATOR, &major_tick_dsc);
/* Configure line draw descriptor for the minor tick drawing */
lv_draw_line_dsc_t minor_tick_dsc;
lv_draw_line_dsc_init(&minor_tick_dsc);
lv_obj_init_draw_line_dsc(obj, LV_PART_ITEMS, &minor_tick_dsc);
uint32_t tick_idx = 0;
for(tick_idx = 0; tick_idx < total_tick_count; tick_idx++) {
const bool is_major_tick = tick_idx % scale->major_tick_every == 0;
const int32_t tick_value = lv_map(tick_idx, 0, total_tick_count - 1, scale->range_min, scale->range_max);
/* Overwrite label and tick properties if tick value is within section range */
lv_scale_section_t * section;
LV_LL_READ_BACK(&scale->section_ll, section) {
if(section->range_min <= tick_value && section->range_max >= tick_value) {
if(is_major_tick) {
scale_set_line_properties(obj, &major_tick_dsc, section->indicator_style, LV_PART_INDICATOR);
}
else {
scale_set_line_properties(obj, &minor_tick_dsc, section->items_style, LV_PART_ITEMS);
}
break;
}
else {
/* Tick is not in section, get the proper styles */
lv_obj_init_draw_line_dsc(obj, LV_PART_INDICATOR, &major_tick_dsc);
lv_obj_init_draw_line_dsc(obj, LV_PART_ITEMS, &minor_tick_dsc);
}
}
/* The tick is represented by a line. We need two points to draw it */
lv_point_t tick_point_a;
lv_point_t tick_point_b;
scale_get_tick_points(obj, tick_idx, is_major_tick, &tick_point_a, &tick_point_b);
/* Store initial and last tick widths to be used in the main line drawing */
scale_store_main_line_tick_width_compensation(obj, tick_idx, is_major_tick, major_tick_dsc.width, minor_tick_dsc.width);
/* Store the first and last section tick vertical/horizontal position */
scale_store_section_line_tick_width_compensation(obj, is_major_tick, &major_tick_dsc, &minor_tick_dsc,
tick_value, tick_idx, &tick_point_a);
}
}
static void scale_draw_main(lv_obj_t * obj, lv_event_t * event)
{
lv_scale_t * scale = (lv_scale_t *)obj;
lv_layer_t * layer = lv_event_get_layer(event);
if(scale->total_tick_count <= 1) return;
if((LV_SCALE_MODE_VERTICAL_LEFT == scale->mode || LV_SCALE_MODE_VERTICAL_RIGHT == scale->mode)
|| (LV_SCALE_MODE_HORIZONTAL_BOTTOM == scale->mode || LV_SCALE_MODE_HORIZONTAL_TOP == scale->mode)) {
/* Configure both line and label draw descriptors for the tick and label drawings */
lv_draw_line_dsc_t line_dsc;
lv_draw_line_dsc_init(&line_dsc);
line_dsc.base.layer = layer;
lv_obj_init_draw_line_dsc(obj, LV_PART_MAIN, &line_dsc);
/* Get style properties so they can be used in the main line drawing */
const int32_t border_width = lv_obj_get_style_border_width(obj, LV_PART_MAIN);
const int32_t pad_top = lv_obj_get_style_pad_top(obj, LV_PART_MAIN) + border_width;
const int32_t pad_bottom = lv_obj_get_style_pad_bottom(obj, LV_PART_MAIN) + border_width;
const int32_t pad_left = lv_obj_get_style_pad_left(obj, LV_PART_MAIN) + border_width;
const int32_t pad_right = lv_obj_get_style_pad_right(obj, LV_PART_MAIN) + border_width;
int32_t x_ofs = 0;
int32_t y_ofs = 0;
if(LV_SCALE_MODE_VERTICAL_LEFT == scale->mode) {
x_ofs = obj->coords.x2 + (line_dsc.width / 2) - pad_right;
y_ofs = obj->coords.y1 + pad_top;
}
else if(LV_SCALE_MODE_VERTICAL_RIGHT == scale->mode) {
x_ofs = obj->coords.x1 + (line_dsc.width / 2) + pad_left;
y_ofs = obj->coords.y1 + pad_top;
}
if(LV_SCALE_MODE_HORIZONTAL_BOTTOM == scale->mode) {
x_ofs = obj->coords.x1 + pad_right;
y_ofs = obj->coords.y1 + (line_dsc.width / 2) + pad_top;
}
else if(LV_SCALE_MODE_HORIZONTAL_TOP == scale->mode) {
x_ofs = obj->coords.x1 + pad_left;
y_ofs = obj->coords.y2 + (line_dsc.width / 2) - pad_bottom;
}
else { /* Nothing to do */ }
lv_point_t main_line_point_a;
lv_point_t main_line_point_b;
/* Setup the tick points */
if(LV_SCALE_MODE_VERTICAL_LEFT == scale->mode || LV_SCALE_MODE_VERTICAL_RIGHT == scale->mode) {
main_line_point_a.x = x_ofs - 1;
main_line_point_a.y = y_ofs;
main_line_point_b.x = x_ofs - 1;
main_line_point_b.y = obj->coords.y2 - pad_bottom;
/* Adjust main line with initial and last tick width */
main_line_point_a.y -= scale->last_tick_width / 2;
main_line_point_b.y += scale->first_tick_width / 2;
}
else {
main_line_point_a.x = x_ofs;
main_line_point_a.y = y_ofs;
/* X of second point starts at the edge of the object minus the left pad */
main_line_point_b.x = obj->coords.x2 - (pad_left);
main_line_point_b.y = y_ofs;
/* Adjust main line with initial and last tick width */
main_line_point_a.x -= scale->last_tick_width / 2;
main_line_point_b.x += scale->first_tick_width / 2;
}
line_dsc.p1 = lv_point_to_precise(&main_line_point_a);
line_dsc.p2 = lv_point_to_precise(&main_line_point_b);
lv_draw_line(layer, &line_dsc);
lv_scale_section_t * section;
LV_LL_READ_BACK(&scale->section_ll, section) {
lv_draw_line_dsc_t section_line_dsc;
lv_draw_line_dsc_init(&section_line_dsc);
section_line_dsc.base.layer = layer;
lv_obj_init_draw_line_dsc(obj, LV_PART_MAIN, &section_line_dsc);
/* Calculate the points of the section line */
lv_point_t section_point_a;
lv_point_t section_point_b;
const int32_t first_tick_width_halved = (int32_t)(section->first_tick_in_section_width / 2);
const int32_t last_tick_width_halved = (int32_t)(section->last_tick_in_section_width / 2);
/* Calculate the position of the section based on the ticks (first and last) index */
if(LV_SCALE_MODE_VERTICAL_LEFT == scale->mode || LV_SCALE_MODE_VERTICAL_RIGHT == scale->mode) {
/* Calculate position of the first tick in the section */
section_point_a.x = main_line_point_a.x;
section_point_a.y = section->first_tick_in_section.y + first_tick_width_halved;
/* Calculate position of the last tick in the section */
section_point_b.x = main_line_point_a.x;
section_point_b.y = section->last_tick_in_section.y - last_tick_width_halved;
}
else {
/* Calculate position of the first tick in the section */
section_point_a.x = section->first_tick_in_section.x - first_tick_width_halved;
section_point_a.y = main_line_point_a.y;
/* Calculate position of the last tick in the section */
section_point_b.x = section->last_tick_in_section.x + last_tick_width_halved;
section_point_b.y = main_line_point_a.y;
}
scale_set_line_properties(obj, &section_line_dsc, section->main_style, LV_PART_MAIN);
section_line_dsc.p1.x = section_point_a.x;
section_line_dsc.p1.y = section_point_a.y;
section_line_dsc.p2.x = section_point_b.x;
section_line_dsc.p2.y = section_point_b.y;
lv_draw_line(layer, &section_line_dsc);
}
}
else if(LV_SCALE_MODE_ROUND_OUTER == scale->mode || LV_SCALE_MODE_ROUND_INNER == scale->mode) {
/* Configure arc draw descriptors for the main part */
lv_draw_arc_dsc_t arc_dsc;
lv_draw_arc_dsc_init(&arc_dsc);
arc_dsc.base.layer = layer;
lv_obj_init_draw_arc_dsc(obj, LV_PART_MAIN, &arc_dsc);
lv_point_t arc_center;
int32_t arc_radius;
scale_get_center(obj, &arc_center, &arc_radius);
/* TODO: Add compensation for the width of the first and last tick over the arc */
const int32_t start_angle = lv_map(scale->range_min, scale->range_min, scale->range_max, scale->rotation,
scale->rotation + scale->angle_range);
const int32_t end_angle = lv_map(scale->range_max, scale->range_min, scale->range_max, scale->rotation,
scale->rotation + scale->angle_range);
arc_dsc.center = arc_center;
arc_dsc.radius = arc_radius;
arc_dsc.start_angle = start_angle;
arc_dsc.end_angle = end_angle;
lv_draw_arc(layer, &arc_dsc);
lv_scale_section_t * section;
LV_LL_READ_BACK(&scale->section_ll, section) {
lv_draw_arc_dsc_t main_arc_section_dsc;
lv_draw_arc_dsc_init(&main_arc_section_dsc);
main_arc_section_dsc.base.layer = layer;
lv_obj_init_draw_arc_dsc(obj, LV_PART_MAIN, &main_arc_section_dsc);
lv_point_t section_arc_center;
int32_t section_arc_radius;
scale_get_center(obj, &section_arc_center, &section_arc_radius);
/* TODO: Add compensation for the width of the first and last tick over the arc */
const int32_t section_start_angle = lv_map(section->range_min, scale->range_min, scale->range_max, scale->rotation,
scale->rotation + scale->angle_range);
const int32_t section_end_angle = lv_map(section->range_max, scale->range_min, scale->range_max, scale->rotation,
scale->rotation + scale->angle_range);
scale_set_arc_properties(obj, &main_arc_section_dsc, section->main_style);
main_arc_section_dsc.center = section_arc_center;
main_arc_section_dsc.radius = section_arc_radius;
main_arc_section_dsc.start_angle = section_start_angle;
main_arc_section_dsc.end_angle = section_end_angle;
lv_draw_arc(layer, &main_arc_section_dsc);
}
}
else { /* Nothing to do */ }
}
/**
* Get center point and radius of scale arc
* @param obj pointer to a scale object
* @param center pointer to center
* @param arc_r pointer to arc radius
*/
static void scale_get_center(const lv_obj_t * obj, lv_point_t * center, int32_t * arc_r)
{
int32_t left_bg = lv_obj_get_style_pad_left(obj, LV_PART_MAIN);
int32_t right_bg = lv_obj_get_style_pad_right(obj, LV_PART_MAIN);
int32_t top_bg = lv_obj_get_style_pad_top(obj, LV_PART_MAIN);
int32_t bottom_bg = lv_obj_get_style_pad_bottom(obj, LV_PART_MAIN);
int32_t r = (LV_MIN(lv_obj_get_width(obj) - left_bg - right_bg, lv_obj_get_height(obj) - top_bg - bottom_bg)) / 2;
center->x = obj->coords.x1 + r + left_bg;
center->y = obj->coords.y1 + r + top_bg;
if(arc_r) *arc_r = r;
}
/**
* Get points for ticks
*
* In order to draw ticks we need two points, this interface returns both points for all scale modes.
*
* @param obj pointer to a scale object
* @param tick_idx index of the current tick
* @param is_major_tick true if tick_idx is a major tick
* @param tick_point_a pointer to point 'a' of the tick
* @param tick_point_b pointer to point 'b' of the tick
*/
static void scale_get_tick_points(lv_obj_t * obj, const uint32_t tick_idx, bool is_major_tick,
lv_point_t * tick_point_a, lv_point_t * tick_point_b)
{
lv_scale_t * scale = (lv_scale_t *)obj;
/* Main line style */
lv_draw_line_dsc_t main_line_dsc;
lv_draw_line_dsc_init(&main_line_dsc);
lv_obj_init_draw_line_dsc(obj, LV_PART_MAIN, &main_line_dsc);
int32_t minor_len = 0;
int32_t major_len = 0;
int32_t radial_offset = 0;
if(is_major_tick) {
major_len = lv_obj_get_style_length(obj, LV_PART_INDICATOR);
radial_offset = lv_obj_get_style_radial_offset(obj, LV_PART_INDICATOR);
}
else {
minor_len = lv_obj_get_style_length(obj, LV_PART_ITEMS);
radial_offset = lv_obj_get_style_radial_offset(obj, LV_PART_ITEMS);
}
if((LV_SCALE_MODE_VERTICAL_LEFT == scale->mode || LV_SCALE_MODE_VERTICAL_RIGHT == scale->mode)
|| (LV_SCALE_MODE_HORIZONTAL_BOTTOM == scale->mode || LV_SCALE_MODE_HORIZONTAL_TOP == scale->mode)) {
/* Get style properties so they can be used in the tick and label drawing */
const int32_t border_width = lv_obj_get_style_border_width(obj, LV_PART_MAIN);
const int32_t pad_top = lv_obj_get_style_pad_top(obj, LV_PART_MAIN) + border_width;
const int32_t pad_bottom = lv_obj_get_style_pad_bottom(obj, LV_PART_MAIN) + border_width;
const int32_t pad_right = lv_obj_get_style_pad_right(obj, LV_PART_MAIN) + border_width;
const int32_t pad_left = lv_obj_get_style_pad_left(obj, LV_PART_MAIN) + border_width;
const int32_t tick_pad_right = lv_obj_get_style_pad_right(obj, LV_PART_ITEMS);
const int32_t tick_pad_left = lv_obj_get_style_pad_left(obj, LV_PART_ITEMS);
const int32_t tick_pad_top = lv_obj_get_style_pad_top(obj, LV_PART_ITEMS);
const int32_t tick_pad_bottom = lv_obj_get_style_pad_bottom(obj, LV_PART_ITEMS);
int32_t x_ofs = 0;
int32_t y_ofs = 0;
if(LV_SCALE_MODE_VERTICAL_LEFT == scale->mode) {
x_ofs = obj->coords.x2 + (main_line_dsc.width / 2) - pad_right;
y_ofs = obj->coords.y1 + (pad_top + tick_pad_top);
}
else if(LV_SCALE_MODE_VERTICAL_RIGHT == scale->mode) {
x_ofs = obj->coords.x1 + (main_line_dsc.width / 2) + pad_left;
y_ofs = obj->coords.y1 + (pad_top + tick_pad_top);
}
else if(LV_SCALE_MODE_HORIZONTAL_BOTTOM == scale->mode) {
x_ofs = obj->coords.x1 + (pad_right + tick_pad_right);
y_ofs = obj->coords.y1 + (main_line_dsc.width / 2) + pad_top;
}
/* LV_SCALE_MODE_HORIZONTAL_TOP == scale->mode */
else {
x_ofs = obj->coords.x1 + (pad_left + tick_pad_left);
y_ofs = obj->coords.y2 + (main_line_dsc.width / 2) - pad_bottom;
}
/* Adjust length when tick will be drawn on horizontal top or vertical right scales */
if((LV_SCALE_MODE_HORIZONTAL_TOP == scale->mode) || (LV_SCALE_MODE_VERTICAL_RIGHT == scale->mode)) {
if(is_major_tick) {
major_len *= -1;
}
else {
minor_len *= -1;
}
}
else { /* Nothing to do */ }
const int32_t tick_length = is_major_tick ? major_len : minor_len;
/* NOTE
* Minus 1 because tick count starts at 0
* TODO
* What if total_tick_count is 1? This will lead to an division by 0 further down */
const uint32_t tmp_tick_count = scale->total_tick_count - 1U;
/* Calculate the position of the tick points based on the mode and tick index */
if(LV_SCALE_MODE_VERTICAL_LEFT == scale->mode || LV_SCALE_MODE_VERTICAL_RIGHT == scale->mode) {
/* Vertical position starts at y2 of the scale main line, we start at y2 because the ticks are drawn from bottom to top */
int32_t vertical_position = obj->coords.y2 - (pad_bottom + tick_pad_bottom);
/* Position the last tick */
if(tmp_tick_count == tick_idx) {
vertical_position = y_ofs;
}
/* Otherwise adjust the tick position depending of its index and number of ticks on the scale */
else if(0 != tick_idx) {
const int32_t scale_total_height = lv_obj_get_height(obj) - (pad_top + pad_bottom + tick_pad_top + tick_pad_bottom);
const int32_t offset = ((int32_t) tick_idx * (int32_t) scale_total_height) / (int32_t)(tmp_tick_count);
vertical_position -= offset;
}
else { /* Nothing to do */ }
tick_point_a->x = x_ofs - 1; /* Move extra pixel out of scale boundary */
tick_point_a->y = vertical_position;
tick_point_b->x = tick_point_a->x - tick_length;
tick_point_b->y = vertical_position;
}
else {
/* Horizontal position starts at x1 of the scale main line */
int32_t horizontal_position = x_ofs;
/* Position the last tick */
if(tmp_tick_count == tick_idx) {
horizontal_position = obj->coords.x2 - (pad_left + tick_pad_left);
}
/* Otherwise adjust the tick position depending of its index and number of ticks on the scale */
else if(0U != tick_idx) {
const int32_t scale_total_width = lv_obj_get_width(obj) - (pad_right + pad_left + tick_pad_right + tick_pad_left);
const int32_t offset = ((int32_t) tick_idx * (int32_t) scale_total_width) / (int32_t)(tmp_tick_count);
horizontal_position += offset;
}
else { /* Nothing to do */ }
tick_point_a->x = horizontal_position;
tick_point_a->y = y_ofs;
tick_point_b->x = horizontal_position;
tick_point_b->y = tick_point_a->y + tick_length;
}
}
else if(LV_SCALE_MODE_ROUND_OUTER == scale->mode || LV_SCALE_MODE_ROUND_INNER == scale->mode) {
lv_area_t scale_area;
lv_obj_get_content_coords(obj, &scale_area);
/* Find the center of the scale */
lv_point_t center_point;
const int32_t radius_edge = LV_MIN(lv_area_get_width(&scale_area) / 2, lv_area_get_height(&scale_area) / 2);
center_point.x = scale_area.x1 + radius_edge;
center_point.y = scale_area.y1 + radius_edge;
int32_t angle_upscale = (int32_t)((tick_idx * scale->angle_range) * 10U) / (scale->total_tick_count - 1U);
angle_upscale += scale->rotation * 10;
/* Draw a little bit longer lines to be sure the mask will clip them correctly
* and to get a better precision. Adding the main line width to the calculation so we don't have gaps
* between the arc and the ticks */
int32_t point_closer_to_arc = 0;
int32_t adjusted_radio_with_tick_len = 0;
if(LV_SCALE_MODE_ROUND_INNER == scale->mode) {
point_closer_to_arc = radius_edge - main_line_dsc.width;
adjusted_radio_with_tick_len = point_closer_to_arc - (is_major_tick ? major_len : minor_len);
}
/* LV_SCALE_MODE_ROUND_OUTER == scale->mode */
else {
point_closer_to_arc = radius_edge - main_line_dsc.width;
adjusted_radio_with_tick_len = point_closer_to_arc + (is_major_tick ? major_len : minor_len);
}
tick_point_a->x = center_point.x + point_closer_to_arc + radial_offset;
tick_point_a->y = center_point.y;
lv_point_transform(tick_point_a, angle_upscale, LV_SCALE_NONE, LV_SCALE_NONE, &center_point, false);
tick_point_b->x = center_point.x + adjusted_radio_with_tick_len + radial_offset;
tick_point_b->y = center_point.y;
lv_point_transform(tick_point_b, angle_upscale, LV_SCALE_NONE, LV_SCALE_NONE, &center_point, false);
}
else { /* Nothing to do */ }
}
/**
* Get coordinates for label
*
* @param obj pointer to a scale object
* @param label_dsc pointer to label descriptor
* @param tick_point pointer to reference tick
* @param label_coords pointer to label coordinates output
*/
static void scale_get_label_coords(lv_obj_t * obj, lv_draw_label_dsc_t * label_dsc, lv_point_t * tick_point,
lv_area_t * label_coords)
{
lv_scale_t * scale = (lv_scale_t *)obj;
/* Reserve appropriate size for the tick label */
lv_point_t label_size;
lv_text_get_size(&label_size, label_dsc->text,
label_dsc->font, label_dsc->letter_space, label_dsc->line_space, LV_COORD_MAX, LV_TEXT_FLAG_NONE);
/* Set the label draw area at some distance of the major tick */
if((LV_SCALE_MODE_HORIZONTAL_BOTTOM == scale->mode) || (LV_SCALE_MODE_HORIZONTAL_TOP == scale->mode)) {
label_coords->x1 = tick_point->x - (label_size.x / 2);
label_coords->x2 = tick_point->x + (label_size.x / 2);
if(LV_SCALE_MODE_HORIZONTAL_BOTTOM == scale->mode) {
label_coords->y1 = tick_point->y + lv_obj_get_style_pad_bottom(obj, LV_PART_INDICATOR);
label_coords->y2 = label_coords->y1 + label_size.y;
}
else {
label_coords->y2 = tick_point->y - lv_obj_get_style_pad_top(obj, LV_PART_INDICATOR);
label_coords->y1 = label_coords->y2 - label_size.y;
}
}
else if((LV_SCALE_MODE_VERTICAL_LEFT == scale->mode) || (LV_SCALE_MODE_VERTICAL_RIGHT == scale->mode)) {
label_coords->y1 = tick_point->y - (label_size.y / 2);
label_coords->y2 = tick_point->y + (label_size.y / 2);
if(LV_SCALE_MODE_VERTICAL_LEFT == scale->mode) {
label_coords->x1 = tick_point->x - label_size.x - lv_obj_get_style_pad_left(obj, LV_PART_INDICATOR);
label_coords->x2 = tick_point->x - lv_obj_get_style_pad_left(obj, LV_PART_INDICATOR);
}
else {
label_coords->x1 = tick_point->x + lv_obj_get_style_pad_right(obj, LV_PART_INDICATOR);
label_coords->x2 = tick_point->x + label_size.x + lv_obj_get_style_pad_right(obj, LV_PART_INDICATOR);
}
}
else if(LV_SCALE_MODE_ROUND_OUTER == scale->mode || LV_SCALE_MODE_ROUND_INNER == scale->mode) {
label_coords->x1 = tick_point->x - (label_size.x / 2);
label_coords->y1 = tick_point->y - (label_size.y / 2);
label_coords->x2 = label_coords->x1 + label_size.x;
label_coords->y2 = label_coords->y1 + label_size.y;
}
else { /* Nothing to do */ }
}
/**
* Set line properties
*
* Checks if the line has a custom section configuration or not and sets the properties accordingly.
*
* @param obj pointer to a scale object
* @param line_dsc pointer to line descriptor
* @param items_section_style pointer to indicator section style
* @param part line part, example: LV_PART_INDICATOR, LV_PART_ITEMS, LV_PART_MAIN
*/
static void scale_set_line_properties(lv_obj_t * obj, lv_draw_line_dsc_t * line_dsc, const lv_style_t * section_style,
lv_part_t part)
{
if(section_style) {
lv_style_value_t value;
lv_style_res_t res;
/* Line width */
res = lv_style_get_prop(section_style, LV_STYLE_LINE_WIDTH, &value);
if(res == LV_STYLE_RES_FOUND) {
line_dsc->width = (int32_t)value.num;
}
else {
line_dsc->width = lv_obj_get_style_line_width(obj, part);
}
/* Line color */
res = lv_style_get_prop(section_style, LV_STYLE_LINE_COLOR, &value);
if(res == LV_STYLE_RES_FOUND) {
line_dsc->color = value.color;
}
else {
line_dsc->color = lv_obj_get_style_line_color(obj, part);
}
/* Line opa */
res = lv_style_get_prop(section_style, LV_STYLE_LINE_OPA, &value);
if(res == LV_STYLE_RES_FOUND) {
line_dsc->opa = (lv_opa_t)value.num;
}
else {
line_dsc->opa = lv_obj_get_style_line_opa(obj, part);
}
}
else {
line_dsc->color = lv_obj_get_style_line_color(obj, part);
line_dsc->opa = lv_obj_get_style_line_opa(obj, part);
line_dsc->width = lv_obj_get_style_line_width(obj, part);
}
}
/**
* Set arc properties
*
* Checks if the arc has a custom section configuration or not and sets the properties accordingly.
*
* @param obj pointer to a scale object
* @param arc_dsc pointer to arc descriptor
* @param items_section_style pointer to indicator section style
*/
static void scale_set_arc_properties(lv_obj_t * obj, lv_draw_arc_dsc_t * arc_dsc, const lv_style_t * section_style)
{
if(section_style) {
lv_style_value_t value;
lv_style_res_t res;
/* arc width */
res = lv_style_get_prop(section_style, LV_STYLE_ARC_WIDTH, &value);
if(res == LV_STYLE_RES_FOUND) {
arc_dsc->width = (int32_t)value.num;
}
else {
arc_dsc->width = lv_obj_get_style_arc_width(obj, LV_PART_MAIN);
}
/* arc color */
res = lv_style_get_prop(section_style, LV_STYLE_ARC_COLOR, &value);
if(res == LV_STYLE_RES_FOUND) {
arc_dsc->color = value.color;
}
else {
arc_dsc->color = lv_obj_get_style_arc_color(obj, LV_PART_MAIN);
}
/* arc opa */
res = lv_style_get_prop(section_style, LV_STYLE_ARC_OPA, &value);
if(res == LV_STYLE_RES_FOUND) {
arc_dsc->opa = (lv_opa_t)value.num;
}
else {
arc_dsc->opa = lv_obj_get_style_arc_opa(obj, LV_PART_MAIN);
}
/* arc rounded */
res = lv_style_get_prop(section_style, LV_STYLE_ARC_ROUNDED, &value);
if(res == LV_STYLE_RES_FOUND) {
arc_dsc->rounded = (uint8_t)value.num;
}
else {
arc_dsc->rounded = lv_obj_get_style_arc_rounded(obj, LV_PART_MAIN);
}
/* arc image src */
res = lv_style_get_prop(section_style, LV_STYLE_ARC_IMAGE_SRC, &value);
if(res == LV_STYLE_RES_FOUND) {
arc_dsc->img_src = (const void *)value.ptr;
}
else {
arc_dsc->img_src = lv_obj_get_style_arc_image_src(obj, LV_PART_MAIN);
}
}
else {
arc_dsc->color = lv_obj_get_style_arc_color(obj, LV_PART_MAIN);
arc_dsc->opa = lv_obj_get_style_arc_opa(obj, LV_PART_MAIN);
arc_dsc->width = lv_obj_get_style_arc_width(obj, LV_PART_MAIN);
arc_dsc->rounded = lv_obj_get_style_arc_rounded(obj, LV_PART_MAIN);
arc_dsc->img_src = lv_obj_get_style_arc_image_src(obj, LV_PART_MAIN);
}
}
/**
* Set indicator label properties
*
* Checks if the indicator has a custom section configuration or not and sets the properties accordingly.
*
* @param obj pointer to a scale object
* @param label_dsc pointer to label descriptor
* @param items_section_style pointer to indicator section style
*/
static void scale_set_indicator_label_properties(lv_obj_t * obj, lv_draw_label_dsc_t * label_dsc,
const lv_style_t * indicator_section_style)
{
if(indicator_section_style) {
lv_style_value_t value;
lv_style_res_t res;
/* Text color */
res = lv_style_get_prop(indicator_section_style, LV_STYLE_TEXT_COLOR, &value);
if(res == LV_STYLE_RES_FOUND) {
label_dsc->color = value.color;
}
else {
label_dsc->color = lv_obj_get_style_text_color(obj, LV_PART_INDICATOR);
}
/* Text opa */
res = lv_style_get_prop(indicator_section_style, LV_STYLE_TEXT_OPA, &value);
if(res == LV_STYLE_RES_FOUND) {
label_dsc->opa = (lv_opa_t)value.num;
}
else {
label_dsc->opa = lv_obj_get_style_text_opa(obj, LV_PART_INDICATOR);
}
/* Text letter space */
res = lv_style_get_prop(indicator_section_style, LV_STYLE_TEXT_LETTER_SPACE, &value);
if(res == LV_STYLE_RES_FOUND) {
label_dsc->letter_space = (int32_t)value.num;
}
else {
label_dsc->letter_space = lv_obj_get_style_text_letter_space(obj, LV_PART_INDICATOR);
}
/* Text font */
res = lv_style_get_prop(indicator_section_style, LV_STYLE_TEXT_FONT, &value);
if(res == LV_STYLE_RES_FOUND) {
label_dsc->font = (const lv_font_t *)value.ptr;
}
else {
label_dsc->font = lv_obj_get_style_text_font(obj, LV_PART_INDICATOR);
}
}
else {
/* If label is not within a range then get the indicator style */
label_dsc->color = lv_obj_get_style_text_color(obj, LV_PART_INDICATOR);
label_dsc->opa = lv_obj_get_style_text_opa(obj, LV_PART_INDICATOR);
label_dsc->letter_space = lv_obj_get_style_text_letter_space(obj, LV_PART_INDICATOR);
label_dsc->font = lv_obj_get_style_text_font(obj, LV_PART_INDICATOR);
}
}
static void scale_find_section_tick_idx(lv_obj_t * obj)
{
lv_scale_t * scale = (lv_scale_t *)obj;
const int32_t min_out = scale->range_min;
const int32_t max_out = scale->range_max;
const uint32_t total_tick_count = scale->total_tick_count;
/* Section handling */
uint32_t tick_idx = 0;
for(tick_idx = 0; tick_idx < total_tick_count; tick_idx++) {
bool is_major_tick = false;
if(tick_idx % scale->major_tick_every == 0) is_major_tick = true;
const int32_t tick_value = lv_map(tick_idx, 0, total_tick_count - 1, min_out, max_out);
lv_scale_section_t * section;
LV_LL_READ_BACK(&scale->section_ll, section) {
if(section->range_min <= tick_value && section->range_max >= tick_value) {
if(LV_SCALE_TICK_IDX_DEFAULT_ID == section->first_tick_idx_in_section) {
section->first_tick_idx_in_section = tick_idx;
section->first_tick_idx_is_major = is_major_tick;
}
if(LV_SCALE_TICK_IDX_DEFAULT_ID == section->last_tick_idx_in_section) {
/* This gets it initialized when the beginning and ending range values are the same. */
section->last_tick_idx_in_section = tick_idx;
section->last_tick_idx_is_major = is_major_tick;
}
/* Now keep setting the `last_tick_idx_...` values as we
* proceed through the `for` loop so it is left with the
* actual last-tick value that is within the Scale's range. */
else if(section->first_tick_idx_in_section != tick_idx) {
section->last_tick_idx_in_section = tick_idx;
section->last_tick_idx_is_major = is_major_tick;
}
}
else {
/* `tick_value` is outside Section's range.
* Nothing to do. */
}
}
}
}
/**
* Stores the width of the initial and last tick of the main line
*
* This width is used to compensate the main line drawing taking into consideration the widths of both ticks
*
* @param obj pointer to a scale object
* @param tick_idx index of the current tick
* @param is_major_tick true if tick_idx is a major tick
* @param major_tick_width width of the major tick
* @param minor_tick_width width of the minor tick
*/
static void scale_store_main_line_tick_width_compensation(lv_obj_t * obj, const uint32_t tick_idx,
const bool is_major_tick, const int32_t major_tick_width, const int32_t minor_tick_width)
{
lv_scale_t * scale = (lv_scale_t *)obj;
const bool is_first_tick = 0U == tick_idx;
const bool is_last_tick = scale->total_tick_count == tick_idx;
const int32_t tick_width = is_major_tick ? major_tick_width : minor_tick_width;
/* Exit early if tick_idx is neither the first nor last tick on the main line */
if(((!is_last_tick) && (!is_first_tick))
/* Exit early if scale mode is round. It doesn't support main line compensation */
|| ((LV_SCALE_MODE_ROUND_INNER == scale->mode) || (LV_SCALE_MODE_ROUND_OUTER == scale->mode))) {
return;
}
if(is_last_tick) {
/* Mode is vertical */
if((LV_SCALE_MODE_VERTICAL_LEFT == scale->mode) || (LV_SCALE_MODE_VERTICAL_RIGHT == scale->mode)) {
scale->last_tick_width = tick_width;
}
/* Mode is horizontal */
else {
scale->first_tick_width = tick_width;
}
}
/* is_first_tick */
else {
/* Mode is vertical */
if((LV_SCALE_MODE_VERTICAL_LEFT == scale->mode) || (LV_SCALE_MODE_VERTICAL_RIGHT == scale->mode)) {
scale->first_tick_width = tick_width;
}
/* Mode is horizontal */
else {
scale->last_tick_width = tick_width;
}
}
}
/**
* Sets the text of the tick label descriptor when using custom labels
*
* Sets the text pointer when valid custom label is available, otherwise set it to NULL.
*
* @param obj pointer to a scale object
* @param label_dsc pointer to the label descriptor
* @param major_tick_idx index of the current major tick
*/
static void scale_build_custom_label_text(lv_obj_t * obj, lv_draw_label_dsc_t * label_dsc,
const uint16_t major_tick_idx)
{
lv_scale_t * scale = (lv_scale_t *) obj;
/* Check if the scale has valid custom labels available,
* this avoids reading past txt_src array when the scale requires more tick labels than available */
if(major_tick_idx <= scale->custom_label_cnt) {
if(scale->txt_src[major_tick_idx - 1U]) {
label_dsc->text = scale->txt_src[major_tick_idx - 1U];
label_dsc->text_local = 0;
}
else {
label_dsc->text = NULL;
}
}
else {
label_dsc->text = NULL;
}
}
/**
* Stores tick width compensation information for main line sections
*
* @param obj pointer to a scale object
* @param is_major_tick Indicates if tick is major or not
* @param major_tick_dsc pointer to the major_tick_dsc
* @param minor_tick_dsc pointer to the minor_tick_dsc
* @param tick_value Current tick value, used to know if tick_idx belongs to a section or not
* @param tick_idx Current tick index
* @param tick_point_a Pointer to tick point a
*/
static void scale_store_section_line_tick_width_compensation(lv_obj_t * obj, const bool is_major_tick,
lv_draw_line_dsc_t * major_tick_dsc, lv_draw_line_dsc_t * minor_tick_dsc,
const int32_t tick_value, const uint8_t tick_idx, lv_point_t * tick_point_a)
{
lv_scale_t * scale = (lv_scale_t *) obj;
lv_scale_section_t * section;
LV_LL_READ_BACK(&scale->section_ll, section) {
if(section->range_min <= tick_value && section->range_max >= tick_value) {
if(is_major_tick) {
scale_set_line_properties(obj, major_tick_dsc, section->indicator_style, LV_PART_INDICATOR);
}
else {
scale_set_line_properties(obj, minor_tick_dsc, section->items_style, LV_PART_ITEMS);
}
}
int32_t tmp_width = 0;
if(tick_idx == section->first_tick_idx_in_section) {
if(section->first_tick_idx_is_major) {
tmp_width = major_tick_dsc->width;
}
else {
tmp_width = minor_tick_dsc->width;
}
section->first_tick_in_section = *tick_point_a;
/* Add 1px as adjustment if tmp_width is odd */
if(tmp_width & 0x01U) {
if(LV_SCALE_MODE_VERTICAL_LEFT == scale->mode || LV_SCALE_MODE_VERTICAL_RIGHT == scale->mode) {
tmp_width += 1;
}
else {
tmp_width -= 1;
}
}
section->first_tick_in_section_width = tmp_width;
}
/* This can also apply when
* (tick_idx == section->first_tick_idx_in_section) when the
* beginning and ending values of the range are the same. */
if(tick_idx == section->last_tick_idx_in_section) {
if(section->last_tick_idx_is_major) {
tmp_width = major_tick_dsc->width;
}
else {
tmp_width = minor_tick_dsc->width;
}
section->last_tick_in_section = *tick_point_a;
/* Add 1px as adjustment if tmp_width is odd */
if(tmp_width & 0x01U) {
if(LV_SCALE_MODE_VERTICAL_LEFT == scale->mode || LV_SCALE_MODE_VERTICAL_RIGHT == scale->mode) {
tmp_width -= 1;
}
else {
tmp_width += 1;
}
}
section->last_tick_in_section_width = tmp_width;
}
else { /* Nothing to do */ }
}
}
static void scale_free_line_needle_points_cb(lv_event_t * e)
{
lv_point_precise_t * needle_line_points = lv_event_get_user_data(e);
lv_free(needle_line_points);
}
#endif