blob: fd2ac8d378e6dd5d673dfe4926610d684ee548e5 [file] [log] [blame]
// Copyright 2019 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <math.h>
#include <fuchsia/ui/app/cpp/fidl.h>
#include <fuchsia/ui/gfx/cpp/fidl.h>
#include <fuchsia/ui/input/cpp/fidl.h>
#include <fuchsia/ui/scenic/cpp/fidl.h>
#include <fuchsia/ui/views/cpp/fidl.h>
#include <lib/async-loop/cpp/loop.h>
#include <lib/component/cpp/startup_context.h>
#include <src/lib/fxl/logging.h>
#include <lib/ui/scenic/cpp/commands.h>
#include <lib/ui/scenic/cpp/view_token_pair.h>
class BouncingBallView : public fuchsia::ui::scenic::SessionListener {
public:
BouncingBallView(component::StartupContext* startup_context,
fuchsia::ui::views::ViewToken view_token)
: session_listener_binding_(this) {
// Connect to Scenic.
fuchsia::ui::scenic::ScenicPtr scenic =
startup_context
->ConnectToEnvironmentService<fuchsia::ui::scenic::Scenic>();
// Create a Scenic Session and a Scenic SessionListener.
scenic->CreateSession(session_.NewRequest(),
session_listener_binding_.NewBinding());
InitializeScene(std::move(view_token));
}
private:
static void PushCommand(std::vector<fuchsia::ui::scenic::Command>* cmds,
fuchsia::ui::gfx::Command cmd) {
// Wrap the gfx::Command in a scenic::Command, then push it.
cmds->push_back(scenic::NewCommand(std::move(cmd)));
}
void InitializeScene(fuchsia::ui::views::ViewToken view_token) {
// Build up a list of commands we will send over our Scenic Session.
std::vector<fuchsia::ui::scenic::Command> cmds;
// View: Use |view_token| to create a View in the Session.
PushCommand(&cmds, scenic::NewCreateViewCmd(kViewId, std::move(view_token),
"bouncing_circle_view"));
// Root Node.
PushCommand(&cmds, scenic::NewCreateEntityNodeCmd(kRootNodeId));
PushCommand(&cmds, scenic::NewAddChildCmd(kViewId, kRootNodeId));
// Background Material.
PushCommand(&cmds, scenic::NewCreateMaterialCmd(kBgMaterialId));
PushCommand(&cmds, scenic::NewSetColorCmd(kBgMaterialId, 0xf5, 0x00, 0x57,
0xff)); // Pink A400
// Background ShapeNode.
PushCommand(&cmds, scenic::NewCreateShapeNodeCmd(kBgNodeId));
PushCommand(&cmds, scenic::NewSetMaterialCmd(kBgNodeId, kBgMaterialId));
PushCommand(&cmds, scenic::NewAddChildCmd(kRootNodeId, kBgNodeId));
// Circle's Material.
PushCommand(&cmds, scenic::NewCreateMaterialCmd(kCircleMaterialId));
PushCommand(&cmds,
scenic::NewSetColorCmd(kCircleMaterialId, 0x67, 0x3a, 0xb7,
0xff)); // Deep Purple 500
// Circle's ShapeNode.
PushCommand(&cmds, scenic::NewCreateShapeNodeCmd(kCircleNodeId));
PushCommand(&cmds,
scenic::NewSetMaterialCmd(kCircleNodeId, kCircleMaterialId));
PushCommand(&cmds, scenic::NewAddChildCmd(kRootNodeId, kCircleNodeId));
session_->Enqueue(std::move(cmds));
// Apply all the commands we've enqueued by calling Present. For this first
// frame we call Present with a presentation_time = 0 which means it the
// commands should be applied immediately. For future frames, we'll use the
// timing information we receive to have precise presentation times.
session_->Present(0, {}, {},
[this](fuchsia::images::PresentationInfo info) {
OnPresent(std::move(info));
});
}
// |fuchsia::ui::scenic::SessionListener|
void OnScenicError(std::string error) override {}
static bool IsViewPropertiesChangedEvent(
const fuchsia::ui::scenic::Event& event) {
return event.Which() == fuchsia::ui::scenic::Event::Tag::kGfx &&
event.gfx().Which() ==
fuchsia::ui::gfx::Event::Tag::kViewPropertiesChanged;
}
static bool IsPointerEvent(const fuchsia::ui::scenic::Event& event) {
return event.Which() == fuchsia::ui::scenic::Event::Tag::kInput &&
event.input().Which() ==
fuchsia::ui::input::InputEvent::Tag::kPointer;
}
static bool IsPointerDownEvent(const fuchsia::ui::scenic::Event& event) {
return IsPointerEvent(event) &&
event.input().pointer().phase ==
fuchsia::ui::input::PointerEventPhase::DOWN;
}
static bool IsPointerUpEvent(const fuchsia::ui::scenic::Event& event) {
return IsPointerEvent(event) &&
event.input().pointer().phase ==
fuchsia::ui::input::PointerEventPhase::UP;
}
// |fuchsia::ui::scenic::SessionListener|
void OnScenicEvent(std::vector<fuchsia::ui::scenic::Event> events) override {
for (auto& event : events) {
if (IsViewPropertiesChangedEvent(event)) {
OnViewPropertiesChanged(
event.gfx().view_properties_changed().properties);
} else if (IsPointerDownEvent(event)) {
pointer_down_ = true;
pointer_id_ = event.input().pointer().pointer_id;
} else if (IsPointerUpEvent(event)) {
if (pointer_id_ == event.input().pointer().pointer_id) {
pointer_down_ = false;
}
} else {
// Unhandled event.
}
}
}
void OnViewPropertiesChanged(fuchsia::ui::gfx::ViewProperties vp) {
view_width_ = (vp.bounding_box.max.x - vp.inset_from_max.x) -
(vp.bounding_box.min.x + vp.inset_from_min.x);
view_height_ = (vp.bounding_box.max.y - vp.inset_from_max.y) -
(vp.bounding_box.min.y + vp.inset_from_min.y);
// Position is relative to the View's origin system.
const float center_x = view_width_ * .5f;
const float center_y = view_height_ * .5f;
// Build up a list of commands we will send over our Scenic Session.
std::vector<fuchsia::ui::scenic::Command> cmds;
// Background Shape.
const int bg_shape_id = new_resource_id_++;
PushCommand(&cmds, scenic::NewCreateRectangleCmd(bg_shape_id, view_width_,
view_height_));
PushCommand(&cmds, scenic::NewSetShapeCmd(kBgNodeId, bg_shape_id));
// We release the Shape Resource here, but it continues to stay alive in
// Scenic because it's being referenced by background ShapeNode (i.e. the
// one with id kBgNodeId). However, we no longer have a way to reference it.
//
// Once the background ShapeNode no longer references this shape, because a
// new Shape was set on it, this Shape will be destroyed internally in
// Scenic.
PushCommand(&cmds, scenic::NewReleaseResourceCmd(bg_shape_id));
// Translate the background node.
constexpr float kBackgroundElevation = 0.f;
PushCommand(&cmds, scenic::NewSetTranslationCmd(
kBgNodeId, (float[]){center_x, center_y,
-kBackgroundElevation}));
// Circle Shape.
circle_radius_ = std::min(view_width_, view_height_) * .1f;
const int circle_shape_id = new_resource_id_++;
PushCommand(&cmds,
scenic::NewCreateCircleCmd(circle_shape_id, circle_radius_));
PushCommand(&cmds, scenic::NewSetShapeCmd(kCircleNodeId, circle_shape_id));
// We release the Shape Resource here, but it continues to stay alive in
// Scenic because it's being referenced by circle's ShapeNode (i.e. the one
// with id kCircleNodeId). However, we no longer have a way to reference it.
//
// Once the background ShapeNode no longer references this shape, because a
// new Shape was set on it, this Shape will be destroyed internally in
// Scenic.
PushCommand(&cmds, scenic::NewReleaseResourceCmd(circle_shape_id));
session_->Enqueue(std::move(cmds));
// The commands won't actually get committed until Session.Present() is
// called. However, since we're animating every frame, in this case we can
// assume Present() will be called shortly.
}
void UpdateCirclePosition(float t) {
if (pointer_down_) {
// Move back to near initial position and velocity when there's a pointer
// down event.
circle_pos_x_ = initialCirclePosX;
circle_pos_y_ = initialCirclePosY;
circle_velocity_y_ = 0.f;
return;
}
constexpr float kYAcceleration = 3.f;
circle_velocity_y_ += kYAcceleration * t;
constexpr float kCircleVelocityX = 0.2;
circle_pos_x_ += kCircleVelocityX * t;
circle_pos_y_ += fmin(circle_velocity_y_ * t, 1.f);
if (circle_pos_y_ > 1.f) {
// Bounce.
circle_velocity_y_ *= -0.8f;
circle_pos_y_ = 1.f;
}
if (circle_pos_y_ >= 0.999f && fabs(circle_velocity_y_) < .015f) {
// If the circle stops bouncing, start the simulation again.
circle_pos_y_ = 0.f;
circle_velocity_y_ = 0.f;
}
if (circle_pos_x_ > 1) {
// Wrap the x position.
circle_pos_x_ = fmod(circle_pos_x_, 1.f);
}
}
void OnPresent(fuchsia::images::PresentationInfo presentation_info) {
uint64_t presentation_time = presentation_info.presentation_time;
constexpr float kSecondsPerNanosecond = .000'000'001f;
float t =
(presentation_time - last_presentation_time_) * kSecondsPerNanosecond;
if (last_presentation_time_ == 0) {
t = 0;
}
last_presentation_time_ = presentation_time;
std::vector<fuchsia::ui::scenic::Command> cmds;
UpdateCirclePosition(t);
const float circle_pos_x_absolute = circle_pos_x_ * view_width_;
const float circle_pos_y_absolute =
circle_pos_y_ * view_height_ - circle_radius_;
// Translate the circle's node.
constexpr float kCircleElevation = 8.f;
PushCommand(&cmds, scenic::NewSetTranslationCmd(
kCircleNodeId, (float[]){circle_pos_x_absolute,
circle_pos_y_absolute,
-kCircleElevation}));
session_->Enqueue(std::move(cmds));
zx_time_t next_presentation_time = presentation_info.presentation_time +
presentation_info.presentation_interval;
session_->Present(next_presentation_time, {}, {},
[this](fuchsia::images::PresentationInfo info) {
OnPresent(std::move(info));
});
}
const int kViewId = 1;
const int kRootNodeId = 2;
const int kBgMaterialId = 3;
const int kBgNodeId = 4;
const int kCircleMaterialId = 5;
const int kCircleNodeId = 6;
// For other resources we create, we use |new_resource_id_| and then increment
// it.
int new_resource_id_ = 7;
uint64_t last_presentation_time_ = 0;
float view_width_ = 0;
float view_height_ = 0;
// Position is in the range [0, 1] and then multiplied by (view_width_,
// view_height_).
static constexpr float initialCirclePosX = 0.12f;
static constexpr float initialCirclePosY = 0.26f;
float circle_pos_x_ = initialCirclePosX;
float circle_pos_y_ = initialCirclePosY;
float circle_velocity_y_ = 0.f;
// Circle's radius in logical pixels.
float circle_radius_ = 0.f;
// Input.
bool pointer_down_ = false;
uint32_t pointer_id_ = 0;
fidl::Binding<fuchsia::ui::scenic::SessionListener> session_listener_binding_;
fuchsia::ui::scenic::SessionPtr session_;
};
// Implement the ViewProvider interface, a standard way for an embedder to
// provide us a token that, using Scenic APIs, allows us to create a View
// that's attached to the embedder's ViewHolder.
class ViewProviderService : public fuchsia::ui::app::ViewProvider {
public:
ViewProviderService(component::StartupContext* startup_context)
: startup_context_(startup_context) {}
// |fuchsia::ui::app::ViewProvider|
void CreateView(
zx::eventpair view_token,
fidl::InterfaceRequest<fuchsia::sys::ServiceProvider> incoming_services,
fidl::InterfaceHandle<fuchsia::sys::ServiceProvider> outgoing_services)
override {
auto view = std::make_unique<BouncingBallView>(
startup_context_, scenic::ToViewToken(std::move(view_token)));
views_.push_back(std::move(view));
}
void HandleViewProviderRequest(
fidl::InterfaceRequest<fuchsia::ui::app::ViewProvider> request) {
bindings_.AddBinding(this, std::move(request));
}
private:
component::StartupContext* startup_context_ = nullptr;
std::vector<std::unique_ptr<BouncingBallView>> views_;
fidl::BindingSet<ViewProvider> bindings_;
};
int main(int argc, const char** argv) {
async::Loop loop(&kAsyncLoopConfigAttachToThread);
std::unique_ptr<component::StartupContext> startup_context =
component::StartupContext::CreateFromStartupInfo();
ViewProviderService view_provider(startup_context.get());
// Add our ViewProvider service to the outgoing services.
startup_context->outgoing().AddPublicService<fuchsia::ui::app::ViewProvider>(
[&view_provider](
fidl::InterfaceRequest<fuchsia::ui::app::ViewProvider> request) {
view_provider.HandleViewProviderRequest(std::move(request));
});
loop.Run();
return 0;
}