330 lines
9.8 KiB
C
330 lines
9.8 KiB
C
/* source/widgets/sgl_dropdown.c
|
|
*
|
|
* MIT License
|
|
*
|
|
* Copyright(c) 2023-present All contributors of SGL
|
|
* Document reference link: https://sgl-docs.readthedocs.io
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
* of this software and associated documentation files (the "Software"), to deal
|
|
* in the Software without restriction, including without limitation the rights
|
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
* copies of the Software, and to permit persons to whom the Software is
|
|
* furnished to do so, subject to the following conditions:
|
|
* The above copyright notice and this permission notice shall be included in all
|
|
* copies or substantial portions of the Software.
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
* SOFTWARE.
|
|
*/
|
|
|
|
#include <sgl_core.h>
|
|
#include <sgl_draw.h>
|
|
#include <sgl_math.h>
|
|
#include <sgl_log.h>
|
|
#include <sgl_mm.h>
|
|
#include <sgl_cfgfix.h>
|
|
#include <sgl_theme.h>
|
|
#include "sgl_dropdown.h"
|
|
|
|
static const uint8_t dropdown_bitmap[] = {
|
|
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x30,0x00,
|
|
0x0c,0xfa,0x00,0x00,0x00,0x00,0x05,0xee,0x50,
|
|
0x0c,0xff,0xa0,0x00,0x00,0x00,0x5e,0xfe,0x30,
|
|
0x00,0xcf,0xfa,0x00,0x00,0x05,0xef,0xe3,0x00,
|
|
0x00,0x0c,0xff,0xa0,0x00,0x5e,0xfe,0x30,0x00,
|
|
0x00,0x00,0xcf,0xfa,0x05,0xef,0xe3,0x00,0x00,
|
|
0x00,0x00,0x0c,0xff,0xae,0xfe,0x30,0x00,0x00,
|
|
0x00,0x00,0x00,0xcf,0xff,0xe3,0x00,0x00,0x00,
|
|
0x00,0x00,0x00,0x0c,0xfe,0x30,0x00,0x00,0x00,
|
|
0x00,0x00,0x00,0x00,0x53,0x00,0x00,0x00,0x00,
|
|
};
|
|
|
|
|
|
static const sgl_icon_pixmap_t dropdown_icon = {
|
|
.bitmap = dropdown_bitmap,
|
|
.height = 10,
|
|
.width = 18,
|
|
};
|
|
|
|
|
|
static void sgl_dropdown_construct_cb(sgl_surf_t *surf, sgl_obj_t* obj, sgl_event_t *evt)
|
|
{
|
|
sgl_dropdown_t *dropdown = sgl_container_of(obj, sgl_dropdown_t, obj);
|
|
int16_t icon_h = dropdown->option_h;
|
|
int16_t pos_x = 0, pos_y = 0, icon_y = 0;
|
|
sgl_dropdown_option_t *option = dropdown->head;
|
|
|
|
sgl_rect_t body_area = {
|
|
.x1 = obj->coords.x1,
|
|
.x2 = obj->coords.x2,
|
|
.y1 = obj->coords.y1,
|
|
.y2 = obj->coords.y1 + dropdown->option_h,
|
|
};
|
|
|
|
sgl_rect_t icon_area = {
|
|
.x1 = obj->coords.x2 - dropdown_icon.width - dropdown->body_desc.radius,
|
|
.x2 = obj->coords.x2 - 2,
|
|
.y1 = obj->coords.y1,
|
|
.y2 = obj->coords.y2,
|
|
};
|
|
|
|
sgl_rect_t text_area = {
|
|
.x1 = obj->coords.x1 + 2,
|
|
.x2 = obj->coords.x2 - dropdown_icon.width - 5,
|
|
.y1 = obj->coords.y1,
|
|
.y2 = obj->coords.y2,
|
|
};
|
|
|
|
icon_y = obj->coords.y1 + (icon_h - dropdown_icon.height) / 2;
|
|
|
|
if (evt->type == SGL_EVENT_DRAW_MAIN) {
|
|
sgl_draw_rect(surf, &obj->area, &body_area, &dropdown->body_desc);
|
|
if (dropdown->is_open) {
|
|
sgl_draw_icon(surf, &obj->area, icon_area.x1, icon_y + 2, dropdown->text_color, dropdown->body_desc.alpha, &dropdown_icon);
|
|
}
|
|
else {
|
|
sgl_draw_icon(surf, &obj->area, icon_area.x1, icon_y, dropdown->text_color, dropdown->body_desc.alpha, &dropdown_icon);
|
|
}
|
|
|
|
for (int i = 0; option != NULL; i++) {
|
|
if (i == dropdown->selected) {
|
|
break;
|
|
}
|
|
option = option->next;
|
|
}
|
|
|
|
if (option != NULL) {
|
|
pos_x = text_area.x1 + dropdown->body_desc.radius;
|
|
pos_y = obj->coords.y1 + (icon_h - dropdown->font->font_height) / 2;
|
|
sgl_draw_string(surf, &text_area, pos_x, pos_y, option->text, dropdown->text_color, dropdown->body_desc.alpha, dropdown->font);
|
|
}
|
|
|
|
if (dropdown->is_open) {
|
|
pos_y = body_area.y2;
|
|
option = dropdown->expend_start;
|
|
body_area.y1 = obj->coords.y1 + dropdown->option_h;
|
|
body_area.y2 = body_area.y1 + dropdown->expand_h - 1;
|
|
sgl_draw_rect(surf, &obj->area, &body_area, &dropdown->body_desc);
|
|
|
|
for (int i = 0; option != NULL; i++) {
|
|
pos_x = text_area.x1 + dropdown->body_desc.radius;
|
|
sgl_draw_string(surf, &obj->area, pos_x, pos_y, option->text, dropdown->text_color, dropdown->body_desc.alpha, dropdown->font);
|
|
option = option->next;
|
|
pos_y += dropdown->font->font_height;
|
|
}
|
|
}
|
|
}
|
|
else if (evt->type == SGL_EVENT_MOVE_UP) {
|
|
|
|
}
|
|
else if (evt->type == SGL_EVENT_MOVE_DOWN) {
|
|
|
|
}
|
|
else if (evt->type == SGL_EVENT_PRESSED || evt->type == SGL_EVENT_CLICKED) {
|
|
dropdown->expand_h = dropdown->font->font_height * sgl_min(10, dropdown->option_num);
|
|
|
|
if (dropdown->is_open) {
|
|
dropdown->is_open = false;
|
|
obj->coords.y2 -= dropdown->expand_h;
|
|
SGL_LOG_INFO("sgl_dropdown_construct_cb: selected %d %d %d", evt->pos.y, obj->coords.y2, dropdown->font->font_height);
|
|
if (evt->pos.y > obj->coords.y2) {
|
|
dropdown->selected = (evt->pos.y - obj->coords.y2) / dropdown->font->font_height;
|
|
}
|
|
}
|
|
else {
|
|
dropdown->is_open = true;
|
|
obj->coords.y2 += dropdown->expand_h;
|
|
}
|
|
|
|
sgl_obj_set_dirty(obj);
|
|
dropdown->expend_start = dropdown->head;
|
|
}
|
|
else if (evt->type == SGL_EVENT_DRAW_INIT) {
|
|
dropdown->option_h = obj->coords.y2 - obj->coords.y1;
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* @brief create a dropdown object
|
|
* @param parent parent of the dropdown
|
|
* @return pointer to the dropdown object
|
|
*/
|
|
sgl_obj_t* sgl_dropdown_create(sgl_obj_t* parent)
|
|
{
|
|
sgl_dropdown_t *dropdown = sgl_malloc(sizeof(sgl_dropdown_t));
|
|
if(dropdown == NULL) {
|
|
SGL_LOG_ERROR("sgl_dropdown_create: malloc failed");
|
|
return NULL;
|
|
}
|
|
|
|
/* set object all member to zero */
|
|
memset(dropdown, 0, sizeof(sgl_dropdown_t));
|
|
|
|
sgl_obj_t *obj = &dropdown->obj;
|
|
sgl_obj_init(&dropdown->obj, parent);
|
|
obj->construct_fn = sgl_dropdown_construct_cb;
|
|
obj->needinit = 1;
|
|
sgl_obj_set_border_width(obj, SGL_THEME_BORDER_WIDTH);
|
|
|
|
sgl_obj_set_clickable(obj);
|
|
sgl_obj_set_movable(obj);
|
|
|
|
dropdown->body_desc.alpha = SGL_THEME_ALPHA;
|
|
dropdown->body_desc.color = SGL_THEME_COLOR;
|
|
dropdown->body_desc.radius = 5;
|
|
dropdown->body_desc.border = 1;
|
|
dropdown->body_desc.border_color = SGL_THEME_BORDER_COLOR;
|
|
|
|
dropdown->font = sgl_get_system_font();
|
|
dropdown->head = NULL;
|
|
dropdown->is_open = false;
|
|
|
|
dropdown->selected = -1;
|
|
dropdown->clicked = 0;
|
|
|
|
return obj;
|
|
}
|
|
|
|
/**
|
|
* @brief get the selected text of the dropdown
|
|
* @param obj pointer to the dropdown object
|
|
* @return pointer to the selected text
|
|
*/
|
|
const char *sgl_dropdown_get_selected_text(sgl_obj_t *obj)
|
|
{
|
|
sgl_dropdown_t *dropdown = (sgl_dropdown_t*)obj;
|
|
sgl_dropdown_option_t *option = dropdown->head;
|
|
|
|
for (int i = 0; i < dropdown->option_num; i++) {
|
|
if (i == dropdown->selected) {
|
|
return option->text;
|
|
}
|
|
option = option->next;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* @brief add an option to the dropdown
|
|
* @param obj pointer to the dropdown object
|
|
* @param text pointer to the text
|
|
* @return none
|
|
*/
|
|
void sgl_dropdown_add_option(sgl_obj_t *obj, const char *text)
|
|
{
|
|
sgl_dropdown_t *dropdown = (sgl_dropdown_t*)obj;
|
|
sgl_dropdown_option_t *tail = dropdown->head;
|
|
sgl_dropdown_option_t *add = sgl_malloc(sizeof(sgl_dropdown_option_t));
|
|
|
|
if (add == NULL) {
|
|
SGL_LOG_ERROR("sgl_dropdown_add_option: malloc failed");
|
|
return;
|
|
}
|
|
|
|
if (tail == NULL) {
|
|
dropdown->head = add;
|
|
}
|
|
else {
|
|
while (tail != NULL) {
|
|
if (tail->next == NULL) {
|
|
tail->next = add;
|
|
break;
|
|
}
|
|
tail = (tail)->next;
|
|
}
|
|
}
|
|
|
|
dropdown->option_num ++;
|
|
add->text = text;
|
|
add->next = NULL;
|
|
|
|
if (dropdown->selected == -1) {
|
|
dropdown->selected = 0;
|
|
}
|
|
|
|
sgl_obj_set_dirty(obj);
|
|
}
|
|
|
|
/**
|
|
* @brief delete an option from the dropdown
|
|
* @param obj pointer to the dropdown object
|
|
* @param text pointer to the text
|
|
* @return none
|
|
*/
|
|
void sgl_dropdown_delete_option_by_text(sgl_obj_t *obj, const char *text)
|
|
{
|
|
sgl_dropdown_t *dropdown = (sgl_dropdown_t*)obj;
|
|
sgl_dropdown_option_t *curr = dropdown->head;
|
|
sgl_dropdown_option_t *temp = dropdown->head;
|
|
sgl_dropdown_option_t *prev = NULL;
|
|
int deleted_index = 0;
|
|
|
|
if (obj == NULL || text == NULL) {
|
|
return;
|
|
}
|
|
|
|
while (curr != NULL) {
|
|
if (curr->text && strcmp(curr->text, text) == 0) {
|
|
break;
|
|
}
|
|
prev = curr;
|
|
curr = curr->next;
|
|
}
|
|
|
|
if (curr == NULL) {
|
|
return;
|
|
}
|
|
|
|
while (temp != curr) {
|
|
temp = temp->next;
|
|
deleted_index ++;
|
|
}
|
|
|
|
if (prev == NULL) {
|
|
dropdown->head = curr->next;
|
|
} else {
|
|
prev->next = curr->next;
|
|
}
|
|
|
|
sgl_free(curr);
|
|
dropdown->option_num--;
|
|
sgl_obj_set_dirty(obj);
|
|
}
|
|
|
|
/**
|
|
* @brief delete an option from the dropdown
|
|
* @param obj pointer to the dropdown object
|
|
* @param index index of the option
|
|
* @return none
|
|
*/
|
|
void sgl_dropdown_delete_option_by_index(sgl_obj_t *obj, int index)
|
|
{
|
|
sgl_dropdown_t *dropdown = (sgl_dropdown_t*)obj;
|
|
sgl_dropdown_option_t *curr = dropdown->head;
|
|
sgl_dropdown_option_t *prev = NULL;
|
|
|
|
if (obj == NULL || index < 0 || index >= dropdown->option_num) {
|
|
return;
|
|
}
|
|
|
|
for (int i = 0; i < index; i++) {
|
|
prev = curr;
|
|
curr = curr->next;
|
|
}
|
|
|
|
if (prev == NULL) {
|
|
dropdown->head = curr->next;
|
|
} else {
|
|
prev->next = curr->next;
|
|
}
|
|
|
|
sgl_free(curr);
|
|
dropdown->option_num--;
|
|
}
|