/** * \brief Set up calibration * * Allocates and initializes the application context; sets up the font, touch * event handler and calibration data; updates the display and then schedules * the calibration task. * * \param completed_task Task to schedule when calibration is complete */ void app_touch_calibrate_setup(struct workqueue_task *completed_task) { calibrate_context = membag_alloc(sizeof(struct touch_calibrate_context)); assert(calibrate_context != NULL); // Use twice as large font for this application. memcpy(&sysfont2x, &sysfont, sizeof(sysfont)); sysfont2x.scale = 2; // Temporarily replace touch event handler. calibrate_context->old_handler = touch_get_event_handler(); touch_set_event_handler(touch_calibrate_event_handler); // Clear the screen and draw the calibration guide text. gfx_set_clipping(0, 0, gfx_get_width(), gfx_get_height()); gfx_draw_filled_rect(0, 0, gfx_get_width(), gfx_get_height(), CAL_BG_COLOR); gfx_draw_progmem_string((const char __progmem_arg *) &calibrate_help_text, 10, 80, &sysfont2x, CAL_FG_COLOR, GFX_COLOR_TRANSPARENT); // Set panel coordinates for all calibration points. calibrate_context->cal_points[0].panel_x = (gfx_get_width() - CAL_OFFSET - 1); calibrate_context->cal_points[0].panel_y = (gfx_get_height() - CAL_OFFSET - 1); calibrate_context->cal_points[1].panel_x = (CAL_OFFSET); calibrate_context->cal_points[1].panel_y = (gfx_get_height() - CAL_OFFSET - 1); calibrate_context->cal_points[2].panel_x = (CAL_OFFSET); calibrate_context->cal_points[2].panel_y = (CAL_OFFSET); // Draw circle for first calibration point. gfx_draw_circle(calibrate_context->cal_points[0].panel_x, calibrate_context->cal_points[0].panel_y, CAL_RADIUS, CAL_FG_COLOR, GFX_WHOLE); // Initialize the calibration state and tasks before scheduling it. calibrate_context->state = 0; calibrate_context->completed_task = completed_task; workqueue_task_init(&calibrate_context->task, touch_calibrate_task_handler); workqueue_add_task(&main_workqueue, &calibrate_context->task); }
/** * \brief Application task worker * * Waits for the touch release events generated after the user has touched a * calibration circle drawn on screen, then stores the calibration data and * draws the circle for the next calibration point. * * Three such calibration points are stored before the calibration matrix for * the touch driver is computed and assigned to it. * * \sa touch_driver_group * * \note If the raw samples of a calibration point do not differ by at least * \ref CAL_TRESHOLD from the previous calibration point, it is interpreted as * an unintended touch and ignored. * * \param task Workqueue task for this worker function */ static void touch_calibrate_task_handler(struct workqueue_task *task) { int16_t dx; int16_t dy; struct touch_calibrate_context *cal_ctx; cal_ctx = container_of(task, struct touch_calibrate_context, task); switch (cal_ctx->state) { case 0: /* Fall through */ case 1: case 2: // Schedule task to run once more workqueue_add_task(&main_workqueue, &cal_ctx->task); // Run until touch is released if (cal_ctx->event.type != TOUCH_RELEASE) break; // Store calibration values cal_ctx->event.type = TOUCH_NO_EVENT; cal_ctx->cal_points[cal_ctx->state].raw_x = cal_ctx->event.point.raw_x; cal_ctx->cal_points[cal_ctx->state].raw_y = cal_ctx->event.point.raw_y; dx = cal_ctx->cal_points[cal_ctx->state - 1].raw_x - cal_ctx->cal_points[cal_ctx->state].raw_x; dy = cal_ctx->cal_points[cal_ctx->state - 1].raw_y - cal_ctx->cal_points[cal_ctx->state].raw_y; dx = abs(dx); dy = abs(dy); // If point is too close to the last one, wait for another touch if ((dx < CAL_TRESHOLD) && (dy < CAL_TRESHOLD)) break; // Clear old circle before moving on. gfx_draw_circle(cal_ctx->cal_points[cal_ctx->state].panel_x, cal_ctx->cal_points[cal_ctx->state].panel_y, CAL_RADIUS, CAL_BG_COLOR, GFX_WHOLE); // Move to next point cal_ctx->state++; /* Skip drawing last circles if we're done. */ if (cal_ctx->state >= 3) break; // Draw next circle. gfx_draw_circle(cal_ctx->cal_points[cal_ctx->state].panel_x, cal_ctx->cal_points[cal_ctx->state].panel_y, CAL_RADIUS, CAL_FG_COLOR, GFX_WHOLE); break; case 3: // Calibration completed // Clear circle before moving on. gfx_draw_circle(cal_ctx->cal_points[2].panel_x, cal_ctx->cal_points[2].panel_y, CAL_RADIUS, CAL_BG_COLOR, GFX_WHOLE); // Compute and assign the calibration matrix to the driver. touch_compute_calibration_matrix(cal_ctx->cal_points, &cal_ctx->cal_matrix); touch_set_calibration_matrix(&cal_ctx->cal_matrix); // Restore handler touch_set_event_handler(cal_ctx->old_handler); // Can now free memory membag_free(calibrate_context); // Schedule task if available if (calibrate_context->completed_task) { workqueue_add_task(&main_workqueue, calibrate_context->completed_task); } break; } }
/** * This function is the window event handler for radio button widgets. * It handles all events sent to the windows composing the widget. * * \param win Window receiving the event. * \param type The event type. * \param data Custom data, depending on event type. * * \return True if the event was recognized and accepted. */ static bool wtk_radio_button_handler(struct win_window *win, enum win_event_type type, void const *data) { struct win_command_event command; /* Custom data for windows of a widget points back to the widget itself. */ struct wtk_radio_button *radio_button = (struct wtk_radio_button *)win_get_custom_data(win); switch (type) { case WIN_EVENT_DRAW: { /* For DRAW events, the data parameter points to the * clipping region. */ struct win_clip_region const *clip = (struct win_clip_region const *)data; /* There should not be other windows in this widget. */ Assert(win == radio_button->container); /* Draw radio button circle. */ gfx_draw_circle(clip->origin.x + WTK_RADIOBUTTON_BUTTON_X, clip->origin.y + WTK_RADIOBUTTON_BUTTON_Y, WTK_RADIOBUTTON_RADIUS, WTK_RADIOBUTTON_BUTTON_COLOR, GFX_WHOLE); /* Draw radio button filled circle background. */ if (WTK_RADIOBUTTON_BACKGROUND_COLOR != GFX_COLOR_TRANSPARENT) { gfx_draw_filled_circle(clip->origin.x + WTK_RADIOBUTTON_BUTTON_X, clip->origin.y + WTK_RADIOBUTTON_BUTTON_Y, WTK_RADIOBUTTON_RADIUS - 1, WTK_RADIOBUTTON_BACKGROUND_COLOR, GFX_WHOLE); } /* Draw radio button select marker if selected. */ if (radio_button->group->selected == radio_button) { gfx_draw_filled_circle(clip->origin.x + WTK_RADIOBUTTON_BUTTON_X, clip->origin.y + WTK_RADIOBUTTON_BUTTON_Y, WTK_RADIOBUTTON_RADIUS - 2, WTK_RADIOBUTTON_SELECT_COLOR, GFX_WHOLE); } /* Draw caption. */ gfx_draw_string(radio_button->caption, clip->origin.x + WTK_RADIOBUTTON_CAPTION_X, clip->origin.y + WTK_RADIOBUTTON_CAPTION_Y, &sysfont, GFX_COLOR_TRANSPARENT, WTK_RADIOBUTTON_CAPTION_COLOR); /* Always accept DRAW events, as the return value is * ignored anyway for that event type. */ return true; } case WIN_EVENT_POINTER: { /* There should not be other windows in this widget. */ Assert(win == radio_button->container); /* For POINTER events, the data parameter points to the * pointer event information. */ struct win_pointer_event const *event = (struct win_pointer_event const *)data; switch (event->type) { case WIN_POINTER_PRESS: /* When radio button pressed, grab pointer and * wait for release inside widget borders. * Other widgets won't get pointer events * before it is released, and the pointer * ungrabbed by us. */ if (radio_button->state == WTK_RADIOBUTTON_NORMAL) { win_grab_pointer(radio_button-> container); radio_button->state = WTK_RADIOBUTTON_PRESSED; win_redraw(radio_button->container); } break; case WIN_POINTER_RELEASE: /* When button released, take action only if * released inside widget extents. */ if (radio_button->state == WTK_RADIOBUTTON_PRESSED) { bool is_inside; /* Ungrab pointer. */ win_grab_pointer(NULL); radio_button->state = WTK_RADIOBUTTON_NORMAL; win_redraw(radio_button->container); /* Check release position. */ is_inside = win_is_inside_window (radio_button-> container, &(event->pos)); /* Select this radio button if inside. */ if (is_inside) { wtk_radio_button_select (radio_button); /* Send non-zero command. */ if (radio_button->command) { command.sender = radio_button-> container; command.recipient = radio_button-> container; command.data = radio_button->command; win_queue_command_event (&command); } } } break; default: break; } /* Accept all POINTER events since all acitivity inside * the widget extents is relevant to us. */ return true; } case WIN_EVENT_DESTROY: /* There should not be other windows in this widget. */ Assert(win == radio_button->container); /* Memory allocated for windows will be automatically destroyed * by the window system. We must destroy other allocations. */ membag_free(radio_button->caption); /* Destroy radio group as well if we are the last one in the * group. If not, remove ourselves from the group. */ --(radio_button->group->num_references); if (!radio_button->group->num_references) { membag_free(radio_button->group); } else { if (radio_button->group->selected == radio_button) { radio_button->group->selected = NULL; } } membag_free(radio_button); /* Always accept DESTROY events, as the return value is ignored * anyway for that event type. */ return true; default: /* Reject unknown event types. */ return false; } }