mirror of
https://github.com/google/pebble.git
synced 2025-05-01 15:51:40 -04:00
303 lines
13 KiB
C
303 lines
13 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 "time_selection_window.h"
|
|
|
|
#include "applib/ui/option_menu_window.h"
|
|
#include "services/common/clock.h"
|
|
#include "shell/system_theme.h"
|
|
|
|
#include <stdio.h>
|
|
|
|
typedef struct TimeSelectionSizeConfig {
|
|
const char *subtitle_font_key;
|
|
|
|
int16_t cell_width;
|
|
int16_t ampm_cell_width;
|
|
int16_t cell_padding;
|
|
|
|
int16_t top_offset_with_label;
|
|
int16_t top_offset_without_label;
|
|
int16_t label_origin_y;
|
|
int16_t range_origin_y;
|
|
} TimeSelectionSizeConfig;
|
|
|
|
static const TimeSelectionSizeConfig s_time_selection_config_medium = {
|
|
.subtitle_font_key = FONT_KEY_GOTHIC_14_BOLD,
|
|
|
|
.cell_width = 40,
|
|
.ampm_cell_width = PBL_IF_RECT_ELSE(40, 50),
|
|
.cell_padding = 4,
|
|
|
|
.top_offset_with_label = 75,
|
|
.top_offset_without_label = 67,
|
|
.label_origin_y = PBL_IF_RECT_ELSE(33, 38),
|
|
.range_origin_y = 119,
|
|
};
|
|
|
|
static const TimeSelectionSizeConfig s_time_selection_config_large = {
|
|
.subtitle_font_key = FONT_KEY_GOTHIC_18_BOLD,
|
|
|
|
.cell_width = 56,
|
|
.ampm_cell_width = 56,
|
|
.cell_padding = 6,
|
|
|
|
.top_offset_with_label = 87,
|
|
.top_offset_without_label = 67, // NOTE: this hasn't been designed, because we don't use it
|
|
.label_origin_y = 33,
|
|
.range_origin_y = 158,
|
|
};
|
|
|
|
static const TimeSelectionSizeConfig *s_time_selection_configs[NumPreferredContentSizes] = {
|
|
[PreferredContentSizeSmall] = &s_time_selection_config_medium,
|
|
[PreferredContentSizeMedium] = &s_time_selection_config_medium,
|
|
[PreferredContentSizeLarge] = &s_time_selection_config_large,
|
|
[PreferredContentSizeExtraLarge] = &s_time_selection_config_large,
|
|
};
|
|
|
|
static const TimeSelectionSizeConfig *prv_selection_config(void) {
|
|
const PreferredContentSize runtime_platform_default_size =
|
|
system_theme_get_default_content_size_for_runtime_platform();
|
|
return s_time_selection_configs[runtime_platform_default_size];
|
|
}
|
|
|
|
static int prv_cell_width(int i, int num_cells) {
|
|
const TimeSelectionSizeConfig * const config = prv_selection_config();
|
|
if (!clock_is_24h_style() && (i == (num_cells - 1))) {
|
|
return config->ampm_cell_width;
|
|
} else {
|
|
return config->cell_width;
|
|
}
|
|
}
|
|
|
|
static void prv_update_selection_layer(TimeSelectionWindowData *time_selection_window) {
|
|
const TimeSelectionSizeConfig * const config = prv_selection_config();
|
|
const int top_offset = time_selection_window->label_text_layer.text ?
|
|
config->top_offset_with_label : config->top_offset_without_label;
|
|
layer_set_frame(&time_selection_window->selection_layer.layer,
|
|
&GRect(0, top_offset, time_selection_window->window.layer.bounds.size.w,
|
|
selection_layer_default_cell_height()));
|
|
}
|
|
|
|
static void prv_vertical_align_text_layer(TimeSelectionWindowData *time_selection_window,
|
|
TextLayer *text_layer, int origin_y, int lines,
|
|
int extra_line_offset_y) {
|
|
// Supports `lines` or `lines + 1` line vertical centering for i18n
|
|
const int line_height = fonts_get_font_height(text_layer->font);
|
|
GRect frame = GRect(0, origin_y, time_selection_window->window.layer.bounds.size.w,
|
|
(lines + 1) * line_height + line_height / 2); // 1/2 more for descenders
|
|
layer_set_frame(&text_layer->layer, &frame);
|
|
#if PBL_ROUND
|
|
const int inset = 4;
|
|
text_layer_enable_screen_text_flow_and_paging(text_layer, inset);
|
|
#endif
|
|
const GSize content_size = app_text_layer_get_content_size(text_layer);
|
|
if (content_size.h > lines * line_height) {
|
|
frame = text_layer->layer.frame;
|
|
frame.origin.y += -line_height / 2 + extra_line_offset_y;
|
|
layer_set_frame(&text_layer->layer, &frame);
|
|
}
|
|
}
|
|
|
|
static void prv_update_label_text_layer(TimeSelectionWindowData *time_selection_window) {
|
|
TextLayer *label_text_layer = &time_selection_window->label_text_layer;
|
|
if (label_text_layer->text) {
|
|
const int label_origin_y = prv_selection_config()->label_origin_y;
|
|
const int lines = 1;
|
|
const int extra_line_offset_y = 0;
|
|
prv_vertical_align_text_layer(time_selection_window, label_text_layer, label_origin_y, lines,
|
|
extra_line_offset_y);
|
|
}
|
|
}
|
|
|
|
static void prv_update_range_text_layer(TimeSelectionWindowData *time_selection_window) {
|
|
TextLayer *range_text_layer = &time_selection_window->range_text_layer;
|
|
TextLayer *range_subtitle_text_layer = &time_selection_window->range_subtitle_text_layer;
|
|
if (range_text_layer->layer.hidden || range_subtitle_text_layer->layer.hidden) {
|
|
return;
|
|
}
|
|
|
|
// update range_text_layer
|
|
const int hour_end = time_selection_window->time_data.hour;
|
|
const int minute_end = time_selection_window->time_data.minute;
|
|
int hour_start = hour_end;
|
|
int minute_start = minute_end;
|
|
clock_hour_and_minute_add(&hour_start, &minute_start,
|
|
-time_selection_window->range_duration_m);
|
|
|
|
char start_buf[TIME_STRING_TIME_LENGTH];
|
|
char end_buf[TIME_STRING_TIME_LENGTH];
|
|
clock_format_time(start_buf, sizeof(start_buf), hour_start, minute_start, true);
|
|
clock_format_time(end_buf, sizeof(end_buf), hour_end, minute_end, true);
|
|
|
|
char *buffer = time_selection_window->range_buf;
|
|
snprintf(buffer, sizeof(time_selection_window->range_buf), "%s - %s", start_buf, end_buf);
|
|
|
|
text_layer_set_text(range_text_layer, buffer);
|
|
|
|
// update range_subtitle_text_layer
|
|
buffer = time_selection_window->range_subtitle_buf;
|
|
snprintf(buffer, sizeof(time_selection_window->range_subtitle_buf),
|
|
"%s", time_selection_window->range_text);
|
|
|
|
text_layer_set_text(range_subtitle_text_layer, buffer);
|
|
|
|
TextLayer *top_layer = PBL_IF_RECT_ELSE(range_subtitle_text_layer, range_text_layer);
|
|
TextLayer *bottom_layer = PBL_IF_RECT_ELSE(range_text_layer, range_subtitle_text_layer);
|
|
|
|
const int range_origin_y = prv_selection_config()->range_origin_y;
|
|
const int extra_line_offset_y = PBL_IF_RECT_ELSE(2, 4);
|
|
prv_vertical_align_text_layer(time_selection_window, top_layer, range_origin_y,
|
|
1, extra_line_offset_y);
|
|
const GSize top_size = app_text_layer_get_content_size(top_layer);
|
|
const int range_bottom_origin_y = range_origin_y + top_size.h;
|
|
prv_vertical_align_text_layer(time_selection_window, bottom_layer, range_bottom_origin_y,
|
|
1, extra_line_offset_y);
|
|
}
|
|
|
|
static void prv_update_layer_placement(TimeSelectionWindowData *time_selection_window) {
|
|
prv_update_selection_layer(time_selection_window);
|
|
prv_update_label_text_layer(time_selection_window);
|
|
prv_update_range_text_layer(time_selection_window);
|
|
}
|
|
|
|
// FROM selection layer callbacks
|
|
static char* prv_handle_from_get_text(unsigned index, void *context) {
|
|
TimeSelectionWindowData *data = context;
|
|
return date_time_selection_get_text(&data->time_data, index, data->cell_buf);
|
|
}
|
|
|
|
// Selection layer callbacks
|
|
static void prv_handle_complete(void *context) {
|
|
TimeSelectionWindowData *data = context;
|
|
data->complete_callback(data, data->callback_context);
|
|
}
|
|
|
|
static void prv_handle_inc(unsigned index, void *context) {
|
|
TimeSelectionWindowData *data = context;
|
|
date_time_handle_time_change(&data->time_data, index, 1);
|
|
prv_update_range_text_layer(data);
|
|
}
|
|
|
|
static void prv_handle_dec(unsigned index, void *context) {
|
|
TimeSelectionWindowData *data = context;
|
|
date_time_handle_time_change(&data->time_data, index, -1);
|
|
prv_update_range_text_layer(data);
|
|
}
|
|
|
|
// Public Functions
|
|
void time_selection_window_set_to_current_time(TimeSelectionWindowData *time_selection_window) {
|
|
struct tm current_time;
|
|
clock_get_time_tm(¤t_time);
|
|
time_selection_window->time_data.hour = current_time.tm_hour;
|
|
time_selection_window->time_data.minute = current_time.tm_min;
|
|
}
|
|
|
|
void time_selection_window_configure(TimeSelectionWindowData *time_selection_window,
|
|
const TimeSelectionWindowConfig *config) {
|
|
text_layer_set_text(&time_selection_window->label_text_layer, config->label);
|
|
if (config->color.a) {
|
|
selection_layer_set_active_bg_color(&time_selection_window->selection_layer, config->color);
|
|
}
|
|
if (config->range.update) {
|
|
time_selection_window->range_text = config->range.text;
|
|
time_selection_window->range_duration_m = config->range.duration_m;
|
|
layer_set_hidden(&time_selection_window->range_text_layer.layer, !config->range.enabled);
|
|
layer_set_hidden(&time_selection_window->range_subtitle_text_layer.layer,
|
|
!config->range.enabled);
|
|
}
|
|
if (config->callback.update) {
|
|
time_selection_window->complete_callback = config->callback.complete;
|
|
time_selection_window->callback_context = config->callback.context;
|
|
}
|
|
prv_update_layer_placement(time_selection_window);
|
|
}
|
|
|
|
static void prv_text_layer_init(Layer *window_layer, TextLayer *text_layer, const GFont font) {
|
|
text_layer_init_with_parameters(text_layer, &GRectZero, NULL, font, GColorBlack, GColorClear,
|
|
GTextAlignmentCenter, GTextOverflowModeTrailingEllipsis);
|
|
layer_add_child(window_layer, &text_layer->layer);
|
|
layer_set_hidden(&text_layer->layer, true);
|
|
}
|
|
|
|
void time_selection_window_init(TimeSelectionWindowData *time_selection_window,
|
|
const TimeSelectionWindowConfig *config) {
|
|
*time_selection_window = (TimeSelectionWindowData) {};
|
|
|
|
// General window setup
|
|
Window *window = &time_selection_window->window;
|
|
window_init(window, WINDOW_NAME("Time Selection Window"));
|
|
window_set_user_data(window, time_selection_window);
|
|
|
|
// Selection layer setup
|
|
const TimeSelectionSizeConfig * const size_config = prv_selection_config();
|
|
const int num_cells = clock_is_24h_style() ? 2 : 3;
|
|
const int padding = size_config->cell_padding;
|
|
SelectionLayer *selection_layer = &time_selection_window->selection_layer;
|
|
selection_layer_init(selection_layer, &GRectZero, num_cells);
|
|
for (int i = 0; i < num_cells; i++) {
|
|
const unsigned int cell_width = prv_cell_width(i, num_cells);
|
|
selection_layer_set_cell_width(selection_layer, i, cell_width);
|
|
}
|
|
|
|
selection_layer_set_cell_padding(selection_layer, padding);
|
|
selection_layer_set_inactive_bg_color(selection_layer, GColorDarkGray);
|
|
selection_layer_set_click_config_onto_window(selection_layer, window);
|
|
selection_layer_set_callbacks(&time_selection_window->selection_layer, time_selection_window,
|
|
(SelectionLayerCallbacks) {
|
|
.get_cell_text = prv_handle_from_get_text,
|
|
.complete = prv_handle_complete,
|
|
.increment = prv_handle_inc,
|
|
.decrement = prv_handle_dec,
|
|
});
|
|
layer_add_child(&window->layer, &time_selection_window->selection_layer.layer);
|
|
|
|
// Label setup
|
|
const GFont header_font = system_theme_get_font_for_default_size(TextStyleFont_Header);
|
|
|
|
TextLayer *label_text_layer = &time_selection_window->label_text_layer;
|
|
prv_text_layer_init(&window->layer, label_text_layer, header_font);
|
|
layer_set_hidden(&label_text_layer->layer, false);
|
|
|
|
// Range setup
|
|
TextLayer *range_text_layer = &time_selection_window->range_text_layer;
|
|
prv_text_layer_init(&window->layer, range_text_layer, header_font);
|
|
|
|
// Range subtitle setup
|
|
TextLayer *range_subtitle_text_layer = &time_selection_window->range_subtitle_text_layer;
|
|
prv_text_layer_init(&window->layer, range_subtitle_text_layer,
|
|
fonts_get_system_font(size_config->subtitle_font_key));
|
|
|
|
// Status setup
|
|
status_bar_layer_init(&time_selection_window->status_layer);
|
|
status_bar_layer_set_colors(&time_selection_window->status_layer,
|
|
PBL_IF_COLOR_ELSE(GColorWhite, GColorBlack),
|
|
PBL_IF_COLOR_ELSE(GColorBlack, GColorWhite));
|
|
status_bar_layer_set_separator_mode(&time_selection_window->status_layer,
|
|
PBL_IF_COLOR_ELSE(OPTION_MENU_STATUS_SEPARATOR_MODE,
|
|
StatusBarLayerSeparatorModeNone));
|
|
layer_add_child(&time_selection_window->window.layer,
|
|
&time_selection_window->status_layer.layer);
|
|
|
|
time_selection_window_configure(time_selection_window, config);
|
|
}
|
|
|
|
void time_selection_window_deinit(TimeSelectionWindowData *time_selection_window) {
|
|
if (time_selection_window) {
|
|
status_bar_layer_deinit(&time_selection_window->status_layer);
|
|
selection_layer_deinit(&time_selection_window->selection_layer);
|
|
}
|
|
}
|