blob: a9b02b4c9012b85fafd8776b0f08beb5fb7ae2ff [file] [log] [blame]
// Copyright 2019 The Cobalt Authors. All Rights Reserved.
//
// 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 "cobalt/ui_navigation/nav_item.h"
#include <vector>
#include "base/bind.h"
#include "starboard/atomic.h"
#include "starboard/common/spin_lock.h"
namespace cobalt {
namespace ui_navigation {
namespace {
// These data structures support queuing of UI navigation changes so they can
// be executed as a batch to atomically update the UI.
SbAtomic32 g_pending_updates_lock(starboard::kSpinLockStateReleased);
struct PendingUpdate {
PendingUpdate(NativeItem nav_item, const base::Closure& closure)
: nav_item(nav_item), closure(closure) {}
NativeItem nav_item;
base::Closure closure;
};
std::vector<PendingUpdate>* g_pending_updates = nullptr;
// Pending focus change should always be done after all updates. This ensures
// the focus target is up-to-date.
volatile NativeItem g_pending_focus = kNativeItemInvalid;
// This tracks the total number of NavItems. It is used to control allocation
// and deletion of data for queued updates.
int32_t g_nav_item_count = 0;
// This helper function is necessary to preserve the |transform| by value.
void SetTransformHelper(NativeItem native_item, NativeMatrix2x3 transform) {
GetInterface().set_item_transform(native_item, &transform);
}
// This processes all pending updates assuming the update spinlock is locked.
void ProcessPendingChangesLocked(void* /* context */) {
for (size_t i = 0; i < g_pending_updates->size(); ++i) {
(*g_pending_updates)[i].closure.Run();
}
g_pending_updates->clear();
if (g_pending_focus != kNativeItemInvalid) {
GetInterface().set_focus(g_pending_focus);
g_pending_focus = kNativeItemInvalid;
}
}
void ProcessPendingChanges(void* context) {
starboard::ScopedSpinLock lock(&g_pending_updates_lock);
ProcessPendingChangesLocked(context);
}
// Remove any pending changes associated with the specified item.
void RemovePendingChangesLocked(NativeItem nav_item) {
DCHECK(nav_item != kNativeItemInvalid);
for (size_t i = 0; i < g_pending_updates->size();) {
if ((*g_pending_updates)[i].nav_item == nav_item) {
g_pending_updates->erase(g_pending_updates->begin() + i);
continue;
}
++i;
}
if (g_pending_focus == nav_item) {
g_pending_focus = kNativeItemInvalid;
}
}
} // namespace
NativeCallbacks NavItem::s_callbacks_ = {
&NavItem::OnBlur, &NavItem::OnFocus, &NavItem::OnScroll,
};
NavItem::NavItem(NativeItemType type, const base::Closure& onblur_callback,
const base::Closure& onfocus_callback,
const base::Closure& onscroll_callback)
: onblur_callback_(onblur_callback),
onfocus_callback_(onfocus_callback),
onscroll_callback_(onscroll_callback),
nav_item_type_(type),
nav_item_(GetInterface().create_item(type, &s_callbacks_, this)),
state_(kStateNew) {
starboard::ScopedSpinLock lock(&g_pending_updates_lock);
if (++g_nav_item_count == 1) {
g_pending_updates = new std::vector<PendingUpdate>();
g_pending_focus = kNativeItemInvalid;
}
}
NavItem::~NavItem() {
starboard::ScopedSpinLock lock(&g_pending_updates_lock);
DCHECK(state_ == kStatePendingDelete);
g_pending_updates->emplace_back(
nav_item_, base::Bind(GetInterface().destroy_item, nav_item_));
if (--g_nav_item_count == 0) {
ProcessPendingChangesLocked(nullptr);
delete g_pending_updates;
g_pending_updates = nullptr;
}
}
void NavItem::PerformQueuedUpdates() {
GetInterface().do_batch_update(&ProcessPendingChanges, nullptr);
}
void NavItem::Focus() {
starboard::ScopedSpinLock lock(&g_pending_updates_lock);
if (state_ == kStateEnabled) {
if (g_pending_updates->empty()) {
// Immediately update focus if nothing else is queued for update.
g_pending_focus = kNativeItemInvalid;
GetInterface().set_focus(nav_item_);
} else {
g_pending_focus = nav_item_;
}
}
}
void NavItem::UnfocusAll() {
starboard::ScopedSpinLock lock(&g_pending_updates_lock);
g_pending_focus = kNativeItemInvalid;
#if SB_API_VERSION >= SB_UI_NAVIGATION2_VERSION
g_pending_updates->emplace_back(
kNativeItemInvalid,
base::Bind(GetInterface().set_focus, kNativeItemInvalid));
#endif
}
void NavItem::SetEnabled(bool enabled) {
starboard::ScopedSpinLock lock(&g_pending_updates_lock);
if (enabled) {
if (state_ == kStateNew) {
state_ = kStateEnabled;
g_pending_updates->emplace_back(
nav_item_,
base::Bind(GetInterface().set_item_enabled, nav_item_, enabled));
}
} else {
if (state_ != kStatePendingDelete) {
state_ = kStatePendingDelete;
// Remove any pending changes associated with this item.
RemovePendingChangesLocked(nav_item_);
// Disable immediately to avoid errant callbacks on this item.
GetInterface().set_item_enabled(nav_item_, enabled);
}
}
}
void NavItem::SetDir(NativeItemDir dir) {
// Do immediately in case it impacts content offset queries.
GetInterface().set_item_dir(nav_item_, dir);
}
void NavItem::SetSize(float width, float height) {
starboard::ScopedSpinLock lock(&g_pending_updates_lock);
g_pending_updates->emplace_back(
nav_item_,
base::Bind(GetInterface().set_item_size, nav_item_, width, height));
}
void NavItem::SetTransform(const NativeMatrix2x3* transform) {
starboard::ScopedSpinLock lock(&g_pending_updates_lock);
g_pending_updates->emplace_back(
nav_item_, base::Bind(&SetTransformHelper, nav_item_, *transform));
}
bool NavItem::GetFocusTransform(NativeMatrix4* out_transform) {
return GetInterface().get_item_focus_transform(nav_item_, out_transform);
}
bool NavItem::GetFocusVector(float* out_x, float* out_y) {
return GetInterface().get_item_focus_vector(nav_item_, out_x, out_y);
}
void NavItem::SetContainerWindow(SbWindow window) {
// Do immediately since the lifetime of |window| is not guaranteed after this
// call.
GetInterface().set_item_container_window(nav_item_, window);
}
void NavItem::SetContainerItem(const scoped_refptr<NavItem>& container) {
// Set the container immediately so that subsequent calls to GetContainerItem
// are correct. However, the actual nav_item_ change should still be queued.
container_ = container;
starboard::ScopedSpinLock lock(&g_pending_updates_lock);
g_pending_updates->emplace_back(
nav_item_,
base::Bind(GetInterface().set_item_container_item, nav_item_,
container ? container->nav_item_ : kNativeItemInvalid));
}
const scoped_refptr<NavItem>& NavItem::GetContainerItem() const {
return container_;
}
void NavItem::SetContentOffset(float x, float y) {
// Do immediately since content offset may be queried immediately after.
GetInterface().set_item_content_offset(nav_item_, x, y);
}
void NavItem::GetContentOffset(float* out_x, float* out_y) {
GetInterface().get_item_content_offset(nav_item_, out_x, out_y);
}
// static
void NavItem::OnBlur(NativeItem item, void* callback_context) {
NavItem* this_ptr = static_cast<NavItem*>(callback_context);
if (!this_ptr->onblur_callback_.is_null()) {
this_ptr->onblur_callback_.Run();
}
}
// static
void NavItem::OnFocus(NativeItem item, void* callback_context) {
NavItem* this_ptr = static_cast<NavItem*>(callback_context);
if (!this_ptr->onfocus_callback_.is_null()) {
this_ptr->onfocus_callback_.Run();
}
}
// static
void NavItem::OnScroll(NativeItem item, void* callback_context) {
NavItem* this_ptr = static_cast<NavItem*>(callback_context);
if (!this_ptr->onscroll_callback_.is_null()) {
this_ptr->onscroll_callback_.Run();
}
}
} // namespace ui_navigation
} // namespace cobalt