mirror of
https://github.com/google/pebble.git
synced 2025-11-22 23:40:54 -05:00
Import of the watch repository from Pebble
This commit is contained in:
252
src/fw/applib/ui/dialogs/actionable_dialog.c
Normal file
252
src/fw/applib/ui/dialogs/actionable_dialog.c
Normal file
@@ -0,0 +1,252 @@
|
||||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "actionable_dialog.h"
|
||||
|
||||
#include "applib/applib_malloc.auto.h"
|
||||
#include "applib/fonts/fonts.h"
|
||||
#include "applib/ui/bitmap_layer.h"
|
||||
#include "applib/ui/dialogs/dialog.h"
|
||||
#include "applib/ui/dialogs/dialog_private.h"
|
||||
#include "applib/ui/kino/kino_reel/scale_segmented.h"
|
||||
#include "applib/ui/layer.h"
|
||||
#include "applib/ui/text_layer.h"
|
||||
#include "applib/ui/window.h"
|
||||
#include "kernel/ui/kernel_ui.h"
|
||||
#include "resource/resource_ids.auto.h"
|
||||
#include "system/passert.h"
|
||||
|
||||
#include <limits.h>
|
||||
#include <string.h>
|
||||
|
||||
static void prv_actionable_dialog_load(Window *window) {
|
||||
ActionableDialog *actionable_dialog = window_get_user_data(window);
|
||||
Dialog *dialog = &actionable_dialog->dialog;
|
||||
Layer *window_root_layer = window_get_root_layer(window);
|
||||
|
||||
// Ownership of icon is taken over by KinoLayer in dialog_init_icon_layer() call below
|
||||
KinoReel *icon = dialog_create_icon(dialog);
|
||||
const GSize icon_size = icon ? kino_reel_get_size(icon) : GSizeZero;
|
||||
|
||||
const GRect *bounds = &window_root_layer->bounds;
|
||||
const uint16_t icon_single_line_text_offset_px = 13;
|
||||
const uint16_t left_margin_px = PBL_IF_RECT_ELSE(5, 0);
|
||||
const uint16_t content_and_action_bar_horizontal_spacing = PBL_IF_RECT_ELSE(5, 7);
|
||||
const uint16_t right_margin_px = ACTION_BAR_WIDTH +
|
||||
content_and_action_bar_horizontal_spacing;
|
||||
const uint16_t text_single_line_text_offset_px = icon_single_line_text_offset_px - 1;
|
||||
const GFont dialog_text_font = fonts_get_system_font(FONT_KEY_GOTHIC_24_BOLD);
|
||||
const int single_line_text_height_px = fonts_get_font_height(dialog_text_font);
|
||||
const int max_text_line_height_px = 2 * single_line_text_height_px + 8;
|
||||
|
||||
const uint16_t status_layer_offset = dialog->show_status_layer ? 6 : 0;
|
||||
uint16_t text_top_margin_px = icon ? icon_size.h + 22 : 6;
|
||||
uint16_t icon_top_margin_px = 18;
|
||||
uint16_t x = 0;
|
||||
uint16_t y = 0;
|
||||
uint16_t w = PBL_IF_RECT_ELSE(bounds->size.w - ACTION_BAR_WIDTH, bounds->size.w);
|
||||
uint16_t h = STATUS_BAR_LAYER_HEIGHT;
|
||||
|
||||
if (dialog->show_status_layer) {
|
||||
dialog_add_status_bar_layer(dialog, &GRect(x, y, w, h));
|
||||
}
|
||||
|
||||
x = left_margin_px;
|
||||
w = bounds->size.w - left_margin_px - right_margin_px;
|
||||
|
||||
GTextAttributes *text_attributes = NULL;
|
||||
#if PBL_ROUND
|
||||
// Create a GTextAttributes for the TextLayer. Note that the matching
|
||||
// graphics_text_attributes_destroy() will not need to be called here, as the ownership
|
||||
// of text_attributes will be transferred to the TextLayer we assign it to.
|
||||
text_attributes = graphics_text_attributes_create();
|
||||
graphics_text_attributes_enable_screen_text_flow(text_attributes, 8);
|
||||
#endif
|
||||
// Check if the text takes up more than one line. If the dialog has a single line of text,
|
||||
// the icon and line of text are positioned lower so as to be more vertically centered.
|
||||
GContext *ctx = graphics_context_get_current_context();
|
||||
const GTextAlignment text_alignment = PBL_IF_RECT_ELSE(GTextAlignmentCenter, GTextAlignmentRight);
|
||||
{
|
||||
// do all this in a block so we enforce that nobody uses these variables outside of the block
|
||||
// when dealing with round displays, sizes change depending on location.
|
||||
const GRect probe_rect = GRect(x, y + text_single_line_text_offset_px,
|
||||
w, max_text_line_height_px);
|
||||
const uint16_t text_height = graphics_text_layout_get_max_used_size(ctx,
|
||||
dialog->buffer,
|
||||
dialog_text_font,
|
||||
probe_rect,
|
||||
GTextOverflowModeWordWrap,
|
||||
text_alignment,
|
||||
text_attributes).h;
|
||||
if (text_height <= single_line_text_height_px) {
|
||||
text_top_margin_px += text_single_line_text_offset_px;
|
||||
icon_top_margin_px += icon_single_line_text_offset_px;
|
||||
} else {
|
||||
text_top_margin_px += status_layer_offset;
|
||||
icon_top_margin_px += status_layer_offset;
|
||||
}
|
||||
}
|
||||
|
||||
y = text_top_margin_px;
|
||||
h = bounds->size.h - y;
|
||||
|
||||
// Set up the text.
|
||||
TextLayer *text_layer = &dialog->text_layer;
|
||||
text_layer_init_with_parameters(text_layer, &GRect(x, y, w, h),
|
||||
dialog->buffer, dialog_text_font,
|
||||
dialog->text_color, GColorClear, text_alignment,
|
||||
GTextOverflowModeWordWrap);
|
||||
if (text_attributes) {
|
||||
text_layer->should_cache_layout = true;
|
||||
text_layer->layout_cache = text_attributes;
|
||||
}
|
||||
|
||||
layer_add_child(&window->layer, &text_layer->layer);
|
||||
|
||||
// Action bar. If the user hasn't given a custom action bar, we'll create one of the preset
|
||||
// types.
|
||||
if (actionable_dialog->action_bar_type != DialogActionBarCustom) {
|
||||
actionable_dialog->action_bar = action_bar_layer_create();
|
||||
action_bar_layer_set_click_config_provider(actionable_dialog->action_bar,
|
||||
actionable_dialog->config_provider);
|
||||
}
|
||||
ActionBarLayer *action_bar = actionable_dialog->action_bar;
|
||||
if (actionable_dialog->action_bar_type == DialogActionBarConfirm) {
|
||||
#if !defined(RECOVERY_FW)
|
||||
actionable_dialog->select_icon = gbitmap_create_with_resource(
|
||||
RESOURCE_ID_ACTION_BAR_ICON_CHECK);
|
||||
#endif
|
||||
action_bar_layer_set_context(action_bar, window);
|
||||
action_bar_layer_set_icon(action_bar, BUTTON_ID_SELECT, actionable_dialog->select_icon);
|
||||
} else if (actionable_dialog->action_bar_type == DialogActionBarDecline) {
|
||||
#if !defined(RECOVERY_FW)
|
||||
actionable_dialog->select_icon = gbitmap_create_with_resource(RESOURCE_ID_ACTION_BAR_ICON_X);
|
||||
#endif
|
||||
action_bar_layer_set_context(action_bar, window);
|
||||
action_bar_layer_set_icon(action_bar, BUTTON_ID_SELECT, actionable_dialog->select_icon);
|
||||
} else if (actionable_dialog->action_bar_type == DialogActionBarConfirmDecline) {
|
||||
#if !defined(RECOVERY_FW)
|
||||
actionable_dialog->up_icon = gbitmap_create_with_resource(RESOURCE_ID_ACTION_BAR_ICON_CHECK);
|
||||
actionable_dialog->down_icon = gbitmap_create_with_resource(RESOURCE_ID_ACTION_BAR_ICON_X);
|
||||
#endif
|
||||
action_bar_layer_set_icon(action_bar, BUTTON_ID_UP, actionable_dialog->up_icon);
|
||||
action_bar_layer_set_icon(action_bar, BUTTON_ID_DOWN, actionable_dialog->down_icon);
|
||||
action_bar_layer_set_context(action_bar, window);
|
||||
}
|
||||
action_bar_layer_add_to_window(action_bar, window);
|
||||
|
||||
// Icon
|
||||
// On rectangular displays we just center it horizontally b/w the left edge of the display and
|
||||
// the left edge of the action bar
|
||||
#if PBL_RECT
|
||||
x = (grect_get_max_x(bounds) - ACTION_BAR_WIDTH - icon_size.w) / 2;
|
||||
#else
|
||||
// On round displays we right align it with respect to the same imaginary vertical line that the
|
||||
// text is right aligned to
|
||||
x = grect_get_max_x(bounds) - ACTION_BAR_WIDTH - content_and_action_bar_horizontal_spacing -
|
||||
icon_size.w;
|
||||
#endif
|
||||
|
||||
y = icon_top_margin_px;
|
||||
|
||||
if (dialog_init_icon_layer(dialog, icon, GPoint(x, y), true)) {
|
||||
layer_add_child(window_root_layer, &dialog->icon_layer.layer);
|
||||
}
|
||||
|
||||
dialog_load(&actionable_dialog->dialog);
|
||||
}
|
||||
|
||||
static void prv_actionable_dialog_appear(Window *window) {
|
||||
ActionableDialog *actionable_dialog = window_get_user_data(window);
|
||||
Dialog *dialog = actionable_dialog_get_dialog(actionable_dialog);
|
||||
dialog_appear(dialog);
|
||||
}
|
||||
|
||||
static void prv_actionable_dialog_unload(Window *window) {
|
||||
ActionableDialog *actionable_dialog = window_get_user_data(window);
|
||||
dialog_unload(&actionable_dialog->dialog);
|
||||
|
||||
// Destroy action bar if it was a predefined type. If the action bar is custom, it is the user's
|
||||
// responsibility to free it.
|
||||
if (actionable_dialog->action_bar_type != DialogActionBarCustom) {
|
||||
action_bar_layer_destroy(actionable_dialog->action_bar);
|
||||
if (actionable_dialog->action_bar_type == DialogActionBarConfirmDecline) {
|
||||
gbitmap_destroy(actionable_dialog->up_icon);
|
||||
gbitmap_destroy(actionable_dialog->down_icon);
|
||||
} else { // DialogActionBarConfirm || DialogActionBarDecline
|
||||
gbitmap_destroy(actionable_dialog->select_icon);
|
||||
}
|
||||
}
|
||||
if (actionable_dialog->dialog.destroy_on_pop) {
|
||||
applib_free(actionable_dialog);
|
||||
}
|
||||
}
|
||||
|
||||
Dialog *actionable_dialog_get_dialog(ActionableDialog *actionable_dialog) {
|
||||
return &actionable_dialog->dialog;
|
||||
}
|
||||
|
||||
void actionable_dialog_push(ActionableDialog *actionable_dialog, WindowStack *window_stack) {
|
||||
dialog_push(&actionable_dialog->dialog, window_stack);
|
||||
}
|
||||
|
||||
void app_actionable_dialog_push(ActionableDialog *actionable_dialog) {
|
||||
app_dialog_push(&actionable_dialog->dialog);
|
||||
}
|
||||
|
||||
void actionable_dialog_pop(ActionableDialog *actionable_dialog) {
|
||||
dialog_pop(&actionable_dialog->dialog);
|
||||
}
|
||||
|
||||
void actionable_dialog_init(ActionableDialog *actionable_dialog, const char *dialog_name) {
|
||||
PBL_ASSERTN(actionable_dialog);
|
||||
*actionable_dialog = (ActionableDialog){};
|
||||
|
||||
dialog_init(&actionable_dialog->dialog, dialog_name);
|
||||
Window *window = &actionable_dialog->dialog.window;
|
||||
window_set_window_handlers(window, &(WindowHandlers) {
|
||||
.load = prv_actionable_dialog_load,
|
||||
.unload = prv_actionable_dialog_unload,
|
||||
.appear = prv_actionable_dialog_appear,
|
||||
});
|
||||
window_set_user_data(window, actionable_dialog);
|
||||
}
|
||||
|
||||
ActionableDialog *actionable_dialog_create(const char *dialog_name) {
|
||||
// Note: Not exported so no need for padding.
|
||||
ActionableDialog *actionable_dialog = applib_malloc(sizeof(ActionableDialog));
|
||||
if (actionable_dialog) {
|
||||
actionable_dialog_init(actionable_dialog, dialog_name);
|
||||
}
|
||||
return actionable_dialog;
|
||||
}
|
||||
|
||||
void actionable_dialog_set_action_bar_type(ActionableDialog *actionable_dialog,
|
||||
DialogActionBarType action_bar_type,
|
||||
ActionBarLayer *action_bar) {
|
||||
if (action_bar_type == DialogActionBarCustom) {
|
||||
PBL_ASSERTN(action_bar); // Action bar must not be NULL if it is a custom type.
|
||||
actionable_dialog->action_bar = action_bar;
|
||||
} else {
|
||||
actionable_dialog->action_bar = NULL;
|
||||
}
|
||||
actionable_dialog->action_bar_type = action_bar_type;
|
||||
}
|
||||
|
||||
void actionable_dialog_set_click_config_provider(ActionableDialog *actionable_dialog,
|
||||
ClickConfigProvider click_config_provider) {
|
||||
actionable_dialog->config_provider = click_config_provider;
|
||||
}
|
||||
Reference in New Issue
Block a user