Files
MusicPlayer/common/animated_progress_bar.gd
T
SansanandClaude Opus 5.5 0862d160e5
Build / test (push) Successful in 1m55s
Build / build (push) Successful in 1m12s
Give the sync progress bar colour and animation
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>
2026-10-16 11:13:43 +02:00

119 lines
4.1 KiB
GDScript

class_name AnimatedProgressBar
extends ProgressBar
## A progress bar with some life in it:
## - [method wait] shows Godot's busy animation until there is any progress;
## - [method move_to] glides to each new value instead of jumping;
## - a soft highlight sweeps along the filled part, kept inside its rounded corners.
##
## The bar's style, e.g. [code]SyncProgressBar[/code], has a [code]shine[/code] colour for
## the highlight, and two more styles go with it: [code]<style>Fill[/code], a Panel shaped
## like the filled part that the highlight is kept inside, and [code]<style>Text[/code],
## the Label with the percentage. With Reduce animations the bar only jumps to its values,
## with no busy animation or highlight.
## How wide the highlight is, in pixels.
const SHINE_WIDTH: float = 260.0
## How fast the highlight moves, in pixels per second.
const SHINE_SPEED: float = 520.0
## How far past the end the highlight goes before starting over, so sweeps have a pause.
const SHINE_GAP: float = 600.0
# The filled part's shape, which only its children are drawn inside
var _mask: Panel
var _shine: TextureRect
# The percentage, drawn above the highlight rather than under it
var _label: Label
var _shine_x: float = -SHINE_WIDTH
func _ready() -> void:
_mask = Panel.new()
_mask.mouse_filter = Control.MOUSE_FILTER_IGNORE
_mask.clip_children = CanvasItem.CLIP_CHILDREN_ONLY
add_child(_mask)
_shine = TextureRect.new()
_shine.mouse_filter = Control.MOUSE_FILTER_IGNORE
_shine.expand_mode = TextureRect.EXPAND_IGNORE_SIZE
_shine.stretch_mode = TextureRect.STRETCH_SCALE
_mask.add_child(_shine)
_label = Label.new()
_label.mouse_filter = Control.MOUSE_FILTER_IGNORE
_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
_label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
_label.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
_label.visible = show_percentage
add_child(_label)
show_percentage = false
_mask.theme_type_variation = StringName(String(theme_type_variation) + "Fill")
_label.theme_type_variation = StringName(String(theme_type_variation) + "Text")
theme_changed.connect(_update_style)
value_changed.connect(func(_value: float) -> void: _update_label())
_update_style()
_update_label()
func _process(delta: float) -> void:
var fill_width: float = fill_width()
_mask.size = Vector2(fill_width, size.y)
_mask.visible = fill_width > 0.0 && !indeterminate && Anim.enabled()
if !_mask.visible:
return
_shine_x += SHINE_SPEED * delta
if _shine_x > fill_width + SHINE_GAP:
_shine_x = -SHINE_WIDTH
_shine.position = Vector2(_shine_x, 0.0)
_shine.size = Vector2(SHINE_WIDTH, size.y)
## Shows the busy animation, with no value, until [method move_to] has some progress.
func wait() -> void:
Anim.stop(self, &"progress")
value = min_value
indeterminate = Anim.enabled()
_update_label()
## Glides to [param target]. Any progress ends the busy animation.
func move_to(target: float) -> void:
if target > min_value:
indeterminate = false
var glide: Tween = Anim.tween(self, &"progress")
if glide == null:
value = target
return
glide.tween_property(self, "value", target, Anim.SLOW) \
.set_trans(Tween.TRANS_QUAD).set_ease(Tween.EASE_OUT)
## How wide the filled part is, the way [ProgressBar] draws it.
func fill_width() -> float:
var margins: float = get_theme_stylebox(&"fill").get_minimum_size().x
var filled: float = roundf(ratio_done() * (size.x - margins))
return filled + margins if filled > 0.0 else 0.0
func _update_style() -> void:
var shine: Color = get_theme_color(&"shine")
var clear: Color = shine
clear.a = 0.0
var gradient: Gradient = Gradient.new()
gradient.offsets = PackedFloat32Array([0.0, 0.5, 1.0])
gradient.colors = PackedColorArray([clear, shine, clear])
var texture: GradientTexture2D = GradientTexture2D.new()
texture.gradient = gradient
texture.width = 256
texture.height = 1
_shine.texture = texture
func _update_label() -> void:
if _label == null:
return
_label.text = "" if indeterminate else "%d%%" % roundi(ratio_done() * 100.0)
## How much is done, from 0 to 1.
func ratio_done() -> float:
var span: float = max_value - min_value
return clampf((value - min_value) / span, 0.0, 1.0) if span > 0.0 else 0.0