/* * Copyright (C) 2020 Alexandros Theodotou * * This file is part of ZToolkit * * ZToolkit is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * ZToolkit is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU General Affero Public License * along with ZToolkit. If not, see . */ #include #include #include "ztoolkit/ztk.h" /** Height of the separator, excluding padding. */ #define SEPARATOR_HEIGHT 2 /** Padding to leave left/right/bot/top of the * text. */ #define PADDING 2 static double get_height ( ZtkComboBox * self) { double height = PADDING * 2; for (int i = 0; i < self->num_elements; i++) { ZtkComboBoxElement * el = &self->elements[i]; if (el->is_separator) { height += SEPARATOR_HEIGHT + PADDING * 2; } else { /* the app is not set until the widget gets * added to the app */ ZtkWidget * widget = (ZtkWidget *) self; if (!widget->app) continue; cairo_t* cr = (cairo_t*) puglGetContext ( widget->app->view); cairo_text_extents_t extents; cairo_set_font_size (cr, self->font_size); cairo_text_extents (cr, el->label, &extents); height += (int) extents.height + PADDING * 2; } } return height; } static double get_width ( ZtkComboBox * self) { double width = 12; for (int i = 0; i < self->num_elements; i++) { ZtkComboBoxElement * el = &self->elements[i]; if (el->is_separator) continue; /* the app is not set until the widget gets * added to the app */ ZtkWidget * widget = (ZtkWidget *) self; if (!widget->app) continue; cairo_t* cr = (cairo_t*) puglGetContext ( widget->app->view); cairo_text_extents_t extents; cairo_set_font_size (cr, self->font_size); cairo_text_extents (cr, el->label, &extents); width = /* *2 for the element, *2 for the frame */ MAX (width, extents.width + PADDING * 4); } return width; } static void get_dimensions ( ZtkComboBox * self, ZtkRect * rect) { double height = get_height (self); double width = get_width (self); rect->width = width; rect->height = height; if (self->upwards) { if (self->backwards) { rect->x = (self->parent->rect.x + self->parent->rect.width) - width; rect->y = self->parent->rect.y - height; } else { rect->x = self->parent->rect.x; rect->y = self->parent->rect.y - height; } } else /* downwards */ { if (self->backwards) { rect->x = (self->parent->rect.x + self->parent->rect.width) - width; rect->y = self->parent->rect.y + self->parent->rect.height; } else { rect->x = self->parent->rect.x; rect->y = self->parent->rect.y + self->parent->rect.height; } } } static void draw_cb ( ZtkWidget * w, cairo_t * cr, ZtkRect * draw_rect, void * data) { ZtkComboBox * self = (ZtkComboBox *) w; ZtkRect rect; get_dimensions (self, &rect); /* draw bg and frame */ ztk_color_set_for_cairo ( &self->frame_color, cr); cairo_rectangle ( cr, rect.x, rect.y, rect.width, rect.height); cairo_fill (cr); ztk_color_set_for_cairo ( &self->bg_color, cr); cairo_rectangle ( cr, rect.x + PADDING, rect.y + PADDING, rect.width - PADDING * 2, rect.height - PADDING * 2); cairo_fill (cr); double height = rect.y + PADDING; double next_height; /* find element hit */ for (int i = 0; i < self->num_elements; i++) { ZtkComboBoxElement * el = &self->elements[i]; if (el->is_separator) { next_height = height + SEPARATOR_HEIGHT + PADDING * 2; /* draw the element bg */ if (self->hovered_idx == i) { if (w->state & ZTK_WIDGET_STATE_PRESSED) { ztk_color_set_for_cairo ( &self->click_color, cr); } else { ztk_color_set_for_cairo ( &self->hover_color, cr); } cairo_rectangle ( cr, rect.x + PADDING, height, rect.width - PADDING * 2, next_height - height); cairo_fill (cr); } /* draw the separator */ ztk_color_set_for_cairo ( &self->separator_color, cr); cairo_rectangle ( cr, rect.x + PADDING * 2, height + PADDING, rect.width - PADDING * 4, SEPARATOR_HEIGHT); cairo_fill (cr); height = next_height; } else { cairo_text_extents_t extents; cairo_set_font_size (cr, self->font_size); cairo_text_extents ( cr, el->label, &extents); next_height = height + extents.height + PADDING * 2; /* draw the element bg */ if (self->hovered_idx == i) { if (w->state & ZTK_WIDGET_STATE_PRESSED) { ztk_color_set_for_cairo ( &self->click_color, cr); } else { ztk_color_set_for_cairo ( &self->hover_color, cr); } cairo_rectangle ( cr, rect.x + PADDING, height, rect.width - PADDING * 2, next_height - height); cairo_fill (cr); } /* draw the text */ if (self->hovered_idx == i) { if (w->state & ZTK_WIDGET_STATE_PRESSED) { ztk_color_set_for_cairo ( &self->text_click_color, cr); } else { ztk_color_set_for_cairo ( &self->text_hover_color, cr); } } else { ztk_color_set_for_cairo ( &self->text_normal_color, cr); } cairo_move_to ( cr, rect.x + PADDING * 2, height + PADDING + extents.height); cairo_show_text ( cr, el->label); height = next_height; } } } static void update_cb ( ZtkWidget * w, void * data) { } static int motion_cb ( ZtkWidget * widget, const PuglEventMotion * event, void * data) { /* set hovered */ ZtkComboBox * self = (ZtkComboBox *) widget; double y = event->y; double height = widget->rect.y + PADDING; double next_height; /* find element hit */ for (int i = 0; i < self->num_elements; i++) { ZtkComboBoxElement * el = &self->elements[i]; if (el->is_separator) { next_height = height + SEPARATOR_HEIGHT + PADDING * 2; if (y >= height && y < next_height) { self->hovered_idx = i; return 0; } height = next_height; } else { cairo_t* cr = (cairo_t*) puglGetContext ( ((ZtkWidget *) self)->app->view); cairo_text_extents_t extents; cairo_set_font_size ( cr, self->font_size); cairo_text_extents ( cr, el->label, &extents); next_height = height + extents.height + PADDING * 2; if (y >= height && y < next_height) { self->hovered_idx = i; return 0; } height = next_height; } } self->hovered_idx = -1; return 0; } static void free_cb ( ZtkWidget * w, void * data) { ZtkComboBox * self = (ZtkComboBox *) w; free (self); } static int button_event_cb ( ZtkWidget * widget, const PuglEventButton * btn, void * data) { ZtkComboBox * self = (ZtkComboBox *) widget; const PuglEvent * ev = (const PuglEvent *) btn; if (ztk_widget_is_hit (widget, btn->x, btn->y)) { /* skip if already removed */ if (!ztk_app_contains_widget ( widget->app, widget)) return 0; /* skip if not release or no valid hover */ if (ev->type != PUGL_BUTTON_RELEASE || self->hovered_idx < 0) return 0; ZtkComboBoxElement * el = &self->elements[self->hovered_idx]; if (!el->is_separator) { /* activate */ el->activate_cb ( widget, el->activate_cb_data); /* remove from app to hide the * combobox */ ztk_app_remove_widget ( widget->app, widget); return 0; } } /* clicked outside */ else { /* remove widget */ ztk_app_remove_widget ( widget->app, widget); } return 0; } /** * Initializes the defaults. */ static void ztk_combo_box_init ( ZtkComboBox * self) { strcpy (self->font_name, "Cantarrel"); self->font_size = 12.0; ztk_color_parse_hex ( &self->text_normal_color, "#DDDDDD"); ztk_color_parse_hex ( &self->text_hover_color, "#EEEEEE"); ztk_color_parse_hex ( &self->text_click_color, "#FFFFFF"); ztk_color_parse_hex ( &self->bg_color, "#323232"); ztk_color_parse_hex ( &self->frame_color, "#646464"); ztk_color_parse_hex ( &self->separator_color, "#AAAAAA"); ztk_color_parse_hex ( &self->hover_color, "#646464"); ztk_color_parse_hex ( &self->click_color, "#868686"); } /** * Creates a new combobox. * * @param parent The parent widget to spawn on. * @param spawn_upwards Spawn upwards instead of * downards. */ ZtkComboBox * ztk_combo_box_new ( ZtkWidget * parent, int spawn_upwards, int spawn_backwards) { ZtkComboBox * self = calloc (1, sizeof (ZtkComboBox)); ZtkRect rect = { 0, 0, 0, 0 }; ztk_widget_init ( (ZtkWidget *) self, ZTK_WIDGET_TYPE_COMBO_BOX, &rect, update_cb, draw_cb, free_cb); ZtkWidget * widget = (ZtkWidget *) self; /* catch button events */ widget->button_event_cb = button_event_cb; widget->motion_event_cb = motion_cb; self->parent = parent; self->upwards = spawn_upwards; self->backwards = spawn_backwards; ztk_combo_box_init (self); self->hovered_idx = -1; /* update dimensions */ get_dimensions (self, &widget->rect); return self; } /** * @param data Data related to the current element * to pass to the activate callback. */ void ztk_combo_box_add_text_element ( ZtkComboBox * self, const char * label, ZtkWidgetActivateCallback activate_cb, void * data) { ZtkComboBoxElement * el = &self->elements[self->num_elements++]; strcpy (el->label, label); el->is_separator = 0; el->activate_cb = activate_cb; el->activate_cb_data = data; /* update dimensions */ ZtkWidget * widget = (ZtkWidget *) self; get_dimensions (self, &widget->rect); } void ztk_combo_box_add_separator ( ZtkComboBox * self) { ZtkComboBoxElement * el = &self->elements[self->num_elements++]; el->is_separator = 1; /* update dimensions */ ZtkWidget * widget = (ZtkWidget *) self; get_dimensions (self, &widget->rect); } void ztk_combo_box_clear ( ZtkComboBox * self) { self->num_elements = 0; }