| Index: content/browser/renderer_host/input/synthetic_smooth_move_gesture.cc
|
| diff --git a/content/browser/renderer_host/input/synthetic_smooth_scroll_gesture.cc b/content/browser/renderer_host/input/synthetic_smooth_move_gesture.cc
|
| similarity index 33%
|
| copy from content/browser/renderer_host/input/synthetic_smooth_scroll_gesture.cc
|
| copy to content/browser/renderer_host/input/synthetic_smooth_move_gesture.cc
|
| index 59f4af31fd891bafa6b19d6532dd1855a0f4d499..80f3ef76c662e54ee6dc94e17dc91a88d426b153 100644
|
| --- a/content/browser/renderer_host/input/synthetic_smooth_scroll_gesture.cc
|
| +++ b/content/browser/renderer_host/input/synthetic_smooth_move_gesture.cc
|
| @@ -2,7 +2,7 @@
|
| // Use of this source code is governed by a BSD-style license that can be
|
| // found in the LICENSE file.
|
|
|
| -#include "content/browser/renderer_host/input/synthetic_smooth_scroll_gesture.h"
|
| +#include "content/browser/renderer_host/input/synthetic_smooth_move_gesture.h"
|
|
|
| #include "base/logging.h"
|
| #include "ui/gfx/geometry/point_f.h"
|
| @@ -22,57 +22,76 @@ gfx::Vector2d CeilFromZero(const gfx::Vector2dF& vector) {
|
| return gfx::Vector2d(x, y);
|
| }
|
|
|
| -gfx::Vector2dF ProjectScalarOntoVector(
|
| - float scalar, const gfx::Vector2d& vector) {
|
| +gfx::Vector2dF ProjectScalarOntoVector(float scalar,
|
| + const gfx::Vector2dF& vector) {
|
| return gfx::ScaleVector2d(vector, scalar / vector.Length());
|
| }
|
|
|
| +const int kDefaultSpeedInPixelsPerSec = 800;
|
| +
|
| } // namespace
|
|
|
| -SyntheticSmoothScrollGesture::SyntheticSmoothScrollGesture(
|
| - const SyntheticSmoothScrollGestureParams& params)
|
| +SyntheticSmoothMoveGestureParams::SyntheticSmoothMoveGestureParams()
|
| + : speed_in_pixels_s(kDefaultSpeedInPixelsPerSec),
|
| + prevent_fling(true),
|
| + add_slop(true) {}
|
| +
|
| +SyntheticSmoothMoveGestureParams::~SyntheticSmoothMoveGestureParams() {}
|
| +
|
| +SyntheticSmoothMoveGesture::SyntheticSmoothMoveGesture(
|
| + SyntheticSmoothMoveGestureParams params)
|
| : params_(params),
|
| - gesture_source_type_(SyntheticGestureParams::DEFAULT_INPUT),
|
| - state_(SETUP) {}
|
| + current_move_segment_start_position_(params.start_point),
|
| + state_(SETUP) {
|
| +}
|
|
|
| -SyntheticSmoothScrollGesture::~SyntheticSmoothScrollGesture() {}
|
| +SyntheticSmoothMoveGesture::~SyntheticSmoothMoveGesture() {}
|
|
|
| -SyntheticGesture::Result SyntheticSmoothScrollGesture::ForwardInputEvents(
|
| - const base::TimeTicks& timestamp, SyntheticGestureTarget* target) {
|
| +SyntheticGesture::Result SyntheticSmoothMoveGesture::ForwardInputEvents(
|
| + const base::TimeTicks& timestamp,
|
| + SyntheticGestureTarget* target) {
|
| if (state_ == SETUP) {
|
| - gesture_source_type_ = params_.gesture_source_type;
|
| - if (gesture_source_type_ == SyntheticGestureParams::DEFAULT_INPUT)
|
| - gesture_source_type_ = target->GetDefaultSyntheticGestureSourceType();
|
| -
|
| state_ = STARTED;
|
| - current_scroll_segment_ = -1;
|
| - current_scroll_segment_stop_time_ = timestamp;
|
| + current_move_segment_ = -1;
|
| + current_move_segment_stop_time_ = timestamp;
|
| }
|
|
|
| - DCHECK_NE(gesture_source_type_, SyntheticGestureParams::DEFAULT_INPUT);
|
| - if (gesture_source_type_ == SyntheticGestureParams::TOUCH_INPUT)
|
| - ForwardTouchInputEvents(timestamp, target);
|
| - else if (gesture_source_type_ == SyntheticGestureParams::MOUSE_INPUT)
|
| - ForwardMouseInputEvents(timestamp, target);
|
| - else
|
| - return SyntheticGesture::GESTURE_SOURCE_TYPE_NOT_IMPLEMENTED;
|
| -
|
| + switch (params_.input_type) {
|
| + case SyntheticSmoothMoveGestureParams::TOUCH_INPUT:
|
| + ForwardTouchInputEvents(timestamp, target);
|
| + break;
|
| + case SyntheticSmoothMoveGestureParams::MOUSE_DRAG_INPUT:
|
| + ForwardMouseClickInputEvents(timestamp, target);
|
| + break;
|
| + case SyntheticSmoothMoveGestureParams::MOUSE_WHEEL_INPUT:
|
| + ForwardMouseWheelInputEvents(timestamp, target);
|
| + break;
|
| + default:
|
| + return SyntheticGesture::GESTURE_SOURCE_TYPE_NOT_IMPLEMENTED;
|
| + }
|
| return (state_ == DONE) ? SyntheticGesture::GESTURE_FINISHED
|
| : SyntheticGesture::GESTURE_RUNNING;
|
| }
|
|
|
| -void SyntheticSmoothScrollGesture::ForwardTouchInputEvents(
|
| - const base::TimeTicks& timestamp, SyntheticGestureTarget* target) {
|
| +// TODO(ssid): Clean up the switch statements by adding functions instead of
|
| +// large code, in the Forward*Events functions. Move the actions for all input
|
| +// types to different class (SyntheticInputDevice) which generates input events
|
| +// for all input types. The gesture class can use instance of device actions.
|
| +// Refer: crbug.com/461825
|
| +
|
| +void SyntheticSmoothMoveGesture::ForwardTouchInputEvents(
|
| + const base::TimeTicks& timestamp,
|
| + SyntheticGestureTarget* target) {
|
| base::TimeTicks event_timestamp = timestamp;
|
| switch (state_) {
|
| case STARTED:
|
| - if (ScrollIsNoOp()) {
|
| + if (MoveIsNoOp()) {
|
| state_ = DONE;
|
| break;
|
| }
|
| - AddTouchSlopToFirstDistance(target);
|
| - ComputeNextScrollSegment();
|
| - current_scroll_segment_start_position_ = params_.anchor;
|
| + if (params_.add_slop)
|
| + AddTouchSlopToFirstDistance(target);
|
| + ComputeNextMoveSegment();
|
| PressTouchPoint(target, event_timestamp);
|
| state_ = MOVING;
|
| break;
|
| @@ -81,11 +100,11 @@ void SyntheticSmoothScrollGesture::ForwardTouchInputEvents(
|
| gfx::Vector2dF delta = GetPositionDeltaAtTime(event_timestamp);
|
| MoveTouchPoint(target, delta, event_timestamp);
|
|
|
| - if (FinishedCurrentScrollSegment(event_timestamp)) {
|
| - if (!IsLastScrollSegment()) {
|
| - current_scroll_segment_start_position_ +=
|
| - params_.distances[current_scroll_segment_];
|
| - ComputeNextScrollSegment();
|
| + if (FinishedCurrentMoveSegment(event_timestamp)) {
|
| + if (!IsLastMoveSegment()) {
|
| + current_move_segment_start_position_ +=
|
| + params_.distances[current_move_segment_];
|
| + ComputeNextMoveSegment();
|
| } else if (params_.prevent_fling) {
|
| state_ = STOPPING;
|
| } else {
|
| @@ -95,9 +114,9 @@ void SyntheticSmoothScrollGesture::ForwardTouchInputEvents(
|
| }
|
| } break;
|
| case STOPPING:
|
| - if (timestamp - current_scroll_segment_stop_time_ >=
|
| + if (timestamp - current_move_segment_stop_time_ >=
|
| target->PointerAssumedStoppedTime()) {
|
| - event_timestamp = current_scroll_segment_stop_time_ +
|
| + event_timestamp = current_move_segment_stop_time_ +
|
| target->PointerAssumedStoppedTime();
|
| ReleaseTouchPoint(target, event_timestamp);
|
| state_ = DONE;
|
| @@ -105,22 +124,23 @@ void SyntheticSmoothScrollGesture::ForwardTouchInputEvents(
|
| break;
|
| case SETUP:
|
| NOTREACHED()
|
| - << "State STARTED invalid for synthetic scroll using touch input.";
|
| + << "State SETUP invalid for synthetic scroll using touch input.";
|
| case DONE:
|
| NOTREACHED()
|
| << "State DONE invalid for synthetic scroll using touch input.";
|
| }
|
| }
|
|
|
| -void SyntheticSmoothScrollGesture::ForwardMouseInputEvents(
|
| - const base::TimeTicks& timestamp, SyntheticGestureTarget* target) {
|
| +void SyntheticSmoothMoveGesture::ForwardMouseWheelInputEvents(
|
| + const base::TimeTicks& timestamp,
|
| + SyntheticGestureTarget* target) {
|
| switch (state_) {
|
| case STARTED:
|
| - if (ScrollIsNoOp()) {
|
| + if (MoveIsNoOp()) {
|
| state_ = DONE;
|
| break;
|
| }
|
| - ComputeNextScrollSegment();
|
| + ComputeNextMoveSegment();
|
| state_ = MOVING;
|
| // Fall through to forward the first event.
|
| case MOVING: {
|
| @@ -130,140 +150,224 @@ void SyntheticSmoothScrollGesture::ForwardMouseInputEvents(
|
| // internal scrolling state. This ensures that when the gesture has
|
| // finished we've scrolled exactly the specified distance.
|
| base::TimeTicks event_timestamp = ClampTimestamp(timestamp);
|
| - gfx::Vector2dF current_scroll_segment_total_delta =
|
| + gfx::Vector2dF current_move_segment_total_delta =
|
| GetPositionDeltaAtTime(event_timestamp);
|
| gfx::Vector2d delta_discrete =
|
| - FloorTowardZero(current_scroll_segment_total_delta -
|
| - current_scroll_segment_total_delta_discrete_);
|
| + FloorTowardZero(current_move_segment_total_delta -
|
| + current_move_segment_total_delta_discrete_);
|
| ForwardMouseWheelEvent(target, delta_discrete, event_timestamp);
|
| - current_scroll_segment_total_delta_discrete_ += delta_discrete;
|
| + current_move_segment_total_delta_discrete_ += delta_discrete;
|
|
|
| - if (FinishedCurrentScrollSegment(event_timestamp)) {
|
| - if (!IsLastScrollSegment()) {
|
| - current_scroll_segment_total_delta_discrete_ = gfx::Vector2d();
|
| - ComputeNextScrollSegment();
|
| - ForwardMouseInputEvents(timestamp, target);
|
| + if (FinishedCurrentMoveSegment(event_timestamp)) {
|
| + if (!IsLastMoveSegment()) {
|
| + current_move_segment_total_delta_discrete_ = gfx::Vector2d();
|
| + ComputeNextMoveSegment();
|
| + ForwardMouseWheelInputEvents(timestamp, target);
|
| } else {
|
| state_ = DONE;
|
| }
|
| }
|
| } break;
|
| case SETUP:
|
| + NOTREACHED() << "State SETUP invalid for synthetic scroll using mouse "
|
| + "wheel input.";
|
| + case STOPPING:
|
| + NOTREACHED() << "State STOPPING invalid for synthetic scroll using mouse "
|
| + "wheel input.";
|
| + case DONE:
|
| NOTREACHED()
|
| - << "State STARTED invalid for synthetic scroll using touch input.";
|
| + << "State DONE invalid for synthetic scroll using mouse wheel input.";
|
| + }
|
| +}
|
| +
|
| +void SyntheticSmoothMoveGesture::ForwardMouseClickInputEvents(
|
| + const base::TimeTicks& timestamp,
|
| + SyntheticGestureTarget* target) {
|
| + base::TimeTicks event_timestamp = timestamp;
|
| + switch (state_) {
|
| + case STARTED:
|
| + if (MoveIsNoOp()) {
|
| + state_ = DONE;
|
| + break;
|
| + }
|
| + ComputeNextMoveSegment();
|
| + PressMousePoint(target, event_timestamp);
|
| + state_ = MOVING;
|
| + break;
|
| + case MOVING: {
|
| + base::TimeTicks event_timestamp = ClampTimestamp(timestamp);
|
| + gfx::Vector2dF delta = GetPositionDeltaAtTime(event_timestamp);
|
| + MoveMousePoint(target, delta, event_timestamp);
|
| +
|
| + if (FinishedCurrentMoveSegment(event_timestamp)) {
|
| + if (!IsLastMoveSegment()) {
|
| + current_move_segment_start_position_ +=
|
| + params_.distances[current_move_segment_];
|
| + ComputeNextMoveSegment();
|
| + } else {
|
| + ReleaseMousePoint(target, event_timestamp);
|
| + state_ = DONE;
|
| + }
|
| + }
|
| + } break;
|
| case STOPPING:
|
| NOTREACHED()
|
| - << "State STOPPING invalid for synthetic scroll using touch input.";
|
| + << "State STOPPING invalid for synthetic drag using mouse input.";
|
| + case SETUP:
|
| + NOTREACHED()
|
| + << "State SETUP invalid for synthetic drag using mouse input.";
|
| case DONE:
|
| NOTREACHED()
|
| - << "State DONE invalid for synthetic scroll using touch input.";
|
| + << "State DONE invalid for synthetic drag using mouse input.";
|
| }
|
| }
|
|
|
| -void SyntheticSmoothScrollGesture::ForwardTouchEvent(
|
| - SyntheticGestureTarget* target, const base::TimeTicks& timestamp) {
|
| +void SyntheticSmoothMoveGesture::ForwardTouchEvent(
|
| + SyntheticGestureTarget* target,
|
| + const base::TimeTicks& timestamp) {
|
| touch_event_.timeStampSeconds = ConvertTimestampToSeconds(timestamp);
|
|
|
| target->DispatchInputEventToPlatform(touch_event_);
|
| }
|
|
|
| -void SyntheticSmoothScrollGesture::ForwardMouseWheelEvent(
|
| +void SyntheticSmoothMoveGesture::ForwardMouseWheelEvent(
|
| SyntheticGestureTarget* target,
|
| const gfx::Vector2dF& delta,
|
| const base::TimeTicks& timestamp) const {
|
| blink::WebMouseWheelEvent mouse_wheel_event =
|
| SyntheticWebMouseWheelEventBuilder::Build(delta.x(), delta.y(), 0, false);
|
|
|
| - mouse_wheel_event.x = params_.anchor.x();
|
| - mouse_wheel_event.y = params_.anchor.y();
|
| + mouse_wheel_event.x = current_move_segment_start_position_.x();
|
| + mouse_wheel_event.y = current_move_segment_start_position_.y();
|
|
|
| mouse_wheel_event.timeStampSeconds = ConvertTimestampToSeconds(timestamp);
|
|
|
| target->DispatchInputEventToPlatform(mouse_wheel_event);
|
| }
|
|
|
| -void SyntheticSmoothScrollGesture::PressTouchPoint(
|
| - SyntheticGestureTarget* target, const base::TimeTicks& timestamp) {
|
| - DCHECK_EQ(current_scroll_segment_, 0);
|
| - touch_event_.PressPoint(params_.anchor.x(), params_.anchor.y());
|
| +void SyntheticSmoothMoveGesture::PressTouchPoint(
|
| + SyntheticGestureTarget* target,
|
| + const base::TimeTicks& timestamp) {
|
| + DCHECK_EQ(current_move_segment_, 0);
|
| + touch_event_.PressPoint(current_move_segment_start_position_.x(),
|
| + current_move_segment_start_position_.y());
|
| ForwardTouchEvent(target, timestamp);
|
| }
|
|
|
| -void SyntheticSmoothScrollGesture::MoveTouchPoint(
|
| +void SyntheticSmoothMoveGesture::MoveTouchPoint(
|
| SyntheticGestureTarget* target,
|
| const gfx::Vector2dF& delta,
|
| const base::TimeTicks& timestamp) {
|
| - DCHECK_GE(current_scroll_segment_, 0);
|
| - DCHECK_LT(current_scroll_segment_,
|
| - static_cast<int>(params_.distances.size()));
|
| - gfx::PointF touch_position = current_scroll_segment_start_position_ + delta;
|
| + DCHECK_GE(current_move_segment_, 0);
|
| + DCHECK_LT(current_move_segment_, static_cast<int>(params_.distances.size()));
|
| + gfx::PointF touch_position = current_move_segment_start_position_ + delta;
|
| touch_event_.MovePoint(0, touch_position.x(), touch_position.y());
|
| ForwardTouchEvent(target, timestamp);
|
| }
|
|
|
| -void SyntheticSmoothScrollGesture::ReleaseTouchPoint(
|
| - SyntheticGestureTarget* target, const base::TimeTicks& timestamp) {
|
| - DCHECK_EQ(current_scroll_segment_,
|
| +void SyntheticSmoothMoveGesture::ReleaseTouchPoint(
|
| + SyntheticGestureTarget* target,
|
| + const base::TimeTicks& timestamp) {
|
| + DCHECK_EQ(current_move_segment_,
|
| static_cast<int>(params_.distances.size()) - 1);
|
| touch_event_.ReleasePoint(0);
|
| ForwardTouchEvent(target, timestamp);
|
| }
|
|
|
| -void SyntheticSmoothScrollGesture::AddTouchSlopToFirstDistance(
|
| +void SyntheticSmoothMoveGesture::PressMousePoint(
|
| + SyntheticGestureTarget* target,
|
| + const base::TimeTicks& timestamp) {
|
| + DCHECK_EQ(params_.input_type,
|
| + SyntheticSmoothMoveGestureParams::MOUSE_DRAG_INPUT);
|
| + blink::WebMouseEvent mouse_event = SyntheticWebMouseEventBuilder::Build(
|
| + blink::WebInputEvent::MouseDown, current_move_segment_start_position_.x(),
|
| + current_move_segment_start_position_.y(), 0);
|
| + mouse_event.clickCount = 1;
|
| + mouse_event.timeStampSeconds = ConvertTimestampToSeconds(timestamp);
|
| + target->DispatchInputEventToPlatform(mouse_event);
|
| +}
|
| +
|
| +void SyntheticSmoothMoveGesture::ReleaseMousePoint(
|
| + SyntheticGestureTarget* target,
|
| + const base::TimeTicks& timestamp) {
|
| + DCHECK_EQ(params_.input_type,
|
| + SyntheticSmoothMoveGestureParams::MOUSE_DRAG_INPUT);
|
| + gfx::PointF mouse_position =
|
| + current_move_segment_start_position_ + GetPositionDeltaAtTime(timestamp);
|
| + blink::WebMouseEvent mouse_event = SyntheticWebMouseEventBuilder::Build(
|
| + blink::WebInputEvent::MouseUp, mouse_position.x(), mouse_position.y(), 0);
|
| + mouse_event.timeStampSeconds = ConvertTimestampToSeconds(timestamp);
|
| + target->DispatchInputEventToPlatform(mouse_event);
|
| +}
|
| +
|
| +void SyntheticSmoothMoveGesture::MoveMousePoint(
|
| + SyntheticGestureTarget* target,
|
| + const gfx::Vector2dF& delta,
|
| + const base::TimeTicks& timestamp) {
|
| + gfx::PointF mouse_position = current_move_segment_start_position_ + delta;
|
| + DCHECK_EQ(params_.input_type,
|
| + SyntheticSmoothMoveGestureParams::MOUSE_DRAG_INPUT);
|
| + blink::WebMouseEvent mouse_event = SyntheticWebMouseEventBuilder::Build(
|
| + blink::WebInputEvent::MouseMove, mouse_position.x(), mouse_position.y(),
|
| + 0);
|
| + mouse_event.button = blink::WebMouseEvent::ButtonLeft;
|
| + mouse_event.timeStampSeconds = ConvertTimestampToSeconds(timestamp);
|
| + target->DispatchInputEventToPlatform(mouse_event);
|
| +}
|
| +
|
| +void SyntheticSmoothMoveGesture::AddTouchSlopToFirstDistance(
|
| SyntheticGestureTarget* target) {
|
| DCHECK_GE(params_.distances.size(), 1ul);
|
| - gfx::Vector2d& first_scroll_distance = params_.distances[0];
|
| - DCHECK_GT(first_scroll_distance.Length(), 0);
|
| - first_scroll_distance += CeilFromZero(ProjectScalarOntoVector(
|
| - target->GetTouchSlopInDips(), first_scroll_distance));
|
| + gfx::Vector2dF& first_move_distance = params_.distances[0];
|
| + DCHECK_GT(first_move_distance.Length(), 0);
|
| + first_move_distance += CeilFromZero(ProjectScalarOntoVector(
|
| + target->GetTouchSlopInDips(), first_move_distance));
|
| }
|
|
|
| -gfx::Vector2dF SyntheticSmoothScrollGesture::GetPositionDeltaAtTime(
|
| +gfx::Vector2dF SyntheticSmoothMoveGesture::GetPositionDeltaAtTime(
|
| const base::TimeTicks& timestamp) const {
|
| // Make sure the final delta is correct. Using the computation below can lead
|
| // to issues with floating point precision.
|
| - if (FinishedCurrentScrollSegment(timestamp))
|
| - return params_.distances[current_scroll_segment_];
|
| + if (FinishedCurrentMoveSegment(timestamp))
|
| + return params_.distances[current_move_segment_];
|
|
|
| float delta_length =
|
| params_.speed_in_pixels_s *
|
| - (timestamp - current_scroll_segment_start_time_).InSecondsF();
|
| + (timestamp - current_move_segment_start_time_).InSecondsF();
|
| return ProjectScalarOntoVector(delta_length,
|
| - params_.distances[current_scroll_segment_]);
|
| + params_.distances[current_move_segment_]);
|
| }
|
|
|
| -void SyntheticSmoothScrollGesture::ComputeNextScrollSegment() {
|
| - current_scroll_segment_++;
|
| - DCHECK_LT(current_scroll_segment_,
|
| - static_cast<int>(params_.distances.size()));
|
| +void SyntheticSmoothMoveGesture::ComputeNextMoveSegment() {
|
| + current_move_segment_++;
|
| + DCHECK_LT(current_move_segment_, static_cast<int>(params_.distances.size()));
|
| int64 total_duration_in_us = static_cast<int64>(
|
| - 1e6 * (params_.distances[current_scroll_segment_].Length() /
|
| + 1e6 * (params_.distances[current_move_segment_].Length() /
|
| params_.speed_in_pixels_s));
|
| DCHECK_GT(total_duration_in_us, 0);
|
| - current_scroll_segment_start_time_ = current_scroll_segment_stop_time_;
|
| - current_scroll_segment_stop_time_ =
|
| - current_scroll_segment_start_time_ +
|
| + current_move_segment_start_time_ = current_move_segment_stop_time_;
|
| + current_move_segment_stop_time_ =
|
| + current_move_segment_start_time_ +
|
| base::TimeDelta::FromMicroseconds(total_duration_in_us);
|
| }
|
|
|
| -base::TimeTicks SyntheticSmoothScrollGesture::ClampTimestamp(
|
| +base::TimeTicks SyntheticSmoothMoveGesture::ClampTimestamp(
|
| const base::TimeTicks& timestamp) const {
|
| - return std::min(timestamp, current_scroll_segment_stop_time_);
|
| + return std::min(timestamp, current_move_segment_stop_time_);
|
| }
|
|
|
| -bool SyntheticSmoothScrollGesture::FinishedCurrentScrollSegment(
|
| +bool SyntheticSmoothMoveGesture::FinishedCurrentMoveSegment(
|
| const base::TimeTicks& timestamp) const {
|
| - return timestamp >= current_scroll_segment_stop_time_;
|
| + return timestamp >= current_move_segment_stop_time_;
|
| }
|
|
|
| -bool SyntheticSmoothScrollGesture::IsLastScrollSegment() const {
|
| - DCHECK_LT(current_scroll_segment_,
|
| - static_cast<int>(params_.distances.size()));
|
| - return current_scroll_segment_ ==
|
| +bool SyntheticSmoothMoveGesture::IsLastMoveSegment() const {
|
| + DCHECK_LT(current_move_segment_, static_cast<int>(params_.distances.size()));
|
| + return current_move_segment_ ==
|
| static_cast<int>(params_.distances.size()) - 1;
|
| }
|
|
|
| -bool SyntheticSmoothScrollGesture::ScrollIsNoOp() const {
|
| +bool SyntheticSmoothMoveGesture::MoveIsNoOp() const {
|
| return params_.distances.size() == 0 || params_.distances[0].IsZero();
|
| }
|
|
|
|
|