The bar in the Sync popup is now an AnimatedProgressBar: taller, with a darker track and the theme's accent fill (orange, frost blue in Nord, green with a gold shine for Christmas). It shows Godot's busy animation until the server reports progress, glides to each new value, has a soft highlight sweeping along the filled part inside its rounded corners, and fills and pops before the result shows. With Reduce animations it only jumps to its values. The styles are SyncProgressBar, SyncProgressBarFill and SyncProgressBarText in every theme. Anim.stop stops a keyed animation without starting an empty one. The server now sends sync times as "MM:SS"; the spec is updated from MusicServer develop (also the linux-package route), and the test data and screenshots use the new format. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
146 lines
5.5 KiB
GDScript
146 lines
5.5 KiB
GDScript
class_name Anim
|
|
extends RefCounted
|
|
## Short animations made with tweens, used all over the game: popping, fading, flashing
|
|
## and pulsing.
|
|
##
|
|
## Every animation is short so a quiz never waits for one. With
|
|
## [member Settings.reduce_animations] on, nothing moves: each helper jumps straight to
|
|
## where the animation would end.
|
|
|
|
## How long quick feedback, such as a button press, takes.
|
|
const FAST: float = 0.12
|
|
## How long most animations take.
|
|
const NORMAL: float = 0.2
|
|
## How long flashes and bigger moments take.
|
|
const SLOW: float = 0.45
|
|
|
|
|
|
## Whether animations are on.
|
|
static func enabled() -> bool:
|
|
return !Settings.reduce_animations
|
|
|
|
|
|
## A new tween on [param node] for the animation called [param key], stopping an earlier
|
|
## one with the same key so they don't fight. Returns null when animations are off or the
|
|
## node is not in the tree.
|
|
static func tween(node: Node, key: StringName = &"anim") -> Tween:
|
|
stop(node, key)
|
|
if !enabled() || !node.is_inside_tree():
|
|
return null
|
|
var new_tween: Tween = node.create_tween()
|
|
node.set_meta(StringName("_tween_" + key), new_tween)
|
|
return new_tween
|
|
|
|
|
|
## Stops the animation called [param key] on [param node], if one is running.
|
|
static func stop(node: Node, key: StringName = &"anim") -> void:
|
|
var meta: StringName = StringName("_tween_" + key)
|
|
var old: Variant = node.get_meta(meta) if node.has_meta(meta) else null
|
|
if old is Tween && (old as Tween).is_valid():
|
|
(old as Tween).kill()
|
|
|
|
|
|
## Makes [param control] grow from its middle.
|
|
static func center_pivot(control: Control) -> void:
|
|
control.pivot_offset = control.size / 2
|
|
|
|
|
|
## Makes [param control] jump to [param peak] times its size and spring back.
|
|
static func pop(control: Control, peak: float = 1.3, duration: float = NORMAL) -> void:
|
|
var pop_tween: Tween = tween(control, &"pop")
|
|
control.scale = Vector2.ONE
|
|
if pop_tween == null:
|
|
return
|
|
center_pivot(control)
|
|
control.scale = Vector2.ONE * peak
|
|
pop_tween.tween_property(control, "scale", Vector2.ONE, duration) \
|
|
.set_trans(Tween.TRANS_BACK).set_ease(Tween.EASE_OUT)
|
|
|
|
|
|
## Makes [param control] grow from [param from] times its size with a bounce.
|
|
static func bounce_in(control: Control, from: float = 0.4, duration: float = SLOW) -> void:
|
|
var bounce_tween: Tween = tween(control, &"pop")
|
|
control.scale = Vector2.ONE
|
|
if bounce_tween == null:
|
|
return
|
|
center_pivot(control)
|
|
control.scale = Vector2.ONE * from
|
|
bounce_tween.tween_property(control, "scale", Vector2.ONE, duration * 1.6) \
|
|
.set_trans(Tween.TRANS_ELASTIC).set_ease(Tween.EASE_OUT)
|
|
|
|
|
|
## Fades [param item] in from invisible.
|
|
static func fade_in(item: CanvasItem, duration: float = NORMAL) -> void:
|
|
var fade_tween: Tween = tween(item, &"fade")
|
|
item.modulate.a = 1.0
|
|
if fade_tween == null:
|
|
return
|
|
item.modulate.a = 0.0
|
|
fade_tween.tween_property(item, "modulate:a", 1.0, duration)
|
|
|
|
|
|
## Slides [param control] in from [param offset] away from where it is, fading in.
|
|
static func slide_in(control: Control, offset: Vector2, duration: float = NORMAL) -> void:
|
|
var slide_tween: Tween = tween(control, &"slide")
|
|
if control.has_meta(&"_slide_home"):
|
|
control.position = control.get_meta(&"_slide_home")
|
|
control.modulate.a = 1.0
|
|
if slide_tween == null:
|
|
return
|
|
var home: Vector2 = control.position
|
|
control.set_meta(&"_slide_home", home)
|
|
control.position = home + offset
|
|
control.modulate.a = 0.0
|
|
slide_tween.set_parallel()
|
|
slide_tween.tween_property(control, "position", home, duration) \
|
|
.set_trans(Tween.TRANS_QUAD).set_ease(Tween.EASE_OUT)
|
|
slide_tween.tween_property(control, "modulate:a", 1.0, duration)
|
|
slide_tween.chain().tween_callback(func() -> void: control.remove_meta(&"_slide_home"))
|
|
|
|
|
|
## Scales [param control] to [param target] times its size, e.g. a little bigger under
|
|
## the mouse.
|
|
static func scale_to(control: Control, target: float, duration: float = FAST) -> void:
|
|
var scale_tween: Tween = tween(control, &"scale")
|
|
if scale_tween == null:
|
|
control.scale = Vector2.ONE
|
|
return
|
|
center_pivot(control)
|
|
scale_tween.tween_property(control, "scale", Vector2.ONE * target, duration) \
|
|
.set_trans(Tween.TRANS_QUAD).set_ease(Tween.EASE_OUT)
|
|
|
|
|
|
## Lights up [param container] with [param colour] for a moment, e.g. a row that just
|
|
## changed. A coloured layer is laid over the container and fades away.
|
|
static func flash(container: Container, colour: Color, duration: float = SLOW) -> void:
|
|
if !enabled() || !container.is_inside_tree():
|
|
return
|
|
var layer: ColorRect = _layer(container, colour)
|
|
var flash_tween: Tween = layer.create_tween()
|
|
flash_tween.tween_property(layer, "color:a", 0.0, duration) \
|
|
.set_trans(Tween.TRANS_QUAD).set_ease(Tween.EASE_IN)
|
|
flash_tween.tween_callback(layer.queue_free)
|
|
|
|
|
|
## Keeps [param container] glowing in [param colour], softly pulsing, until the returned
|
|
## layer is freed. With animations off the glow stays still.
|
|
static func pulse(container: Container, colour: Color) -> ColorRect:
|
|
var layer: ColorRect = _layer(container, colour)
|
|
layer.name = "Pulse"
|
|
if enabled():
|
|
var pulse_tween: Tween = layer.create_tween().set_loops()
|
|
pulse_tween.tween_property(layer, "color:a", colour.a * 0.3, 0.6) \
|
|
.set_trans(Tween.TRANS_SINE)
|
|
pulse_tween.tween_property(layer, "color:a", colour.a, 0.6) \
|
|
.set_trans(Tween.TRANS_SINE)
|
|
return layer
|
|
|
|
|
|
# A layer of [param colour] over the whole of [param container] that lets clicks through
|
|
static func _layer(container: Container, colour: Color) -> ColorRect:
|
|
var layer: ColorRect = ColorRect.new()
|
|
layer.color = colour
|
|
layer.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
|
container.add_child(layer)
|
|
return layer
|