blob: 7d39bed25c31fbf265a62d01033a8f2277b89f0c [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 specified by the given context. The update
// list should already be locked from modifications by other threads.
void ProcessPendingChanges(void* context) {
std::vector<PendingUpdate>* updates =
static_cast<std::vector<PendingUpdate>*>(context);
for (size_t i = 0; i < updates->size(); ++i) {
(*updates)[i].closure.Run();
}
updates->clear();
}
// 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
// Use an atomic to track the currently-focused item. Don't use a mutex or
// spinlock in GetGlobalFocusTransform() / GetGlobalFocusVector() and
// OnBlur() / OnFocus() since this can result in a deadlock if the starboard
// implementation uses a lock of its own for the NativeItem.
SbAtomicPtr NavItem::s_focused_nav_item_(
reinterpret_cast<intptr_t>(kNativeItemInvalid));
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) {
static_assert(sizeof(s_focused_nav_item_) == sizeof(NativeItem),
"Size mismatch");
static_assert(sizeof(s_focused_nav_item_) == sizeof(intptr_t),
"Size mismatch");
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);
DCHECK(SbAtomicNoBarrier_LoadPtr(&s_focused_nav_item_) !=
reinterpret_cast<intptr_t>(nav_item_));
g_pending_updates->emplace_back(
nav_item_, base::Bind(GetInterface().destroy_item, nav_item_));
if (--g_nav_item_count == 0) {
DCHECK(g_pending_focus == kNativeItemInvalid);
ProcessPendingChanges(g_pending_updates);
delete g_pending_updates;
g_pending_updates = nullptr;
}
}
void NavItem::PerformQueuedUpdates() {
std::vector<PendingUpdate> updates_snapshot;
// Process only the updates that have been queued up to this point. Other
// threads may be queueing more updates, and we shouldn't pick them up in this
// batch as that may result in them getting processed before async tasks that
// may be done by the platform.
{
starboard::ScopedSpinLock lock(&g_pending_updates_lock);
updates_snapshot.swap(*g_pending_updates);
if (g_pending_focus != kNativeItemInvalid) {
updates_snapshot.emplace_back(
g_pending_focus,
base::Bind(GetInterface().set_focus, g_pending_focus));
g_pending_focus = kNativeItemInvalid;
}
}
GetInterface().do_batch_update(&ProcessPendingChanges, &updates_snapshot);
}
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);
}
SbAtomicNoBarrier_CompareAndSwapPtr(
&s_focused_nav_item_, reinterpret_cast<intptr_t>(nav_item_),
reinterpret_cast<intptr_t>(kNativeItemInvalid));
}
}
void NavItem::SetDir(NativeItemDir dir) {
// Do immediately in case it impacts content offset queries.
GetInterface().set_item_dir(nav_item_, dir);
}
void NavItem::SetFocusDuration(float seconds) {
GetInterface().set_item_focus_duration(nav_item_, seconds);
}
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
bool NavItem::GetGlobalFocusTransform(NativeMatrix4* out_transform) {
starboard::ScopedSpinLock lock(&g_pending_updates_lock);
NativeItem focused_item = reinterpret_cast<NativeItem>(
SbAtomicNoBarrier_LoadPtr(&s_focused_nav_item_));
if (focused_item == kNativeItemInvalid) {
return false;
}
return GetInterface().get_item_focus_transform(focused_item, out_transform);
}
// static
bool NavItem::GetGlobalFocusVector(float* out_x, float* out_y) {
starboard::ScopedSpinLock lock(&g_pending_updates_lock);
NativeItem focused_item = reinterpret_cast<NativeItem>(
SbAtomicNoBarrier_LoadPtr(&s_focused_nav_item_));
if (focused_item == kNativeItemInvalid) {
return false;
}
return GetInterface().get_item_focus_vector(focused_item, out_x, out_y);
}
// static
void NavItem::OnBlur(NativeItem item, void* callback_context) {
NavItem* this_ptr = static_cast<NavItem*>(callback_context);
SbAtomicNoBarrier_CompareAndSwapPtr(
&s_focused_nav_item_, reinterpret_cast<intptr_t>(this_ptr->nav_item_),
reinterpret_cast<intptr_t>(kNativeItemInvalid));
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);
SbAtomicNoBarrier_StorePtr(&s_focused_nav_item_,
reinterpret_cast<intptr_t>(this_ptr->nav_item_));
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