pebble/src/fw/applib/ui/menu_layer_system_cells.c
Josh Soref 42ff2a4a3e spelling: initial
Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>
2025-01-29 00:03:24 -05:00

618 lines
26 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 "menu_layer.h"
#include "applib/graphics/graphics.h"
#include "applib/ui/kino/kino_reel.h"
#include "applib/ui/kino/kino_reel_gbitmap_private.h"
#include "process_management/process_manager.h"
#include "shell/system_theme.h"
#include "system/passert.h"
#include "util/math.h"
/////////////////////////////////
// System Provided Cell Types
//
// NOTES: Below are the implementations of system provided cell drawing functions.
//
//////////////////////
// Basic menu cell
typedef struct MenuCellDimensions {
int16_t basic_cell_height;
int16_t small_cell_height;
int16_t horizontal_inset;
int16_t title_subtitle_left_margin;
} MenuCellDimensions;
static const MenuCellDimensions s_menu_cell_dimensions[NumPreferredContentSizes] = {
//! @note these are the same as Medium until Small is designed
[PreferredContentSizeSmall] = {
.basic_cell_height = 44,
.small_cell_height = 34,
.horizontal_inset = 5,
.title_subtitle_left_margin = 30,
},
[PreferredContentSizeMedium] = {
.basic_cell_height = 44,
.small_cell_height = 34,
.horizontal_inset = 5,
.title_subtitle_left_margin = 30,
},
[PreferredContentSizeLarge] = {
.basic_cell_height = 61,
.small_cell_height = 42,
.horizontal_inset = 10,
.title_subtitle_left_margin = 34,
},
//! @note these are the same as Large until ExtraLarge is designed
[PreferredContentSizeExtraLarge] = {
.basic_cell_height = 61,
.small_cell_height = 42,
.horizontal_inset = 10,
.title_subtitle_left_margin = 34,
},
};
static const MenuCellDimensions *prv_get_dimensions_for_runtime_platform_default_size(void) {
const PreferredContentSize runtime_platform_default_size =
system_theme_get_default_content_size_for_runtime_platform();
return &s_menu_cell_dimensions[runtime_platform_default_size];
}
int16_t menu_cell_basic_cell_height(void) {
return prv_get_dimensions_for_runtime_platform_default_size()->basic_cell_height;
}
int16_t menu_cell_small_cell_height(void) {
return prv_get_dimensions_for_runtime_platform_default_size()->small_cell_height;
}
int16_t menu_cell_basic_horizontal_inset(void) {
return prv_get_dimensions_for_runtime_platform_default_size()->horizontal_inset;
}
static int16_t prv_title_subtitle_left_margin(void) {
return prv_get_dimensions_for_runtime_platform_default_size()->title_subtitle_left_margin;
}
static ALWAYS_INLINE GFont prv_get_cell_title_font(const MenuCellLayerConfig *config) {
return config->title_font ?: system_theme_get_font_for_default_size(TextStyleFont_MenuCellTitle);
}
static ALWAYS_INLINE GFont prv_get_cell_subtitle_font(const MenuCellLayerConfig *config) {
return config->subtitle_font ?:
system_theme_get_font_for_default_size(TextStyleFont_MenuCellSubtitle);
}
static ALWAYS_INLINE GFont prv_get_cell_value_font(const MenuCellLayerConfig *config) {
return config->value_font ?: prv_get_cell_title_font(config);
}
static ALWAYS_INLINE void prv_draw_icon(GContext *ctx, GBitmap *icon, const GRect *icon_frame,
bool is_legacy2) {
if (!is_legacy2) {
bool tint_icon = (icon && (gbitmap_get_format(icon) == GBitmapFormat1Bit));
if (tint_icon) {
graphics_context_set_compositing_mode(ctx, GCompOpTint);
} else if (ctx->draw_state.compositing_mode == GCompOpAssign) {
graphics_context_set_compositing_mode(ctx, GCompOpSet);
}
}
graphics_draw_bitmap_in_rect(ctx, icon, icon_frame);
}
static void prv_menu_cell_basic_draw_custom_rect(
GContext *ctx, const Layer *cell_layer, const MenuCellLayerConfig *config) {
const GRect *bounds = &cell_layer->bounds;
const bool is_legacy2 = process_manager_compiled_with_legacy2_sdk();
const GFont title_font = prv_get_cell_title_font(config);
const int16_t title_height = fonts_get_font_height(title_font);
const GFont subtitle_font = prv_get_cell_subtitle_font(config);
const int16_t subtitle_height = config->subtitle ? fonts_get_font_height(subtitle_font) : 0;
const int16_t full_height = title_height + subtitle_height + 10;
const int horizontal_margin = menu_cell_basic_horizontal_inset();
const int vertical_margin = (bounds->size.h - full_height) / 2;
int left_margin = 0;
GRect box;
const GAlign icon_align = (GAlign)config->icon_align;
if (config->icon) {
box = (GRect) {
.size = config->icon->bounds.size,
.origin = bounds->origin,
};
if (is_legacy2) {
static const GSize s_icon_size = { 33, 44 };
if (icon_align == GAlignRight) {
box.origin.x += bounds->size.w - (horizontal_margin + config->icon->bounds.size.w);
} else { // icon on left
box.origin.x += ((config->icon->bounds.size.w & 1) + // Nudge odd-width icons one right
((s_icon_size.w - config->icon->bounds.size.w) / 2));
}
box.origin.y += (s_icon_size.h - config->icon->bounds.size.h) / 2;
} else {
const GRect container_rect =
grect_inset(*bounds, GEdgeInsets(vertical_margin, horizontal_margin));
grect_align(&box, &container_rect, icon_align, true /* clip */);
if ((icon_align == GAlignTopLeft) || (icon_align == GAlignTop) ||
(icon_align == GAlignTopRight)) {
// Offset by the cap offset to match round's icon-title delta
box.origin.y += fonts_get_font_cap_offset(title_font);
}
}
if (config->icon_box_model) {
box.origin = gpoint_add(box.origin, config->icon_box_model->offset);
}
left_margin = box.origin.x;
prv_draw_icon(ctx, config->icon, &box, is_legacy2);
}
box = *bounds;
if (icon_align == GAlignRight) {
left_margin = horizontal_margin;
box.size.w -= config->icon->bounds.size.w;
} else {
left_margin = !config->icon ? horizontal_margin :
config->icon_form_fit ? (left_margin + config->icon->bounds.size.w +
(config->icon_box_model ? config->icon_box_model->margin.w : 0))
: prv_title_subtitle_left_margin() + horizontal_margin;
}
box.origin.x += left_margin;
box.size.w -= left_margin;
GRect value_box = box;
if (config->overflow_mode != GTextOverflowModeWordWrap) {
box.origin.y += vertical_margin;
box.size.h = title_height + 4;
value_box.origin.y = box.origin.y;
} else {
// Value box is centered when drawing with GTextOverflowModeWordWrap.
value_box.origin.y = MIN(box.size.h - full_height, title_height) / 2;
}
if (is_legacy2) {
// Update the text color to Black for legacy apps - this is to maintain existing behavior for
// 2.x compiled apps - no need to restore original since original 2.x did not do any restore
ctx->draw_state.text_color = GColorBlack;
}
if (config->value && (icon_align != GAlignRight)) {
value_box.size.w -= horizontal_margin;
const GFont value_font = prv_get_cell_value_font(config);
const GSize text_size = graphics_text_layout_get_max_used_size(
ctx, config->value, value_font, value_box, config->overflow_mode,
GTextAlignmentRight, NULL);
box.size.w -= (text_size.w + horizontal_margin * 2);
graphics_draw_text(ctx, config->value, value_font, value_box,
config->overflow_mode, GTextAlignmentRight, NULL);
}
if (config->title) {
graphics_draw_text(ctx, config->title, title_font, box,
config->overflow_mode, GTextAlignmentLeft, NULL);
}
if (config->subtitle) {
box.origin.y += title_height;
box.size.h = subtitle_height + 4;
graphics_draw_text(ctx, config->subtitle, subtitle_font, box,
config->overflow_mode, GTextAlignmentLeft, NULL);
}
}
// This function duplicates `grect_inset()` but helps us save some stack space by using pointer
// arguments and always inlining the function
static ALWAYS_INLINE void prv_grect_inset(GRect *rect, GEdgeInsets *insets) {
grect_standardize(rect);
const int16_t new_width = rect->size.w - insets->left - insets->right;
const int16_t new_height = rect->size.h - insets->top - insets->bottom;
if (new_width < 0 || new_height < 0) {
*rect = GRectZero;
} else {
*rect = GRect(rect->origin.x + insets->left, rect->origin.y + insets->top,
new_width, new_height);
}
}
static ALWAYS_INLINE bool prv_should_render_subtitle_round(const MenuCellLayerConfig *config,
bool is_selected) {
// If the cell isn't selected and there's no value text, then no subtitle text should be shown
return ((is_selected || config->value) && (config->subtitle != NULL));
}
static ALWAYS_INLINE GRect prv_menu_cell_basic_draw_custom_one_column_round(
GContext *ctx, const GRect *cell_layer_bounds, const MenuCellLayerConfig *config,
GTextAlignment text_alignment, GAlign container_alignment, bool is_selected) {
if (!cell_layer_bounds) {
return GRectZero;
}
const GFont title_font = prv_get_cell_title_font(config);
const uint8_t title_font_height = fonts_get_font_height(title_font);
GSize cell_layer_bounds_size = cell_layer_bounds->size;
// Bail out if we can't even fit a single line of the title
if (title_font_height > cell_layer_bounds_size.h) {
return GRectZero;
}
// Initialize our subtitle text height and icon frame size to 0 since we don't know yet if we will
// render them
int16_t subtitle_text_frame_height = 0;
GSize icon_frame_size = GSizeZero;
const GFont subtitle_font = prv_get_cell_subtitle_font(config);
const bool render_subtitle = prv_should_render_subtitle_round(config, is_selected);
if (render_subtitle) {
subtitle_text_frame_height = fonts_get_font_height(subtitle_font);
}
const int subtitle_text_cap_offset =
(config->subtitle) ? fonts_get_font_cap_offset(subtitle_font) : 0;
const GAlign icon_align = (GAlign)config->icon_align;
const bool render_icon = ((config->icon != NULL) && (icon_align != GAlignRight));
const GSize icon_bitmap_size = config->icon ? config->icon->bounds.size : GSizeZero;
if (render_icon) {
icon_frame_size = icon_bitmap_size;
if (config->icon_box_model) {
icon_frame_size = gsize_add(icon_frame_size, config->icon_box_model->margin);
}
}
const bool can_use_two_lines_for_title = !(render_subtitle || render_icon);
const bool can_use_many_lines_for_title = (config->overflow_mode == GTextOverflowModeWordWrap);
const int16_t initial_title_text_lines = can_use_two_lines_for_title ? 2 : 1;
int16_t title_text_frame_height = can_use_many_lines_for_title
? graphics_text_layout_get_text_height(ctx, config->title, title_font,
cell_layer_bounds_size.w, config->overflow_mode,
text_alignment)
: title_font_height * initial_title_text_lines;
const int title_text_cap_offset = (config->title) ? fonts_get_font_cap_offset(title_font) : 0;
int16_t container_height = title_text_frame_height + subtitle_text_frame_height;
if (icon_align == GAlignTop) {
// The icon is rendered above the others, add to container height
container_height += icon_frame_size.h;
} else if (icon_frame_size.h > cell_layer_bounds_size.h) {
// The icon is rendered beside but does not fit, cut it out
icon_frame_size = GSizeZero;
}
if (container_height > cell_layer_bounds_size.h) {
// If we couldn't fit one line of title, subtitle, and icon, try cutting out the icon
if (icon_align == GAlignTop) {
container_height -= icon_frame_size.h;
}
if (container_height > cell_layer_bounds_size.h) {
// If we couldn't fit one title line and the subtitle, try cutting out the subtitle instead
container_height = title_text_frame_height + icon_frame_size.h;
if (container_height > cell_layer_bounds_size.h) {
// If we couldn't fit just the title and icon, try just two lines for the title
container_height = title_font_height * 2;
if (container_height > cell_layer_bounds_size.h) {
// If we couldn't fit two title lines, just use one title line
title_text_frame_height = title_font_height;
} else {
title_text_frame_height = container_height;
}
subtitle_text_frame_height = 0;
if (icon_align == GAlignTop) {
icon_frame_size = GSizeZero;
}
} else {
subtitle_text_frame_height = 0;
}
} else {
icon_frame_size = GSizeZero;
}
}
// We'll reuse this rect to conserve stack space; here it is used as the title text frame
GRect rect = (GRect) {
.origin = cell_layer_bounds->origin,
.size = GSize(cell_layer_bounds_size.w, title_text_frame_height),
};
// Update the title text frame's height using the max used size of the title text because we
// might only have one line of text to render even though we have space for two lines
title_text_frame_height = graphics_text_layout_get_max_used_size(
ctx, config->title, title_font, rect, config->overflow_mode, text_alignment, NULL).h;
// Calculate the final container height and create a rectangle for it
container_height = title_text_frame_height + subtitle_text_frame_height;
const bool icon_on_left = ((icon_align == GAlignLeft) || (icon_align == GAlignTopLeft));
if (icon_align == GAlignTop) {
// The icon is on its own line at the top, extend accordingly
container_height += icon_frame_size.h;
} else if (render_icon && (icon_align == GAlignLeft)) {
// Let the icon extend the container height if it's taller than the title/subtitle combo
container_height = MAX(icon_frame_size.h, container_height);
}
GRect container_rect = (GRect) { .size = GSize(cell_layer_bounds_size.w, container_height) };
// Align the container rect in the cell
grect_align(&container_rect, cell_layer_bounds, container_alignment, true /* clip */);
// Here we reuse rect as the icon frame
rect.size = icon_frame_size;
// Align the icon frame (which might have zero height) at the top center of the container
grect_align(&rect, &container_rect, icon_align, true /* clip */);
// Save the title origin y before re-purposing container_rect
int title_text_frame_origin_y = container_rect.origin.y;
// Draw the icon (if one was provided and we have room for it)
if (render_icon && !gsize_equal(&rect.size, &GSizeZero)) {
// Only draw the icon if it fits within the cell
if (gsize_equal(&rect.size, &icon_frame_size)) {
// round has never worked with legacy2 apps
static const bool is_legacy2 = false;
// Reuse container_rect as icon frame
container_rect = (GRect) {
.origin = rect.origin,
.size = icon_bitmap_size,
};
if (config->icon_box_model) {
container_rect.origin = gpoint_add(container_rect.origin, config->icon_box_model->offset);
}
prv_draw_icon(ctx, config->icon, &container_rect, is_legacy2);
}
}
int cell_layer_bounds_origin_x = cell_layer_bounds->origin.x;
// Move the title and subtitle closer together to match designs
const int16_t icon_on_left_title_subtitle_vertical_spacing_offset = -3;
if (icon_align == GAlignTop) {
// Set the title text's frame origin at the bottom of the icon's frame
title_text_frame_origin_y = grect_get_max_y(&rect);
} else if (icon_on_left) {
// Move content to the right for the icon on the left
cell_layer_bounds_origin_x = grect_get_max_x(&rect);
cell_layer_bounds_size.w -= cell_layer_bounds_origin_x - cell_layer_bounds->origin.x;
if (icon_align == GAlignLeft) {
// Vertically center the title and subtitle within the container
title_text_frame_origin_y =
cell_layer_bounds->origin.y +
((cell_layer_bounds->size.h - title_text_frame_height -
subtitle_text_frame_height - 1) / 2);
if (subtitle_text_frame_height) {
title_text_frame_origin_y -= icon_on_left_title_subtitle_vertical_spacing_offset;
}
}
}
// Draw the subtitle (if one was provided and we have room), taking into account the cap offset
if (render_subtitle && (subtitle_text_frame_height != 0)) {
int16_t subtitle_text_frame_origin_y =
(int16_t)(title_text_frame_origin_y + title_text_frame_height - subtitle_text_cap_offset);
if (icon_align == GAlignLeft) {
subtitle_text_frame_origin_y += icon_on_left_title_subtitle_vertical_spacing_offset;
}
// Reuse rect as the subtitle text frame
rect = (GRect) {
.origin = GPoint(cell_layer_bounds_origin_x, subtitle_text_frame_origin_y),
.size = GSize(cell_layer_bounds_size.w, subtitle_text_frame_height)
};
graphics_draw_text(ctx, config->subtitle, subtitle_font, rect, config->overflow_mode,
text_alignment, NULL);
}
// Draw the title, which we're guaranteed to have room for because otherwise we would have bailed
// out at the beginning of this function
// Reuse rect as the title text frame
rect = (GRect) {
.origin = GPoint(cell_layer_bounds_origin_x, title_text_frame_origin_y),
.size = GSize(cell_layer_bounds_size.w, title_text_frame_height)
};
// Accumulate the cap offsets we need to position the title properly
int cap_offsets_to_apply = title_text_cap_offset;
if ((icon_align == GAlignLeft) && subtitle_text_frame_height) {
cap_offsets_to_apply += subtitle_text_cap_offset;
}
rect.origin.y -= cap_offsets_to_apply;
graphics_draw_text(ctx, config->title, title_font, rect, config->overflow_mode,
text_alignment, NULL);
// Add back the cap offset so functions that use the returned title text frame can position
// themselves using the actual frame
rect.origin.y += cap_offsets_to_apply;
return rect;
}
static ALWAYS_INLINE void prv_menu_cell_basic_draw_custom_two_columns_round(
GContext *ctx, const GRect *cell_layer_bounds, const MenuCellLayerConfig *config,
bool is_selected) {
if (!cell_layer_bounds) {
return;
}
const GSize icon_size = config->icon ? config->icon->bounds.size : GSizeZero;
// Calculate the size used by the value or icon on the right
// NOTE: If a value and icon is provided we only draw the value so we can re-use this function for
// drawing "icon on right" and "value"
GSize right_element_size;
const GFont value_font = prv_get_cell_value_font(config);
if (config->value) {
right_element_size = graphics_text_layout_get_max_used_size(
ctx, config->value, value_font, *cell_layer_bounds, config->overflow_mode,
GTextAlignmentRight, NULL);
} else {
right_element_size = icon_size;
}
// We reuse this rect to save stack space; here it is the rect of the left column content
GRect rect = *cell_layer_bounds;
prv_grect_inset(&rect, &GEdgeInsets(0, right_element_size.w, 0, 0));
// We overwrite rect to store the rect of the title text frame drawn in the one-column function
rect = prv_menu_cell_basic_draw_custom_one_column_round(ctx, &rect, config, GTextAlignmentLeft,
GAlignLeft, is_selected);
// Don't draw the right element if we couldn't draw the title in the left column
if (grect_equal(&rect, &GRectZero)) {
return;
}
// Now we store the right element frame in rect
rect = (GRect) {
.origin = GPoint(grect_get_max_x(&rect), rect.origin.y),
.size = right_element_size
};
if (config->value) {
rect.origin.y -= fonts_get_font_cap_offset(value_font);
graphics_draw_text(ctx, config->value, value_font, rect, config->overflow_mode,
GTextAlignmentRight, NULL);
} else {
// Only draw the icon if it fits within the cell after aligning it center right
grect_clip(&rect, cell_layer_bounds);
grect_align(&rect, cell_layer_bounds, GAlignRight, true);
if (gsize_equal(&rect.size, &icon_size)) {
// round has never worked with legacy2 apps
static const bool is_legacy2 = false;
if (config->icon_box_model) {
rect.origin = gpoint_add(rect.origin, config->icon_box_model->offset);
}
prv_draw_icon(ctx, config->icon, &rect, is_legacy2);
}
}
}
static ALWAYS_INLINE void prv_menu_cell_basic_draw_custom_round(
GContext* ctx, const Layer *cell_layer, const MenuCellLayerConfig *config) {
// TODO PBL-23041: When round MenuLayer animations are enabled, we need a "is_selected" function
const bool cell_is_selected = menu_cell_layer_is_highlighted(cell_layer);
const bool draw_two_columns =
(config->value || (config->icon && ((GAlign)config->icon_align == GAlignRight)));
// Determine appropriate insets to match designs
GRect cell_layer_bounds = cell_layer->bounds;
const int16_t horizontal_inset = (cell_is_selected && !draw_two_columns)
? MENU_CELL_ROUND_FOCUSED_HORIZONTAL_INSET
: MENU_CELL_ROUND_UNFOCUSED_HORIZONTAL_INSET;
prv_grect_inset(&cell_layer_bounds,
&GEdgeInsets(0, horizontal_inset + config->horizontal_inset));
if (draw_two_columns) {
prv_menu_cell_basic_draw_custom_two_columns_round(ctx, &cell_layer_bounds, config,
cell_is_selected);
} else {
prv_menu_cell_basic_draw_custom_one_column_round(ctx, &cell_layer_bounds, config,
GTextAlignmentCenter, GAlignCenter,
cell_is_selected);
}
}
static ALWAYS_INLINE void prv_draw_cell(GContext *ctx, const Layer *cell_layer,
const MenuCellLayerConfig *config) {
PBL_IF_RECT_ELSE(prv_menu_cell_basic_draw_custom_rect,
prv_menu_cell_basic_draw_custom_round)(ctx, cell_layer, config);
}
//! TODO: PBL-21467 Replace with MenuCellLayer
void menu_cell_layer_draw(GContext *ctx, const Layer *cell_layer,
const MenuCellLayerConfig *config) {
prv_draw_cell(ctx, cell_layer, config);
}
static ALWAYS_INLINE void prv_draw_basic(
GContext* ctx, const Layer *cell_layer, GFont const title_font, const char *title,
GFont const value_font, const char *value, GFont const subtitle_font, const char *subtitle,
GBitmap *icon, bool icon_on_right, GTextOverflowMode overflow_mode) {
MenuCellLayerConfig config = {
.title_font = title_font,
.subtitle_font = subtitle_font,
.value_font = value_font,
.title = title,
.subtitle = subtitle,
.value = value,
.icon = icon,
.icon_align = icon_on_right ? MenuCellLayerIconAlign_Right :
PBL_IF_RECT_ELSE(MenuCellLayerIconAlign_Left,
MenuCellLayerIconAlign_Top),
.overflow_mode = overflow_mode,
};
prv_draw_cell(ctx, cell_layer, &config);
}
void menu_cell_basic_draw_custom(GContext* ctx, const Layer *cell_layer, GFont const title_font,
const char *title, GFont const value_font, const char *value,
GFont const subtitle_font, const char *subtitle, GBitmap *icon,
bool icon_on_right, GTextOverflowMode overflow_mode) {
prv_draw_basic(ctx, cell_layer, title_font, title, value_font, value, subtitle_font, subtitle,
icon, icon_on_right, overflow_mode);
}
void menu_cell_basic_draw_icon_right(GContext* ctx, const Layer *cell_layer,
const char *title, const char *subtitle, GBitmap *icon) {
prv_draw_basic(ctx, cell_layer, NULL, title, NULL, NULL, NULL, subtitle, icon, true,
GTextOverflowModeFill);
}
void menu_cell_basic_draw(GContext* ctx, const Layer *cell_layer, const char *title,
const char *subtitle, GBitmap *icon) {
prv_draw_basic(ctx, cell_layer, NULL, title, NULL, NULL, NULL, subtitle, icon, false,
GTextOverflowModeFill);
}
//////////////////////
// Title menu cell
void menu_cell_title_draw(GContext* ctx, const Layer *cell_layer, const char *title) {
// Title:
if (title) {
const bool is_legacy2 = process_manager_compiled_with_legacy2_sdk();
if (is_legacy2) {
// Update the text color to Black for legacy apps - this is to maintain existing behavior for
// 2.x compiled apps - no need to restore original since original 2.x did not do any restore
graphics_context_set_text_color(ctx, GColorBlack);
}
GFont font = fonts_get_system_font(FONT_KEY_GOTHIC_28);
GRect box = cell_layer->bounds;
box.origin.x = 3;
box.origin.y -= 4;
box.size.w -= 3;
graphics_draw_text(ctx, title, font, box, GTextOverflowModeFill, GTextAlignmentLeft, NULL);
}
}
//////////////////////
// Basic header cell
void menu_cell_basic_header_draw(GContext* ctx, const Layer *cell_layer, const char *title) {
// Title:
if (title) {
const bool is_legacy2 = process_manager_compiled_with_legacy2_sdk();
if (is_legacy2) {
// Update the text color to Black for legacy apps - this is to maintain existing behavior for
// 2.x compiled apps - no need to restore original since original 2.x did not do any restore
graphics_context_set_text_color(ctx, GColorBlack);
}
GFont font = fonts_get_system_font(FONT_KEY_GOTHIC_14_BOLD);
GRect box = cell_layer->bounds;
// Pixel nudging...
box.origin.x += 2;
box.origin.y -= 1;
graphics_draw_text(ctx, title, font, box, GTextOverflowModeFill, GTextAlignmentLeft, NULL);
}
}