/* * 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 "unobstructed_area_service.h" #include "unobstructed_area_service_private.h" #include "applib/app.h" #include "applib/graphics/framebuffer.h" #include "kernel/events.h" #include "kernel/pbl_malloc.h" #include "process_state/app_state/app_state.h" #include "syscall/syscall_internal.h" #include "system/passert.h" static void prv_handle_unobstructed_area_event(PebbleEvent *event, void *context); static void prv_origin_y_to_area(int16_t origin_y, GRect *area_out); void unobstructed_area_service_init(UnobstructedAreaState *state, int16_t current_y) { PBL_ASSERTN(state); *state = (UnobstructedAreaState) {}; prv_origin_y_to_area(current_y, &state->area); state->event_info = (EventServiceInfo) { .type = PEBBLE_UNOBSTRUCTED_AREA_EVENT, .handler = prv_handle_unobstructed_area_event, .context = state, }; event_service_client_subscribe(&state->event_info); } void unobstructed_area_service_deinit(UnobstructedAreaState *state) { PBL_ASSERTN(state); event_service_client_unsubscribe(&state->event_info); } static void prv_put_area_event(UnobstructedAreaEventType type, int16_t current_y, int16_t final_y, AnimationProgress progress) { PebbleEvent event = { .type = PEBBLE_UNOBSTRUCTED_AREA_EVENT, .unobstructed_area = { .type = type, .current_y = current_y, .final_y = final_y, .progress = progress, }, }; event_put(&event); } static void prv_clip_area(GRect *area_out) { PBL_ASSERTN(area_out); const GRect display_frame = { .size = framebuffer_get_size(app_state_get_framebuffer()) }; grect_clip(area_out, &display_frame); } //! Currently, the unobstructed area is derived from the origin of the obstruction. //! This is equivalent to the height of the unobstructed area. static void prv_origin_y_to_area(int16_t origin_y, GRect *area_out) { PBL_ASSERTN(area_out); *area_out = (GRect) { .size = { DISP_COLS, origin_y }, }; prv_clip_area(area_out); } void unobstructed_area_service_will_change(int16_t current_y, int16_t final_y) { prv_put_area_event(UnobstructedAreaEventType_WillChange, current_y, final_y, ANIMATION_NORMALIZED_MIN); } void unobstructed_area_service_change(int16_t current_y, int16_t final_y, AnimationProgress progress) { prv_put_area_event(UnobstructedAreaEventType_Change, current_y, final_y, progress); } void unobstructed_area_service_did_change(int16_t final_y) { prv_put_area_event(UnobstructedAreaEventType_DidChange, final_y, final_y, ANIMATION_NORMALIZED_MAX); } static void prv_save_event_area(UnobstructedAreaState *state, PebbleEvent *event) { GRect area; prv_origin_y_to_area(event->unobstructed_area.current_y, &area); state->area = area; prv_clip_area(&state->area); } static void prv_call_will_change(UnobstructedAreaState *state, PebbleEvent *event) { if (state->is_changing) { return; } // Always call the will handler even if the app restarts or starts out of sync with // the animation and has had its app state reinitialized. Set `is_changing` to keep track. state->is_changing = true; if (state->handlers.will_change) { GRect final_area; prv_origin_y_to_area(event->unobstructed_area.final_y, &final_area); state->handlers.will_change(final_area, state->context); } } static void prv_handle_will_change_event(PebbleEvent *event, void *context) { UnobstructedAreaState *state = context; // It is the producer's responsibility not to overlap unobstructed area changes. PBL_ASSERTN(!state->is_changing); prv_call_will_change(state, event); } static void prv_handle_change_event(PebbleEvent *event, void *context) { UnobstructedAreaState *state = context; prv_call_will_change(state, event); if (state->handlers.change) { state->handlers.change(event->unobstructed_area.progress, state->context); } } static void prv_handle_did_change_event(PebbleEvent *event, void *context) { UnobstructedAreaState *state = context; prv_call_will_change(state, event); state->is_changing = false; if (state->handlers.did_change) { state->handlers.did_change(state->context); } } static void prv_handle_unobstructed_area_event(PebbleEvent *event, void *context) { UnobstructedAreaState *state = context; GRect previous_area = state->area; prv_save_event_area(state, event); if (!grect_equal(&previous_area, &state->area)) { app_request_render(); } switch (event->unobstructed_area.type) { case UnobstructedAreaEventType_WillChange: prv_handle_will_change_event(event, context); break; case UnobstructedAreaEventType_Change: prv_handle_change_event(event, context); break; case UnobstructedAreaEventType_DidChange: prv_handle_did_change_event(event, context); break; } } void unobstructed_area_service_subscribe(UnobstructedAreaState *state, const UnobstructedAreaHandlers *handlers, void *context) { PBL_ASSERTN(state && handlers); state->handlers = *handlers; state->context = context; } void unobstructed_area_service_unsubscribe(UnobstructedAreaState *state) { PBL_ASSERTN(state); state->handlers = (UnobstructedAreaHandlers) {}; } void unobstructed_area_service_get_area(UnobstructedAreaState *state, GRect *area_out) { if (state && area_out) { *area_out = state->area; } } void app_unobstructed_area_service_subscribe(UnobstructedAreaHandlers handlers, void *context) { unobstructed_area_service_subscribe(app_state_get_unobstructed_area_state(), &handlers, context); } void app_unobstructed_area_service_unsubscribe(void) { unobstructed_area_service_unsubscribe(app_state_get_unobstructed_area_state()); }