Skip to content

Commit 2b5e45a

Browse files
Ink Open Sourcecopybara-github
Ink Open Source
authored andcommitted
Add kTimeSinceInputsFinished brush behavior sources
This allows for one-shot whole-stroke animations that occur after input is finished. PiperOrigin-RevId: 621598492
1 parent 7cfc270 commit 2b5e45a

13 files changed

+133
-5
lines changed

ink/brush/brush_behavior.cc

+9-1
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,8 @@ bool IsValidBehaviorSource(BrushBehavior::Source source) {
204204
kInputAccelerationForwardInCentimetersPerSecondSquared:
205205
case BrushBehavior::Source::
206206
kInputAccelerationLateralInCentimetersPerSecondSquared:
207+
case BrushBehavior::Source::kTimeSinceInputsFinishedInSeconds:
208+
case BrushBehavior::Source::kTimeSinceInputsFinishedInMillis:
207209
return true;
208210
}
209211
return false;
@@ -214,9 +216,11 @@ absl::Status ValidateSourceAndOutOfRangeCombination(
214216
switch (source) {
215217
case BrushBehavior::Source::kTimeSinceInputInSeconds:
216218
case BrushBehavior::Source::kTimeSinceInputInMillis:
219+
case BrushBehavior::Source::kTimeSinceInputsFinishedInSeconds:
220+
case BrushBehavior::Source::kTimeSinceInputsFinishedInMillis:
217221
if (out_of_range != BrushBehavior::OutOfRange::kClamp) {
218222
return absl::InvalidArgumentError(
219-
"`Source::kTimeSinceInput*` must only be used with "
223+
"`Source::kTimeSince*` must only be used with "
220224
"`source_out_of_range_behavior` of `kClamp`.");
221225
}
222226
break;
@@ -592,6 +596,10 @@ std::string ToFormattedString(BrushBehavior::Source source) {
592596
case BrushBehavior::Source::
593597
kInputAccelerationLateralInCentimetersPerSecondSquared:
594598
return "kInputAccelerationLateralInCentimetersPerSecondSquared";
599+
case BrushBehavior::Source::kTimeSinceInputsFinishedInSeconds:
600+
return "kTimeSinceInputsFinishedInSeconds";
601+
case BrushBehavior::Source::kTimeSinceInputsFinishedInMillis:
602+
return "kTimeSinceInputsFinishedInMillis";
595603
}
596604
return absl::StrCat("Source(", static_cast<int>(source), ")");
597605
}

ink/brush/brush_behavior.h

+7
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,13 @@ struct BrushBehavior {
230230
// Y-axis (and a negative value indicates acceleration along the negative
231231
// Y-axis).
232232
kInputAccelerationLateralInCentimetersPerSecondSquared,
233+
// The amount of time that has elapsed since all stroke inputs have
234+
// finished, or null if the stroke inputs aren't finished yet. This can be
235+
// used to drive a final animation on the stroke geometry. These enumerators
236+
// are only compatible with a `source_out_of_range_behavior` of `kClamp`, to
237+
// ensure that the animation will eventually end.
238+
kTimeSinceInputsFinishedInSeconds,
239+
kTimeSinceInputsFinishedInMillis,
233240
// TODO: b/336565152 - Add kInputDistanceRemainingInCentimeters (this will
234241
// require some refactoring for the code that calculates
235242
// BrushTipModeler::distance_remaining_behavior_upper_bound_).

ink/brush/brush_behavior_test.cc

+7-1
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,12 @@ TEST(BrushBehaviorTest, StringifySource) {
136136
absl::StrCat(BrushBehavior::Source::
137137
kInputAccelerationLateralInCentimetersPerSecondSquared),
138138
"kInputAccelerationLateralInCentimetersPerSecondSquared");
139+
EXPECT_EQ(
140+
absl::StrCat(BrushBehavior::Source::kTimeSinceInputsFinishedInSeconds),
141+
"kTimeSinceInputsFinishedInSeconds");
142+
EXPECT_EQ(
143+
absl::StrCat(BrushBehavior::Source::kTimeSinceInputsFinishedInMillis),
144+
"kTimeSinceInputsFinishedInMillis");
139145
EXPECT_EQ(absl::StrCat(static_cast<BrushBehavior::Source>(123)),
140146
"Source(123)");
141147
}
@@ -562,7 +568,7 @@ TEST(BrushBehaviorTest, ValidateSourceNode) {
562568
});
563569
EXPECT_EQ(status.code(), absl::StatusCode::kInvalidArgument);
564570
EXPECT_THAT(status.message(),
565-
HasSubstr("kTimeSinceInput*` must only be used with "
571+
HasSubstr("kTimeSince*` must only be used with "
566572
"`source_out_of_range_behavior` of `kClamp"));
567573

568574
status = brush_internal::ValidateBrushBehaviorNode(BrushBehavior::SourceNode{

ink/brush/brush_family_test.cc

+2-2
Original file line numberDiff line numberDiff line change
@@ -568,7 +568,7 @@ TEST(BrushFamilyTest, CreateWithInvalidBehaviorSourceAndOutOfRangeBehavior) {
568568
absl::Status status = BrushFamily::Create(brush_tip, BrushPaint{}).status();
569569
EXPECT_EQ(status.code(), kInvalidArgument);
570570
EXPECT_THAT(status.message(),
571-
HasSubstr("kTimeSinceInput*` must only be used with "
571+
HasSubstr("kTimeSince*` must only be used with "
572572
"`source_out_of_range_behavior` of `kClamp"));
573573

574574
source_node->source = BrushBehavior::Source::kTimeSinceInputInMillis;
@@ -577,7 +577,7 @@ TEST(BrushFamilyTest, CreateWithInvalidBehaviorSourceAndOutOfRangeBehavior) {
577577
status = BrushFamily::Create(brush_tip, BrushPaint{}).status();
578578
EXPECT_EQ(status.code(), kInvalidArgument);
579579
EXPECT_THAT(status.message(),
580-
HasSubstr("kTimeSinceInput*` must only be used with "
580+
HasSubstr("kTimeSince*` must only be used with "
581581
"`source_out_of_range_behavior` of `kClamp"));
582582
}
583583

ink/brush/fuzz_domains.cc

+4
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,8 @@ Domain<BrushBehavior::OutOfRange> ValidBrushBehaviorOutOfRangeForSource(
159159
switch (source) {
160160
case BrushBehavior::Source::kTimeSinceInputInSeconds:
161161
case BrushBehavior::Source::kTimeSinceInputInMillis:
162+
case BrushBehavior::Source::kTimeSinceInputsFinishedInSeconds:
163+
case BrushBehavior::Source::kTimeSinceInputsFinishedInMillis:
162164
return Just(BrushBehavior::OutOfRange::kClamp);
163165
default:
164166
return ArbitraryBrushBehaviorOutOfRange();
@@ -213,6 +215,8 @@ Domain<BrushBehavior::Source> ArbitraryBrushBehaviorSource() {
213215
kInputAccelerationForwardInCentimetersPerSecondSquared,
214216
BrushBehavior::Source::
215217
kInputAccelerationLateralInCentimetersPerSecondSquared,
218+
BrushBehavior::Source::kTimeSinceInputsFinishedInSeconds,
219+
BrushBehavior::Source::kTimeSinceInputsFinishedInMillis,
216220
});
217221
}
218222
// LINT.ThenChange(brush_behavior.h:source)

ink/strokes/in_progress_stroke.cc

+3
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,9 @@ absl::Status InProgressStroke::UpdateShape(Duration32 current_elapsed_time) {
129129
for (uint32_t i = 0; i < num_coats; ++i) {
130130
StrokeShapeUpdate update = shape_builders_[i].ExtendStroke(
131131
queued_real_inputs_, queued_predicted_inputs_, current_elapsed_time);
132+
if (inputs_are_finished_) {
133+
shape_builders_[i].FinishStrokeInputs();
134+
}
132135

133136
updated_region_.Add(update.region);
134137
// TODO: b/286547863 - Pass `update.first_vertex_offset` and

ink/strokes/internal/brush_tip_modeler.cc

+4
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,8 @@ float DistanceRemainingUpperBound(const BrushBehavior::SourceNode& node,
139139
kInputAccelerationForwardInCentimetersPerSecondSquared:
140140
case BrushBehavior::Source::
141141
kInputAccelerationLateralInCentimetersPerSecondSquared:
142+
case BrushBehavior::Source::kTimeSinceInputsFinishedInSeconds:
143+
case BrushBehavior::Source::kTimeSinceInputsFinishedInMillis:
142144
return 0;
143145
}
144146
ABSL_LOG(FATAL)
@@ -149,8 +151,10 @@ float DistanceRemainingUpperBound(const BrushBehavior::SourceNode& node,
149151
Duration32 TimeRemainingUpperBound(const BrushBehavior::SourceNode& node) {
150152
switch (node.source) {
151153
case BrushBehavior::Source::kTimeSinceInputInSeconds:
154+
case BrushBehavior::Source::kTimeSinceInputsFinishedInSeconds:
152155
return Duration32::Seconds(SourceValueUpperBound(node));
153156
case BrushBehavior::Source::kTimeSinceInputInMillis:
157+
case BrushBehavior::Source::kTimeSinceInputsFinishedInMillis:
154158
return Duration32::Millis(SourceValueUpperBound(node));
155159
case BrushBehavior::Source::kNormalizedPressure:
156160
case BrushBehavior::Source::kTiltInRadians:

ink/strokes/internal/brush_tip_modeler_helpers.cc

+10
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,16 @@ std::optional<float> GetSourceValue(
231231
return Vec::DotProduct(input.acceleration,
232232
input.velocity.AsUnitVec().Orthogonal()) *
233233
input_modeler_state.stroke_unit_length->ToCentimeters();
234+
case BrushBehavior::Source::kTimeSinceInputsFinishedInSeconds:
235+
if (!input_modeler_state.inputs_are_finished) break;
236+
return (input_modeler_state.complete_elapsed_time -
237+
input_modeler_state.total_real_elapsed_time)
238+
.ToSeconds();
239+
case BrushBehavior::Source::kTimeSinceInputsFinishedInMillis:
240+
if (!input_modeler_state.inputs_are_finished) break;
241+
return (input_modeler_state.complete_elapsed_time -
242+
input_modeler_state.total_real_elapsed_time)
243+
.ToMillis();
234244
}
235245
return std::nullopt;
236246
}

ink/strokes/internal/brush_tip_modeler_helpers_test.cc

+42
Original file line numberDiff line numberDiff line change
@@ -782,6 +782,48 @@ TEST_F(ProcessBehaviorNodeTest,
782782
EXPECT_THAT(stack_, ElementsAre(NullNodeValueMatcher()));
783783
}
784784

785+
TEST_F(ProcessBehaviorNodeTest, SourceNodeTimeSinceInputsFinishedInSeconds) {
786+
BrushBehavior::SourceNode source_node = {
787+
.source = BrushBehavior::Source::kTimeSinceInputsFinishedInSeconds,
788+
.source_value_range = {0, 10},
789+
};
790+
791+
// If `inputs_are_finished` is still `false`, the source node emits a null
792+
// value.
793+
input_modeler_state_.total_real_elapsed_time = Duration32::Seconds(3);
794+
input_modeler_state_.complete_elapsed_time = Duration32::Seconds(5);
795+
input_modeler_state_.inputs_are_finished = false;
796+
ProcessBehaviorNode(source_node, context_);
797+
EXPECT_THAT(stack_, ElementsAre(NullNodeValueMatcher()));
798+
799+
// Once `inputs_are_finished` is `true`, the source node emits its value.
800+
stack_.clear();
801+
input_modeler_state_.inputs_are_finished = true;
802+
ProcessBehaviorNode(source_node, context_);
803+
EXPECT_THAT(stack_, ElementsAre(0.2f));
804+
}
805+
806+
TEST_F(ProcessBehaviorNodeTest, SourceNodeTimeSinceInputsFinishedInMillis) {
807+
BrushBehavior::SourceNode source_node = {
808+
.source = BrushBehavior::Source::kTimeSinceInputsFinishedInMillis,
809+
.source_value_range = {0, 10000},
810+
};
811+
812+
// If `inputs_are_finished` is still `false`, the source node emits a null
813+
// value.
814+
input_modeler_state_.total_real_elapsed_time = Duration32::Seconds(3);
815+
input_modeler_state_.complete_elapsed_time = Duration32::Seconds(5);
816+
input_modeler_state_.inputs_are_finished = false;
817+
ProcessBehaviorNode(source_node, context_);
818+
EXPECT_THAT(stack_, ElementsAre(NullNodeValueMatcher()));
819+
820+
// Once `inputs_are_finished` is `true`, the source node emits its value.
821+
stack_.clear();
822+
input_modeler_state_.inputs_are_finished = true;
823+
ProcessBehaviorNode(source_node, context_);
824+
EXPECT_THAT(stack_, ElementsAre(0.2f));
825+
}
826+
785827
TEST_F(ProcessBehaviorNodeTest, SourceNodeOutOfRangeClamp) {
786828
BrushBehavior::SourceNode source_node = {
787829
.source = BrushBehavior::Source::kNormalizedPressure,

ink/strokes/internal/stroke_input_modeler.cc

+3
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,9 @@ void StrokeInputModeler::ExtendStroke(const StrokeInputBatch& real_inputs,
134134
const StrokeInputBatch& predicted_inputs,
135135
Duration32 current_elapsed_time) {
136136
ABSL_CHECK_GT(brush_epsilon_, 0) << "`StartStroke()` has not been called.";
137+
ABSL_CHECK(!state_.inputs_are_finished ||
138+
(real_inputs.IsEmpty() && predicted_inputs.IsEmpty()))
139+
<< "Can't add more inputs after calling `FinishStrokeInputs()`.";
137140

138141
absl::Cleanup update_time_and_distance = [&]() {
139142
UpdateStateTimeAndDistance(current_elapsed_time);

ink/strokes/internal/stroke_input_modeler.h

+14-1
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,9 @@ class StrokeInputModeler {
122122
// of this member variable will be non-decreasing over the course of a
123123
// single stroke.
124124
size_t real_input_count = 0;
125+
// True if inputs are finished for this stroke (in which case, no more real
126+
// or predicted inputs will be added).
127+
bool inputs_are_finished = false;
125128
};
126129

127130
StrokeInputModeler() = default;
@@ -148,11 +151,17 @@ class StrokeInputModeler {
148151
//
149152
// This always clears any previously generated unstable modeled inputs. Either
150153
// or both of `real_inputs` and `predicted_inputs` may be empty. CHECK-fails
151-
// if `StartStroke()` has not been called at least once.
154+
// if `StartStroke()` has not been called at least once, or if
155+
// `FinishStrokeInputs()` has been called for this stroke and `real_inputs`
156+
// and `predicted_inputs` aren't both empty.
152157
void ExtendStroke(const StrokeInputBatch& real_inputs,
153158
const StrokeInputBatch& predicted_inputs,
154159
Duration32 current_elapsed_time);
155160

161+
// Indicates that the inputs for the current stroke are finished. This method
162+
// is idempotent.
163+
void FinishStrokeInputs();
164+
156165
const State& GetState() const { return state_; }
157166
absl::Span<const ModeledStrokeInput> GetModeledInputs() const {
158167
return modeled_inputs_;
@@ -184,6 +193,10 @@ class StrokeInputModeler {
184193
State state_;
185194
};
186195

196+
inline void StrokeInputModeler::FinishStrokeInputs() {
197+
state_.inputs_are_finished = true;
198+
}
199+
187200
} // namespace ink::strokes_internal
188201

189202
#endif // INK_STROKES_INTERNAL_STROKE_INPUT_MODELER_H_

ink/strokes/internal/stroke_shape_builder.h

+11
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,9 @@ class StrokeShapeBuilder {
8080
// The `current_elapsed_time` should be the duration from the start of the
8181
// stroke until "now". The `elapsed_time` of inputs may be "in the future"
8282
// relative to this duration.
83+
//
84+
// CHECK-fails if `FinishStrokeInputs()` has been called for this stroke and
85+
// either `real_inputs` or `predicted_inputs` is non-empty.
8386
StrokeShapeUpdate ExtendStroke(const StrokeInputBatch& real_inputs,
8487
const StrokeInputBatch& predicted_inputs,
8588
Duration32 current_elapsed_time);
@@ -89,6 +92,10 @@ class StrokeShapeBuilder {
8992
// time (even in the absence of any new inputs).
9093
bool HasUnfinishedTimeBehaviors() const;
9194

95+
// Indicates that the inputs for the current stroke are finished. This method
96+
// is idempotent.
97+
void FinishStrokeInputs();
98+
9299
const MutableMesh& GetMesh() const;
93100

94101
const Envelope& GetMeshBounds() const;
@@ -128,6 +135,10 @@ class StrokeShapeBuilder {
128135
inline StrokeShapeBuilder::StrokeShapeBuilder()
129136
: mesh_(StrokeVertex::FullMeshFormat()) {}
130137

138+
inline void StrokeShapeBuilder::FinishStrokeInputs() {
139+
input_modeler_.FinishStrokeInputs();
140+
}
141+
131142
inline const MutableMesh& StrokeShapeBuilder::GetMesh() const { return mesh_; }
132143

133144
inline const Envelope& StrokeShapeBuilder::GetMeshBounds() const {

ink/strokes/internal/stroke_shape_builder_test.cc

+17
Original file line numberDiff line numberDiff line change
@@ -191,5 +191,22 @@ TEST(StrokeShapeBuilderDeathTest, ExtendWithoutStart) {
191191
"");
192192
}
193193

194+
TEST(StrokeShapeBuilderDeathTest, ExtendInputsAfterInputsFinished) {
195+
absl::StatusOr<StrokeInputBatch> inputs = StrokeInputBatch::Create({
196+
{.position = {5, 7}, .elapsed_time = Duration32::Zero()},
197+
});
198+
ASSERT_EQ(inputs.status(), absl::OkStatus());
199+
200+
StrokeShapeBuilder builder;
201+
std::vector<BrushTip> brush_tips = {BrushTip()};
202+
builder.StartStroke(BrushFamily::DefaultInputModel(), brush_tips, 1, 0.1);
203+
builder.ExtendStroke(*inputs, {}, Duration32::Zero());
204+
builder.FinishStrokeInputs();
205+
206+
EXPECT_DEATH_IF_SUPPORTED(
207+
builder.ExtendStroke(*inputs, {}, Duration32::Zero()),
208+
"Can't add more inputs");
209+
}
210+
194211
} // namespace
195212
} // namespace ink::strokes_internal

0 commit comments

Comments
 (0)