Files
pebble/src/fw/applib/ui/dialogs/simple_dialog.c
Josh Soref 0ad05d3c3d spelling: dismissed
Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>
2025-01-28 21:32:35 -05:00

228 lines
8.8 KiB
C

/*
* 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 "simple_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/layer.h"
#include "applib/ui/text_layer.h"
#include "applib/ui/window.h"
#include "kernel/ui/kernel_ui.h"
#include "system/passert.h"
#include <limits.h>
#include <string.h>
#if (RECOVERY_FW || UNITTEST)
#define SIMPLE_DIALOG_ANIMATED false
#else
#define SIMPLE_DIALOG_ANIMATED true
#endif
// Layout Defines
#define TEXT_ALIGNMENT (GTextAlignmentCenter)
#define TEXT_OVERFLOW (GTextOverflowModeWordWrap)
#define TEXT_FONT (fonts_get_system_font(FONT_KEY_GOTHIC_24_BOLD))
#define TEXT_LEFT_MARGIN_PX (PBL_IF_RECT_ELSE(6, 0))
#define TEXT_RIGHT_MARGIN_PX (PBL_IF_RECT_ELSE(6, 0))
#define TEXT_FLOW_INSET_PX (PBL_IF_RECT_ELSE(0, 8))
#define TEXT_LINE_HEIGHT_PX (fonts_get_font_height(TEXT_FONT))
#define TEXT_MAX_HEIGHT_PX ((2 * TEXT_LINE_HEIGHT_PX) + 8) // 2 line + some space for descenders
static int prv_get_rendered_text_height(const char *text, const GRect *text_box) {
GContext *ctx = graphics_context_get_current_context();
TextLayoutExtended layout = { 0 };
graphics_text_attributes_enable_screen_text_flow((GTextLayoutCacheRef) &layout,
TEXT_FLOW_INSET_PX);
return graphics_text_layout_get_max_used_size(ctx,
text,
TEXT_FONT,
*text_box,
TEXT_OVERFLOW,
TEXT_ALIGNMENT,
(GTextLayoutCacheRef) &layout).h;
}
static int prv_get_icon_top_margin(bool has_status_bar, int icon_height, int window_height) {
const uint16_t status_layer_offset = has_status_bar ? 6 : 0;
#if PLATFORM_ROBERT || PLATFORM_CALCULUS
const uint16_t icon_top_default_margin_px = 42 + status_layer_offset;
#else
const uint16_t icon_top_default_margin_px = 18 + status_layer_offset;
#endif
const uint16_t frame_height_claimed = icon_height + TEXT_MAX_HEIGHT_PX + status_layer_offset;
const uint16_t icon_top_adjusted_margin_px = MAX(window_height - frame_height_claimed, 0);
// Try and use the default value if possible.
return (icon_top_adjusted_margin_px < icon_top_default_margin_px) ? icon_top_adjusted_margin_px :
icon_top_default_margin_px;
}
static void prv_get_text_box(GSize frame_size, GSize icon_size,
int icon_top_margin_px, GRect *text_box_out) {
const uint16_t icon_text_spacing_px = PBL_IF_ROUND_ELSE(2, 4);
const uint16_t text_x = TEXT_LEFT_MARGIN_PX;
const uint16_t text_y = icon_top_margin_px + MAX(icon_size.h, 6) + icon_text_spacing_px;
const uint16_t text_w = frame_size.w - TEXT_LEFT_MARGIN_PX - TEXT_RIGHT_MARGIN_PX;
// Limit to 2 lines if there is an icon
const uint16_t text_h = icon_size.h ? TEXT_MAX_HEIGHT_PX : frame_size.h - text_y;
*text_box_out = GRect(text_x, text_y, text_w, text_h);
}
static void prv_simple_dialog_load(Window *window) {
SimpleDialog *simple_dialog = window_get_user_data(window);
Dialog *dialog = &simple_dialog->dialog;
// 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;
GRect frame = window->layer.bounds;
// Status Layer
if (dialog->show_status_layer) {
dialog_add_status_bar_layer(dialog, &GRect(0, 0, frame.size.w, STATUS_BAR_LAYER_HEIGHT));
}
uint16_t icon_top_margin_px = prv_get_icon_top_margin(dialog->show_status_layer,
icon_size.h, frame.size.h);
// Text
GRect text_box;
prv_get_text_box(frame.size, icon_size, icon_top_margin_px, &text_box);
const uint16_t text_height = prv_get_rendered_text_height(dialog->buffer, &text_box);
if (text_height <= TEXT_LINE_HEIGHT_PX) {
const int additional_icon_top_offset_for_single_line_text_px = 13;
// Move the icon down by increasing the margin to vertically center things
icon_top_margin_px += additional_icon_top_offset_for_single_line_text_px;
// Move the text down as well to preserve spacing
// The -1 is there to preserve prior functionality ¯\_(ツ)_/¯
text_box.origin.y += additional_icon_top_offset_for_single_line_text_px - 1;
}
TextLayer *text_layer = &dialog->text_layer;
text_layer_init_with_parameters(text_layer, &text_box, dialog->buffer, TEXT_FONT,
dialog->text_color, GColorClear, TEXT_ALIGNMENT, TEXT_OVERFLOW);
layer_add_child(&window->layer, &text_layer->layer);
#if PBL_ROUND
text_layer_enable_screen_text_flow_and_paging(text_layer, TEXT_FLOW_INSET_PX);
#endif
// Icon
const GPoint icon_origin = GPoint((grect_get_max_x(&frame) - icon_size.w) / 2,
icon_top_margin_px);
if (dialog_init_icon_layer(dialog, icon, icon_origin, !simple_dialog->icon_static)) {
layer_add_child(&dialog->window.layer, &dialog->icon_layer.layer);
}
dialog_load(dialog);
}
static void prv_simple_dialog_appear(Window *window) {
SimpleDialog *simple_dialog = window_get_user_data(window);
Dialog *dialog = simple_dialog_get_dialog(simple_dialog);
dialog_appear(dialog);
}
static void prv_simple_dialog_unload(Window *window) {
SimpleDialog *simple_dialog = window_get_user_data(window);
dialog_unload(&simple_dialog->dialog);
if (simple_dialog->dialog.destroy_on_pop) {
applib_free(simple_dialog);
}
}
static void prv_click_handler(ClickRecognizerRef recognizer, void *context) {
SimpleDialog *simple_dialog = context;
if (!simple_dialog->buttons_disabled) {
dialog_pop(&simple_dialog->dialog);
}
}
static void prv_config_provider(void *context) {
// Simple dialogs are dismissed when any button is pushed.
window_single_click_subscribe(BUTTON_ID_SELECT, prv_click_handler);
window_single_click_subscribe(BUTTON_ID_UP, prv_click_handler);
window_single_click_subscribe(BUTTON_ID_DOWN, prv_click_handler);
}
Dialog *simple_dialog_get_dialog(SimpleDialog *simple_dialog) {
return &simple_dialog->dialog;
}
void simple_dialog_push(SimpleDialog *simple_dialog, WindowStack *window_stack) {
dialog_push(&simple_dialog->dialog, window_stack);
}
void app_simple_dialog_push(SimpleDialog *simple_dialog) {
app_dialog_push(&simple_dialog->dialog);
}
void simple_dialog_init(SimpleDialog *simple_dialog, const char *dialog_name) {
PBL_ASSERTN(simple_dialog);
*simple_dialog = (SimpleDialog) {
.icon_static = !SIMPLE_DIALOG_ANIMATED,
};
dialog_init(&simple_dialog->dialog, dialog_name);
Window *window = &simple_dialog->dialog.window;
window_set_window_handlers(window, &(WindowHandlers) {
.load = prv_simple_dialog_load,
.unload = prv_simple_dialog_unload,
.appear = prv_simple_dialog_appear,
});
window_set_click_config_provider_with_context(window, prv_config_provider, simple_dialog);
window_set_user_data(window, simple_dialog);
}
SimpleDialog *simple_dialog_create(const char *dialog_name) {
SimpleDialog *simple_dialog = applib_malloc(sizeof(SimpleDialog));
if (simple_dialog) {
simple_dialog_init(simple_dialog, dialog_name);
}
return simple_dialog;
}
void simple_dialog_set_buttons_enabled(SimpleDialog *simple_dialog, bool enabled) {
simple_dialog->buttons_disabled = !enabled;
}
void simple_dialog_set_icon_animated(SimpleDialog *simple_dialog, bool animated) {
// This cannot be set after the window has been loaded
PBL_ASSERTN(!window_is_loaded(&simple_dialog->dialog.window));
simple_dialog->icon_static = !animated;
}
bool simple_dialog_does_text_fit(const char *text, GSize window_size,
GSize icon_size, bool has_status_bar) {
const uint16_t icon_top_margin_px = prv_get_icon_top_margin(has_status_bar, icon_size.h,
window_size.h);
GRect text_box;
prv_get_text_box(window_size, icon_size, icon_top_margin_px, &text_box);
return prv_get_rendered_text_height(text, &text_box) <= TEXT_MAX_HEIGHT_PX;
}