1use makepad_widgets::*;
2
3live_design! {
4 import makepad_widgets::base::*;
5 import makepad_widgets::theme_desktop_dark::*;
6 import crate::shared::styles::*;
7
8 ANIMATION_DURATION = 0.65
9 DEFAULT_SWING_TOP = 10.0
10 DEFAULT_SWING_BOTTOM = 3.0
11
12 // 1. Set the width and height to the same value.
13 // 2. Set the radius to half of the width/height.
14 EllipsisDot = <CircleView> {
15 width: 3
16 height: 3
17 draw_bg: {
18 radius: 1.5
19 color: (TYPING_NOTICE_TEXT_COLOR)
20 }
21 }
22
23 TypingAnimation = {{TypingAnimation}} {
24 width: Fit,
25 height: Fit,
26
27 // 添加可配置的动画参数
28 swing_amplitude: (DEFAULT_SWING_TOP), // 小球摆动的最大高度
29 swing_base: (DEFAULT_SWING_BOTTOM), // 小球摆动的基础高度
30 animation_duration: (ANIMATION_DURATION), // 动画周期
31
32 flow: Down,
33 align: {x: 0.0, y: 0.5},
34
35 content = <View> {
36 width: Fit,
37 height: Fit,
38 spacing: 2,
39 circle1 = <EllipsisDot> {}
40 circle2 = <EllipsisDot> {}
41 circle3 = <EllipsisDot> {}
42 }
43
44 animator: {
45 circle1 = {
46 default: down,
47 down = {
48 redraw: true,
49 from: {all: Forward {duration: (ANIMATION_DURATION * 0.5)}}
50 ease: InOutQuad
51 apply: {content = { circle1 = { margin: {top: (DEFAULT_SWING_TOP)} }}}
52 }
53 up = {
54 redraw: true,
55 from: {all: Forward {duration: (ANIMATION_DURATION * 0.5)}}
56 ease: InOutQuad
57 apply: {content = { circle1 = { margin: {top: (DEFAULT_SWING_BOTTOM)} }}}
58 }
59 }
60
61 circle2 = {
62 default: down,
63 down = {
64 redraw: true,
65 from: {all: Forward {duration: (ANIMATION_DURATION * 0.5)}}
66 ease: InOutQuad
67 apply: {content = { circle2 = { margin: {top: (DEFAULT_SWING_TOP)} }}}
68 }
69 up = {
70 redraw: true,
71 from: {all: Forward {duration: (ANIMATION_DURATION * 0.5)}}
72 ease: InOutQuad
73 apply: {content = { circle2 = { margin: {top: (DEFAULT_SWING_BOTTOM)} }}}
74 }
75 }
76
77 circle3 = {
78 default: down,
79 down = {
80 redraw: true,
81 from: {all: Forward {duration: (ANIMATION_DURATION * 0.5)}}
82 ease: InOutQuad
83 apply: {content = { circle3 = { margin: {top: (DEFAULT_SWING_TOP)} }}}
84 }
85 up = {
86 redraw: true,
87 from: {all: Forward {duration: (ANIMATION_DURATION * 0.5)}}
88 ease: InOutQuad
89 apply: {content = { circle3 = { margin: {top: (DEFAULT_SWING_BOTTOM)} }}}
90 }
91 }
92 }
93 }
94}
95
96#[derive(Live, Widget)]
97pub struct TypingAnimation {
98 #[deref] view: View,
99 #[animator] animator: Animator,
100
101 #[live(0.65)] animation_duration: f64,
102 #[live(10.0)] swing_amplitude: f64, // 摆动幅度
103 #[live(3.0)] swing_base: f64, // 基础高度
104
105 #[rust] next_frame: Option<NextFrame>,
106 #[rust] animation_start_time: f64,
107 #[rust] current_animated_dot: CurrentAnimatedDot,
108
109 #[rust] initialized: bool, // 添加初始化标志
110 #[rust] is_animating: bool, // 添加动画状态标志
111}
112
113impl LiveHook for TypingAnimation {
114 fn after_apply(&mut self, cx: &mut Cx, apply: &mut Apply, index: usize, nodes: &[LiveNode]) {
115 // 在组件完全应用后初始化
116 if !self.initialized {
117 self.initialized = true;
118 }
119 }
120}
121
122#[derive(Copy, Clone, Default)]
123enum CurrentAnimatedDot {
124 #[default]
125 Dot1,
126 Dot2,
127 Dot3,
128}
129impl CurrentAnimatedDot {
130 fn next(&self) -> Self {
131 match self {
132 Self::Dot1 => Self::Dot2,
133 Self::Dot2 => Self::Dot3,
134 Self::Dot3 => Self::Dot1,
135 }
136 }
137}
138
139impl Widget for TypingAnimation {
140 fn handle_event(&mut self, cx: &mut Cx, event: &Event, scope: &mut Scope) {
141 // 先检查初始化
142 if self.initialized && !self.is_animating {
143 self.init(cx);
144 }
145
146 match event {
147 Event::NextFrame(ne) => {
148 // 检查是否是我们的 next_frame
149 if let Some(next_frame) = self.next_frame {
150 if ne.set.contains(&next_frame) {
151 let elapsed = ne.time - self.animation_start_time;
152 if elapsed >= self.animation_duration * 0.5 {
153 // 更新动画
154 self.update_animation(cx, ne.time);
155 }
156 // 继续请求下一帧
157 self.next_frame = Some(cx.new_next_frame());
158 }
159 } else if self.initialized && !self.is_animating {
160 // 如果组件已初始化但没有动画,重新开始动画
161 self.animation_start_time = ne.time;
162 self.next_frame = Some(cx.new_next_frame());
163 self.update_animation(cx, ne.time);
164 self.is_animating = true;
165 }
166 }
167 Event::WindowGeomChange(_) => {
168 self.reset_animation(cx);
169 }
170 _ => {}
171 }
172
173 if self.animator_handle_event(cx, event).must_redraw() {
174 self.redraw(cx);
175 }
176
177 self.view.handle_event(cx, event, scope);
178 }
179
180 fn draw_walk(&mut self, cx: &mut Cx2d, scope: &mut Scope, walk: Walk) -> DrawStep {
181 self.view.draw_walk(cx, scope, walk)
182 }
183}
184
185impl TypingAnimation {
186
187 pub fn init(&mut self, cx: &mut Cx) {
188 self.animation_start_time = cx.seconds_since_app_start();
189 self.next_frame = Some(cx.new_next_frame());
190
191 if let Some(state) = &mut self.animator.state {
192 // 设置初始状态
193 state.push_live(live!{
194 state: {
195 content: {
196 circle1: { margin: { top: (self.swing_base) }},
197 circle2: { margin: { top: (self.swing_base) }},
198 circle3: { margin: { top: (self.swing_base) }}
199 }
200 }
201 });
202 }
203
204 // 立即开始第一个动画
205 self.update_animation(cx, self.animation_start_time);
206 self.is_animating = true; // 设为 true,表示动画已经开始
207 }
208
209 fn reset_animation(&mut self, cx: &mut Cx) {
210 self.animation_start_time = cx.seconds_since_app_start();
211 self.next_frame = Some(cx.new_next_frame());
212 self.current_animated_dot = CurrentAnimatedDot::default();
213 self.is_animating = false;
214 }
215 pub fn update_animation(&mut self, cx: &mut Cx, time: f64) {
216 self.current_animated_dot = self.current_animated_dot.next();
217 self.animation_start_time = time;
218
219 match self.current_animated_dot {
220 CurrentAnimatedDot::Dot1 => {
221 // 构建状态节点
222 let mut up_nodes = LiveNodeVec::new();
223 up_nodes.push_live(live_object!{
224 content: {
225 circle1: {
226 margin: {top: (self.swing_base)}
227 }
228 }
229 });
230
231 let mut down_nodes = LiveNodeVec::new();
232 down_nodes.push_live(live_object!{
233 content: {
234 circle3: {
235 margin: {top: (self.swing_amplitude)}
236 }
237 }
238 });
239
240 // 更新动画状态
241 if let Some(state) = &mut self.animator.state {
242 state.replace_or_insert_last_node_by_path(
243 0,
244 &[live_id!(circle1).as_field(), live_id!(up).as_field(), live_id!(apply).as_field()],
245 &up_nodes
246 );
247
248 state.replace_or_insert_last_node_by_path(
249 0,
250 &[live_id!(circle3).as_field(), live_id!(down).as_field(), live_id!(apply).as_field()],
251 &down_nodes
252 );
253 }
254
255 self.animator_play(cx, id!(circle1.up));
256 self.animator_play(cx, id!(circle3.down));
257 }
258 CurrentAnimatedDot::Dot2 => {
259 let mut up_nodes = LiveNodeVec::new();
260 up_nodes.push_live(live_object!{
261 content: {
262 circle2: {
263 margin: {top: (self.swing_base)}
264 }
265 }
266 });
267
268 let mut down_nodes = LiveNodeVec::new();
269 down_nodes.push_live(live_object!{
270 content: {
271 circle1: {
272 margin: {top: (self.swing_amplitude)}
273 }
274 }
275 });
276
277 if let Some(state) = &mut self.animator.state {
278 state.replace_or_insert_last_node_by_path(
279 0,
280 &[live_id!(circle2).as_field(), live_id!(up).as_field(), live_id!(apply).as_field()],
281 &up_nodes
282 );
283
284 state.replace_or_insert_last_node_by_path(
285 0,
286 &[live_id!(circle1).as_field(), live_id!(down).as_field(), live_id!(apply).as_field()],
287 &down_nodes
288 );
289 }
290
291 self.animator_play(cx, id!(circle1.down));
292 self.animator_play(cx, id!(circle2.up));
293 }
294 CurrentAnimatedDot::Dot3 => {
295 let mut up_nodes = LiveNodeVec::new();
296 up_nodes.push_live(live_object!{
297 content: {
298 circle3: {
299 margin: {top: (self.swing_base)}
300 }
301 }
302 });
303
304 let mut down_nodes = LiveNodeVec::new();
305 down_nodes.push_live(live_object!{
306 content: {
307 circle2: {
308 margin: {top: (self.swing_amplitude)}
309 }
310 }
311 });
312
313 if let Some(state) = &mut self.animator.state {
314 state.replace_or_insert_last_node_by_path(
315 0,
316 &[live_id!(circle3).as_field(), live_id!(up).as_field(), live_id!(apply).as_field()],
317 &up_nodes
318 );
319
320 state.replace_or_insert_last_node_by_path(
321 0,
322 &[live_id!(circle2).as_field(), live_id!(down).as_field(), live_id!(apply).as_field()],
323 &down_nodes
324 );
325 }
326
327 self.animator_play(cx, id!(circle2.down));
328 self.animator_play(cx, id!(circle3.up));
329 }
330 };
331 }
332
333 pub fn start(&mut self, cx: &mut Cx) {
334 // 如果动画没在运行,就启动它
335 if !self.is_animating {
336 self.initialized = true; // 确保初始化状态
337 self.is_animating = false; // 临时设为 false,这样 handle_event 会重新启动动画
338 self.reset_animation(cx); // 重置动画状态
339
340 // 强制立即开始一次动画循环
341 self.animation_start_time = cx.seconds_since_app_start();
342 self.next_frame = Some(cx.new_next_frame());
343 self.update_animation(cx, self.animation_start_time);
344 }
345 }
346
347 pub fn stop(&mut self, cx: &mut Cx) {
348 if self.is_animating {
349 self.is_animating = false; // 停止动画循环
350 self.next_frame = None; // 移除下一帧的请求
351
352 // 将所有点重置到基础位置
353 if let Some(state) = &mut self.animator.state {
354 state.push_live(live!{
355 state: {
356 content: {
357 circle1: { margin: { top: (self.swing_base) }},
358 circle2: { margin: { top: (self.swing_base) }},
359 circle3: { margin: { top: (self.swing_base) }}
360 }
361 }
362 });
363 }
364 self.redraw(cx); // 强制重绘以更新视觉状态
365 }
366 }
367
368}
369
370
371/// // 设置小幅度快速摆动
372/// typing_animation.set_swing_parameters(cx, 5.0, 2.0); // 小幅度
373/// typing_animation.set_animation_speed(cx, 0.3); // 更快的速度
374///
375/// // 设置大幅度慢速摆动
376/// typing_animation.set_swing_parameters(cx, 15.0, 3.0); // 大幅度
377/// typing_animation.set_animation_speed(cx, 1.0); // 更慢的速度
378impl TypingAnimationRef {
379 // 提供设置动画参数的方法
380 pub fn set_swing_parameters(&self, cx: &mut Cx, amplitude: f64, base: f64) {
381 if let Some(mut inner) = self.borrow_mut() {
382 inner.swing_amplitude = amplitude;
383 inner.swing_base = base;
384 // 如果动画正在进行,需要重新应用参数
385 if inner.next_frame.is_some() {
386 let time = cx.seconds_since_app_start();
387 inner.update_animation(cx, time);
388 }
389 }
390 }
391
392 pub fn set_animation_speed(&self, cx: &mut Cx, duration: f64) {
393 if let Some(mut inner) = self.borrow_mut() {
394 inner.animation_duration = duration;
395 // 如果动画正在进行,需要重新应用参数
396 if inner.next_frame.is_some() {
397 let time = cx.seconds_since_app_start();
398 inner.update_animation(cx, time);
399 }
400 }
401 }
402
403 pub fn start(&self, cx: &mut Cx) {
404 // self.set_swing_parameters(cx, 5.0, 2.0);
405 self.set_animation_speed(cx, 0.3);
406 if let Some(mut inner) = self.borrow_mut() {
407 inner.start(cx);
408 }
409 }
410
411 pub fn stop(&self, cx: &mut Cx) {
412 if let Some(mut inner) = self.borrow_mut() {
413 inner.stop(cx);
414 }
415 }
416}