fix(scale): fix angle calculation error (#7362)

Co-authored-by: Gabor Kiss-Vamosi <kisvegabor@gmail.com>
This commit is contained in:
Victor Wheeler
2024-12-01 19:51:10 -07:00
committed by GitHub
parent 6e7ad249fd
commit 8baabf358b
5 changed files with 197 additions and 103 deletions

View File

@@ -55,8 +55,14 @@ be any of these values:
Setting range
-------------
A Scale starts its life with a default range of [0..100]. You can change this
range with :cpp:expr:`lv_scale_set_range(scale, min, max)`.
A Scale starts its life with a default numeric range of [0..100] and a default
angular range of 270. You can change these ranges with:
- :cpp:expr:`lv_scale_set_range(scale, min, max)`, and
- :cpp:expr:`lv_scale_set_angle_range(scale, angle_range)`
where ``min`` and ``max`` will become the numeric low and high values for the Scale,
and ``angle_range`` is the angle between the low and high ends of the Scale.
Tick drawing order

View File

@@ -174,9 +174,17 @@ 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;
scale->rotation = 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);
}
@@ -335,16 +343,19 @@ lv_scale_section_t * lv_scale_add_section(lv_obj_t * obj)
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_section_set_range(lv_scale_section_t * section, int32_t minor_range, int32_t major_range)
void lv_scale_section_set_range(lv_scale_section_t * section, int32_t min, int32_t max)
{
if(NULL == section) return;
section->minor_range = minor_range;
section->major_range = major_range;
section->range_min = min;
section->range_max = max;
}
void lv_scale_section_set_style(lv_scale_section_t * section, lv_part_t part, lv_style_t * section_part_style)
@@ -389,6 +400,12 @@ int32_t lv_scale_get_major_tick_every(lv_obj_t * obj)
return scale->major_tick_every;
}
lv_scale_mode_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;
@@ -568,7 +585,7 @@ static void scale_draw_indicator(lv_obj_t * obj, lv_event_t * event)
/* 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->minor_range <= tick_value && section->major_range >= tick_value) {
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);
@@ -755,7 +772,7 @@ static void scale_calculate_main_compensation(lv_obj_t * obj)
/* 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->minor_range <= tick_value && section->major_range >= tick_value) {
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);
}
@@ -933,9 +950,9 @@ static void scale_draw_main(lv_obj_t * obj, lv_event_t * event)
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->minor_range, scale->range_min, scale->range_max, scale->rotation,
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->major_range, scale->range_min, scale->range_max, scale->rotation,
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);
@@ -1395,7 +1412,7 @@ static void scale_find_section_tick_idx(lv_obj_t * obj)
lv_scale_section_t * section;
LV_LL_READ_BACK(&scale->section_ll, section) {
if(section->minor_range <= tick_value && section->major_range >= tick_value) {
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;
@@ -1519,7 +1536,7 @@ static void scale_store_section_line_tick_width_compensation(lv_obj_t * obj, con
lv_scale_section_t * section;
LV_LL_READ_BACK(&scale->section_ll, section) {
if(section->minor_range <= tick_value && section->major_range >= tick_value) {
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);
}

View File

@@ -72,7 +72,7 @@ LV_ATTRIBUTE_EXTERN_DATA extern const lv_obj_class_t lv_scale_class;
/**
* Create an scale object
* @param parent pointer to an object, it will be the parent of the new scale
* @return pointer to the created scale
* @return pointer to created Scale Widget
*/
lv_obj_t * lv_scale_create(lv_obj_t * parent);
@@ -85,120 +85,137 @@ lv_obj_t * lv_scale_create(lv_obj_t * parent);
*====================*/
/**
* Set scale mode. See lv_scale_mode_t
* @param obj pointer the scale object
* Set scale mode. See lv_scale_mode_t.
* @param obj pointer to Scale Widget
* @param mode the new scale mode
*/
void lv_scale_set_mode(lv_obj_t * obj, lv_scale_mode_t mode);
/**
* Set scale total tick count (including minor and major ticks)
* @param obj pointer the scale object
* Set scale total tick count (including minor and major ticks).
* @param obj pointer to Scale Widget
* @param total_tick_count New total tick count
*/
void lv_scale_set_total_tick_count(lv_obj_t * obj, uint32_t total_tick_count);
/**
* Sets how often the major tick will be drawn
* @param obj pointer the scale object
* Sets how often major ticks are drawn.
* @param obj pointer to Scale Widget
* @param major_tick_every the new count for major tick drawing
*/
void lv_scale_set_major_tick_every(lv_obj_t * obj, uint32_t major_tick_every);
/**
* Sets label visibility
* @param obj pointer the scale object
* Sets label visibility.
* @param obj pointer to Scale Widget
* @param show_label true/false to enable tick label
*/
void lv_scale_set_label_show(lv_obj_t * obj, bool show_label);
/**
* Set the minimal and maximal values on a scale
* @param obj pointer to a scale object
* @param min minimum value of the scale
* @param max maximum value of the scale
* Set minimum and maximum values on Scale.
* @param obj pointer to Scale Widget
* @param min minimum value of Scale
* @param max maximum value of Scale
*/
void lv_scale_set_range(lv_obj_t * obj, int32_t min, int32_t max);
/**
* Set properties specific to round scale
* @param obj pointer to a scale object
* @param angle_range the angular range of the scale
* Set angle between the low end and the high end of the Scale.
* (Applies only to round Scales.)
* @param obj pointer to Scale Widget
* @param max_angle angle in degrees from Scale minimum where top end of Scale will be drawn
*/
void lv_scale_set_angle_range(lv_obj_t * obj, uint32_t angle_range);
/**
* Set properties specific to round scale
* @param obj pointer to a scale object
* @param rotation the angular offset from the 3 o'clock position (clock-wise)
* Set angular offset from the 3-o'clock position of the low end of the Scale.
* (Applies only to round Scales.)
* @param obj pointer to Scale Widget
* @param rotation clockwise angular offset (in degrees) from the 3-o'clock position
* of the low end of the scale; negative and >360 values are first normalized
* to range [0..360].
* Examples:
* - 0 = 3 o'clock (right side)
* - 30 = 4 o'clock
* - 60 = 5 o'clock
* - 90 = 6 o'clock
* - 135 = midway between 7 and 8 o'clock (default)
* - 180 = 9 o'clock
* - 270 = 12 o'clock
* - 300 = 1 o'clock
* - 330 = 2 o'clock
* - -30 = 2 o'clock
* - 390 = 4 o'clock
*/
void lv_scale_set_rotation(lv_obj_t * obj, int32_t rotation);
/**
* Point the needle to the corresponding value through the line
* @param obj pointer to a scale object
* @param needle_line needle_line of the scale. The line points will be allocated and
* managed by the scale unless the line point array was previously set
* Point line needle to specified value.
* @param obj pointer to Scale Widget
* @param needle_line needle_line of the Scale. The line points will be allocated and
* managed by the Scale unless the line point array was previously set
* using `lv_line_set_points_mutable`.
* @param needle_length length of the needle
* needle_length>0 needle_length=needle_length;
* needle_length<0 needle_length=radius-|needle_length|;
* @param value needle to point to the corresponding value
* - needle_length>0: needle_length=needle_length;
* - needle_length<0: needle_length=radius-|needle_length|;
* @param value Scale value needle will point to
*/
void lv_scale_set_line_needle_value(lv_obj_t * obj, lv_obj_t * needle_line, int32_t needle_length,
int32_t value);
/**
* Point the needle to the corresponding value through the image,
* Point image needle to specified value;
image must point to the right. E.g. -O------>
* @param obj pointer to a scale object
* @param needle_img needle_img of the scale
* @param value needle to point to the corresponding value
* @param obj pointer to Scale Widget
* @param needle_img pointer to needle's Image
* @param value Scale value needle will point to
*/
void lv_scale_set_image_needle_value(lv_obj_t * obj, lv_obj_t * needle_img, int32_t value);
/**
* Set custom text source for major ticks labels
* @param obj pointer to a scale object
* Set custom text source for major ticks labels.
* @param obj pointer to Scale Widget
* @param txt_src pointer to an array of strings which will be display at major ticks;
* last element must be a NULL pointer.
*/
void lv_scale_set_text_src(lv_obj_t * obj, const char * txt_src[]);
/**
* Draw the scale after all the children are drawn
* @param obj pointer to a scale object
* Draw Scale after all its children are drawn.
* @param obj pointer to Scale Widget
* @param en true: enable post draw
*/
void lv_scale_set_post_draw(lv_obj_t * obj, bool en);
/**
* Draw the scale ticks on top of all parts
* @param obj pointer to a scale object
* Draw Scale ticks on top of all other parts.
* @param obj pointer to Scale Widget
* @param en true: enable draw ticks on top of all parts
*/
void lv_scale_set_draw_ticks_on_top(lv_obj_t * obj, bool en);
/**
* Add a section to the given scale
* @param obj pointer to a scale object
* @return pointer to the new section
* Add a Section to specified Scale. Section will not be drawn until
* a valid range is set for it using `lv_scale_section_set_range()`.
* @param obj pointer to Scale Widget
* @return pointer to new Section
*/
lv_scale_section_t * lv_scale_add_section(lv_obj_t * obj);
/**
* Set the range for the given scale section
* @param section pointer to a scale section object
* @param minor_range section new minor range
* @param major_range section new major range
* Set range for specified Scale Section
* @param section pointer to Section
* @param range_min Section new minimum value
* @param range_max Section new maximum value
*/
void lv_scale_section_set_range(lv_scale_section_t * section, int32_t minor_range, int32_t major_range);
void lv_scale_section_set_range(lv_scale_section_t * section, int32_t min, int32_t max);
/**
* Set the style of the part for the given scale section
* @param section pointer to a scale section object
* @param part the part for the section, e.g. LV_PART_INDICATOR
* @param section_part_style Pointer to the section part style
* Set style for specified part of Section.
* @param section pointer to Section
* @param part the part of the Scale the style will apply to, e.g. LV_PART_INDICATOR
* @param section_part_style pointer to style to apply
*/
void lv_scale_section_set_style(lv_scale_section_t * section, lv_part_t part, lv_style_t * section_part_style);
@@ -208,50 +225,57 @@ void lv_scale_section_set_style(lv_scale_section_t * section, lv_part_t part, lv
/**
* Get scale mode. See lv_scale_mode_t
* @param obj pointer the scale object
* @param obj pointer to Scale Widget
* @return Scale mode
*/
lv_scale_mode_t lv_scale_get_mode(lv_obj_t * obj);
/**
* Get scale total tick count (including minor and major ticks)
* @param obj pointer the scale object
* @param obj pointer to Scale Widget
* @return Scale total tick count
*/
int32_t lv_scale_get_total_tick_count(lv_obj_t * obj);
/**
* Gets how often the major tick will be drawn
* @param obj pointer the scale object
* Get how often the major tick will be drawn
* @param obj pointer to Scale Widget
* @return Scale major tick every count
*/
int32_t lv_scale_get_major_tick_every(lv_obj_t * obj);
/**
* Get angular location of low end of Scale.
* @param obj pointer to Scale Widget
* @return Scale major tick every count
*/
lv_scale_mode_t lv_scale_get_rotation(lv_obj_t * obj);
/**
* Gets label visibility
* @param obj pointer the scale object
* @param obj pointer to Scale Widget
* @return true if tick label is enabled, false otherwise
*/
bool lv_scale_get_label_show(lv_obj_t * obj);
/**
* Get angle range of a round scale
* @param obj pointer to a scale object
* @return Scale angle_range
* Get Scale's range in degrees
* @param obj pointer to Scale Widget
* @return Scale's angle_range
*/
uint32_t lv_scale_get_angle_range(lv_obj_t * obj);
/**
* Get the min range for the given scale section
* @param obj pointer to a scale section object
* @return section minor range
* Get minimum value for Scale
* @param obj pointer to Scale Widget
* @return Scale's minimum value
*/
int32_t lv_scale_get_range_min_value(lv_obj_t * obj);
/**
* Get the max range for the given scale section
* @param obj pointer to a scale section object
* @return section max range
* Get maximum value for Scale
* @param obj pointer to Scale Widget
* @return Scale's maximum value
*/
int32_t lv_scale_get_range_max_value(lv_obj_t * obj);

View File

@@ -28,40 +28,49 @@ extern "C" {
**********************/
struct _lv_scale_section_t {
lv_style_t * main_style;
lv_style_t * indicator_style;
lv_style_t * items_style;
int32_t minor_range;
int32_t major_range;
uint32_t first_tick_idx_in_section;
uint32_t last_tick_idx_in_section;
uint32_t first_tick_idx_is_major;
uint32_t last_tick_idx_is_major;
int32_t first_tick_in_section_width;
int32_t last_tick_in_section_width;
lv_point_t first_tick_in_section;
lv_point_t last_tick_in_section;
lv_style_t * main_style; /**< Style to use for MAIN part(s) of scale
* when it falls within this section's range */
lv_style_t * indicator_style; /**< Style to use for INDICATOR part(s) of scale
* when it falls within this section's range */
lv_style_t * items_style; /**< Style to use for ITEMS part(s) of scale
* when it falls within this section's range */
int32_t range_min; /**< Scale parts with value >= this value will be drawn using applicable style. */
int32_t range_max; /**< Scale parts with value <= this value will be drawn using applicable style. */
uint32_t first_tick_idx_in_section; /**< Internal (set during drawing): Tick index of first tick that falls within
* this section; LV_SCALE_TICK_IDX_DEFAULT_ID if section contains no ticks. */
uint32_t last_tick_idx_in_section; /**< Internal (set during drawing): Tick index of last tick that falls within
* this section; LV_SCALE_TICK_IDX_DEFAULT_ID if section contains no ticks. */
int32_t first_tick_in_section_width; /**< Internal (set during drawing) */
int32_t last_tick_in_section_width; /**< Internal (set during drawing) */
lv_point_t first_tick_in_section; /**< Internal (set during drawing) */
lv_point_t last_tick_in_section; /**< Internal (set during drawing) */
uint32_t first_tick_idx_is_major : 1; /**< Internal (set during drawing): true if
* `first_tick_idx_in_section` represents a major tick. */
uint32_t last_tick_idx_is_major : 1; /**< Internal (set during drawing): true if
* `last_tick_idx_in_section` represents a major tick. */
};
struct _lv_scale_t {
lv_obj_t obj;
lv_ll_t section_ll; /**< Linked list for the sections (stores lv_scale_section_t)*/
const char ** txt_src;
lv_scale_mode_t mode;
int32_t range_min;
int32_t range_max;
uint32_t total_tick_count : 15;
uint32_t major_tick_every : 15;
uint32_t label_enabled : 1;
uint32_t post_draw : 1;
uint32_t draw_ticks_on_top : 1;
lv_obj_t obj; /**< Base Widget part of Scale */
lv_ll_t section_ll; /**< Linked list for the sections (stores lv_scale_section_t)*/
const char ** txt_src; /**< Optional list of text strings for major ticks
* when custom labels are provided. */
lv_scale_mode_t mode; /**< Orientation and layout of scale. */
int32_t range_min; /**< Scale's minimum value */
int32_t range_max; /**< Scale's maximum value */
uint32_t total_tick_count : 15; /**< Total number of ticks (major and minor) */
uint32_t major_tick_every : 15; /**< Frequency of major ticks to minor ticks */
uint32_t label_enabled : 1; /**< Draw labels for major ticks? */
uint32_t post_draw : 1; /**< false: drawing occurs during LV_EVENT_DRAW_MAIN;
* true : drawing occurs during LV_EVENT_DRAW_POST. */
uint32_t draw_ticks_on_top : 1; /**< Draw ticks on top of main line? */
/* Round scale */
uint32_t angle_range;
int32_t rotation;
uint32_t angle_range; /**< Degrees between low end and high end of scale */
int32_t rotation; /**< Clockwise angular offset from 3-o'clock position of low end of scale */
/* Private properties */
int32_t custom_label_cnt;
int32_t last_tick_width;
int32_t first_tick_width;
int32_t custom_label_cnt; /**< Number of custom labels provided in `txt_src` */
int32_t last_tick_width; /**< Width of last tick in pixels */
int32_t first_tick_width; /**< Width of first tick in pixels */
};

View File

@@ -438,6 +438,44 @@ void test_scale_angle_range(void)
TEST_ASSERT_EQUAL(angle_range, lv_scale_get_angle_range(scale));
}
void test_scale_rotation(void)
{
lv_obj_t * scale = lv_scale_create(lv_screen_active());
lv_scale_set_rotation(scale, 135);
TEST_ASSERT_EQUAL(lv_scale_get_rotation(scale), 135);
lv_scale_set_rotation(scale, 375); /* 15 */
TEST_ASSERT_EQUAL(lv_scale_get_rotation(scale), 15);
lv_scale_set_rotation(scale, 540); /* 180 */
TEST_ASSERT_EQUAL(lv_scale_get_rotation(scale), 180);
lv_scale_set_rotation(scale, 1085); /* 5 */
TEST_ASSERT_EQUAL(lv_scale_get_rotation(scale), 5);
lv_scale_set_rotation(scale, -90); /* 270 */
TEST_ASSERT_EQUAL(lv_scale_get_rotation(scale), 270);
lv_scale_set_rotation(scale, -270); /* 90 */
TEST_ASSERT_EQUAL(lv_scale_get_rotation(scale), 90);
lv_scale_set_rotation(scale, -355); /* 5 */
TEST_ASSERT_EQUAL(lv_scale_get_rotation(scale), 5);
lv_scale_set_rotation(scale, -370); /* 350 */
TEST_ASSERT_EQUAL(lv_scale_get_rotation(scale), 350);
lv_scale_set_rotation(scale, -405); /* 315 */
TEST_ASSERT_EQUAL(lv_scale_get_rotation(scale), 315);
lv_scale_set_rotation(scale, -450); /* 270 */
TEST_ASSERT_EQUAL(lv_scale_get_rotation(scale), 270);
lv_scale_set_rotation(scale, -1075); /* 5 */
TEST_ASSERT_EQUAL(lv_scale_get_rotation(scale), 5);
}
void test_scale_range(void)
{
lv_obj_t * scale = lv_scale_create(lv_screen_active());