diff --git a/common/button_bounce.gd b/common/button_bounce.gd new file mode 100644 index 0000000..f1d48c2 --- /dev/null +++ b/common/button_bounce.gd @@ -0,0 +1,69 @@ +class_name ButtonBounce +extends RefCounted +## Small animations that make buttons feel alive: a button grows a little under the mouse, +## shrinks while it is held down, and can pop when its icon changes. +## +## The button is scaled around its middle, so its place in a container does not change. + +## How big a button is under the mouse. +const HOVER_SCALE: Vector2 = Vector2(1.08, 1.08) +## How big a button is while it is held down. +const PRESSED_SCALE: Vector2 = Vector2(0.92, 0.92) +## How small a button starts when it pops. +const POP_SCALE: Vector2 = Vector2(0.8, 0.8) +## How long growing or shrinking takes, in seconds. +const DURATION: float = 0.08 +## How long a pop takes, in seconds. +const POP_DURATION: float = 0.18 + +const _TWEEN_META: StringName = &"button_bounce_tween" +const _PIVOT_META: StringName = &"button_bounce_pivot" + + +## Makes [param button] grow under the mouse and shrink while held down. +static func add_to(button: BaseButton) -> void: + _keep_pivot_centred(button) + button.mouse_entered.connect(func() -> void: + if !button.disabled: + _scale_to(button, HOVER_SCALE)) + button.mouse_exited.connect(func() -> void: _scale_to(button, Vector2.ONE)) + button.button_down.connect(func() -> void: _scale_to(button, PRESSED_SCALE)) + button.button_up.connect(func() -> void: _scale_to(button, resting_scale(button))) + + +## The size [param button] goes back to when it is not held down: bigger under the mouse. +static func resting_scale(button: BaseButton) -> Vector2: + return HOVER_SCALE if button.is_hovered() && !button.disabled else Vector2.ONE + + +## Shrinks [param button] and lets it spring back, e.g. when its icon changes. +static func pop(button: BaseButton) -> void: + _keep_pivot_centred(button) + button.scale = POP_SCALE + var tween: Tween = _new_tween(button) + tween.set_trans(Tween.TRANS_BACK).set_ease(Tween.EASE_OUT) + tween.tween_property(button, ^"scale", resting_scale(button), POP_DURATION) + + +static func _scale_to(button: BaseButton, target: Vector2) -> void: + var tween: Tween = _new_tween(button) + tween.set_trans(Tween.TRANS_QUAD).set_ease(Tween.EASE_OUT) + tween.tween_property(button, ^"scale", target, DURATION) + + +# A new tween for [param button], stopping the one before so they never fight +static func _new_tween(button: BaseButton) -> Tween: + if button.has_meta(_TWEEN_META): + var running: Tween = button.get_meta(_TWEEN_META) + if running.is_valid(): + running.kill() + var tween: Tween = button.create_tween() + button.set_meta(_TWEEN_META, tween) + return tween + + +static func _keep_pivot_centred(button: BaseButton) -> void: + button.pivot_offset = button.size / 2 + if !button.has_meta(_PIVOT_META): + button.set_meta(_PIVOT_META, true) + button.resized.connect(func() -> void: button.pivot_offset = button.size / 2) diff --git a/common/button_bounce.gd.uid b/common/button_bounce.gd.uid new file mode 100644 index 0000000..8c5d343 --- /dev/null +++ b/common/button_bounce.gd.uid @@ -0,0 +1 @@ +uid://c4vekt3m8q1p7 diff --git a/icons/pause_button_icon.svg b/icons/pause_button_icon.svg new file mode 100644 index 0000000..d3e8d87 --- /dev/null +++ b/icons/pause_button_icon.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/icons/pause_button_icon.svg.import b/icons/pause_button_icon.svg.import new file mode 100644 index 0000000..1a23ea9 --- /dev/null +++ b/icons/pause_button_icon.svg.import @@ -0,0 +1,43 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://djdsgbhw8gpj" +path="res://.godot/imported/pause_button_icon.svg-cf86876de39506a769c5610b329fdb75.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://icons/pause_button_icon.svg" +dest_files=["res://.godot/imported/pause_button_icon.svg-cf86876de39506a769c5610b329fdb75.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/icons/play_button_icon.svg b/icons/play_button_icon.svg new file mode 100644 index 0000000..d556145 --- /dev/null +++ b/icons/play_button_icon.svg @@ -0,0 +1,4 @@ + + + + diff --git a/icons/play_button_icon.svg.import b/icons/play_button_icon.svg.import new file mode 100644 index 0000000..a2506c7 --- /dev/null +++ b/icons/play_button_icon.svg.import @@ -0,0 +1,43 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dkweun1f426gl" +path="res://.godot/imported/play_button_icon.svg-76b19c7150b176f215ad401b4dd9488d.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://icons/play_button_icon.svg" +dest_files=["res://.godot/imported/play_button_icon.svg-76b19c7150b176f215ad401b4dd9488d.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/icons/restart_light_icon.svg b/icons/restart_light_icon.svg new file mode 100644 index 0000000..63a2e63 --- /dev/null +++ b/icons/restart_light_icon.svg @@ -0,0 +1,4 @@ + + + + diff --git a/icons/restart_light_icon.svg.import b/icons/restart_light_icon.svg.import new file mode 100644 index 0000000..9da2d5e --- /dev/null +++ b/icons/restart_light_icon.svg.import @@ -0,0 +1,43 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://4t7vk634k0v4" +path="res://.godot/imported/restart_light_icon.svg-3822a30546bff23ba1adb74c1723ba80.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://icons/restart_light_icon.svg" +dest_files=["res://.godot/imported/restart_light_icon.svg-3822a30546bff23ba1adb74c1723ba80.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/icons/volume_light_icon.svg b/icons/volume_light_icon.svg new file mode 100644 index 0000000..104ea8c --- /dev/null +++ b/icons/volume_light_icon.svg @@ -0,0 +1,4 @@ + + + + diff --git a/icons/volume_light_icon.svg.import b/icons/volume_light_icon.svg.import new file mode 100644 index 0000000..8e97865 --- /dev/null +++ b/icons/volume_light_icon.svg.import @@ -0,0 +1,43 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://d0uyxivsv58uh" +path="res://.godot/imported/volume_light_icon.svg-a1b08f489924a01123c9c4f7fa5e16a0.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://icons/volume_light_icon.svg" +dest_files=["res://.godot/imported/volume_light_icon.svg-a1b08f489924a01123c9c4f7fa5e16a0.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/player/MusicPlayer.tscn b/player/MusicPlayer.tscn index 10a1357..908de27 100644 --- a/player/MusicPlayer.tscn +++ b/player/MusicPlayer.tscn @@ -1,9 +1,10 @@ -[gd_scene load_steps=10 format=3 uid="uid://ds15cgsf8vpvc"] +[gd_scene format=3 uid="uid://ds15cgsf8vpvc"] [ext_resource type="Script" uid="uid://d1v0rquma6nt8" path="res://player/music_player.gd" id="1_t24ra"] [ext_resource type="AudioStream" uid="uid://n2g8jddr85h2" path="res://sounds/01. Opening.mp3" id="2_xti80"] -[ext_resource type="Texture2D" uid="uid://comxqfiykp54f" path="res://icons/play_icon_light.svg" id="3_6g308"] -[ext_resource type="Texture2D" uid="uid://ccb6rvbldlgdg" path="res://icons/reload_light_icon.svg" id="4_jleuo"] +[ext_resource type="Texture2D" path="res://icons/play_button_icon.svg" id="3_6g308"] +[ext_resource type="Texture2D" path="res://icons/restart_light_icon.svg" id="4_jleuo"] +[ext_resource type="Texture2D" path="res://icons/volume_light_icon.svg" id="6_volume"] [ext_resource type="PackedScene" uid="uid://w400rnew7453" path="res://player/VolumeSlider.tscn" id="5_iifuj"] [sub_resource type="InputEventKey" id="InputEventKey_r4qo3"] @@ -29,7 +30,7 @@ events = [SubResource("InputEventKey_2nvce")] [node name="MusicPlayer" type="Control"] layout_mode = 3 offset_right = 1792.0 -offset_bottom = 71.0 +offset_bottom = 42.0 mouse_filter = 2 script = ExtResource("1_t24ra") @@ -46,52 +47,81 @@ stream = ExtResource("2_xti80") bus = &"music" [node name="PlayerBar" type="PanelContainer" parent="."] -layout_mode = 0 -offset_left = 1.0 -offset_right = 1792.0 -offset_bottom = 32.0 +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +mouse_filter = 2 +theme_type_variation = &"PlayerBar" [node name="MusicPlayerContainer" type="HBoxContainer" parent="PlayerBar"] layout_mode = 2 -size_flags_vertical = 4 +mouse_filter = 2 +theme_override_constants/separation = 10 -[node name="PlayTextureButton" type="TextureButton" parent="PlayerBar/MusicPlayerContainer"] +[node name="PlayButton" type="Button" parent="PlayerBar/MusicPlayerContainer"] unique_name_in_owner = true -custom_minimum_size = Vector2(32, 32) +custom_minimum_size = Vector2(34, 34) layout_mode = 2 +size_flags_vertical = 4 +tooltip_text = "Play or pause (Alt+X)" focus_mode = 0 +theme_type_variation = &"PlayerButtonPrimary" action_mode = 0 shortcut = SubResource("Shortcut_ipcfh") -texture_normal = ExtResource("3_6g308") -ignore_texture_size = true -stretch_mode = 0 +shortcut_feedback = false +shortcut_in_tooltip = false +icon = ExtResource("3_6g308") +icon_alignment = 1 +expand_icon = true -[node name="RestartTextureButton" type="TextureButton" parent="PlayerBar/MusicPlayerContainer"] +[node name="RestartButton" type="Button" parent="PlayerBar/MusicPlayerContainer"] unique_name_in_owner = true -custom_minimum_size = Vector2(32, 32) +custom_minimum_size = Vector2(34, 34) layout_mode = 2 +size_flags_vertical = 4 +tooltip_text = "Restart the song (Alt+Z)" focus_mode = 0 +theme_type_variation = &"PlayerButton" action_mode = 0 shortcut = SubResource("Shortcut_i7swq") -texture_normal = ExtResource("4_jleuo") -ignore_texture_size = true -stretch_mode = 0 -flip_h = true +shortcut_feedback = false +shortcut_in_tooltip = false +icon = ExtResource("4_jleuo") +icon_alignment = 1 +expand_icon = true [node name="MusicPlayerSlider" type="HSlider" parent="PlayerBar/MusicPlayerContainer"] unique_name_in_owner = true layout_mode = 2 size_flags_horizontal = 3 size_flags_vertical = 4 +theme_type_variation = &"ProgressSlider" scrollable = false [node name="MusicTimeLabel" type="Label" parent="PlayerBar/MusicPlayerContainer"] unique_name_in_owner = true +custom_minimum_size = Vector2(120, 0) layout_mode = 2 -text = "0:00 / 0:00" +theme_type_variation = &"PlayerTime" +text = "00:00 / 00:00" +horizontal_alignment = 1 + +[node name="VolumeIcon" type="TextureRect" parent="PlayerBar/MusicPlayerContainer"] +custom_minimum_size = Vector2(22, 22) +layout_mode = 2 +size_flags_vertical = 4 +mouse_filter = 2 +texture = ExtResource("6_volume") +expand_mode = 1 +stretch_mode = 5 [node name="VolumeSlider" parent="PlayerBar/MusicPlayerContainer" instance=ExtResource("5_iifuj")] unique_name_in_owner = true -custom_minimum_size = Vector2(100, 0) +custom_minimum_size = Vector2(110, 0) layout_mode = 2 size_flags_vertical = 4 +tooltip_text = "Volume" +theme_type_variation = &"VolumeBar" diff --git a/player/music_player.gd b/player/music_player.gd index 6fffd72..f619e32 100644 --- a/player/music_player.gd +++ b/player/music_player.gd @@ -2,19 +2,22 @@ class_name MusicPlayer extends Control ## The bar that plays the songs, with play, restart, progress and volume. Whether a song ## repeats is a quiz setting ([code]Settings.auto_repeat_song[/code]). +## +## The buttons grow a little under the mouse and the play button pops when it switches +## between play and pause, see [ButtonBounce]. signal update_song_list signal play_next_song -var play_icon: Texture = preload("res://icons/play_icon_light.svg") -var pause_icon: Texture = preload("res://icons/pause_icon_light.svg") +var play_icon: Texture = preload("res://icons/play_button_icon.svg") +var pause_icon: Texture = preload("res://icons/pause_button_icon.svg") var is_changing: bool = false var playback_position: float var stream: AudioStream var song_finished: bool = false -@onready var play_button: TextureButton = %PlayTextureButton -@onready var restart_button: TextureButton = %RestartTextureButton +@onready var play_button: Button = %PlayButton +@onready var restart_button: Button = %RestartButton @onready var audio_player: AudioStreamPlayer = $AudioStreamPlayer @onready var sound_player: AudioStreamPlayer = $AudioSoundPlayer @onready var sound_effect_player: AudioStreamPlayer = $AudioSoundEffectPlayer @@ -26,6 +29,8 @@ var song_finished: bool = false func _ready() -> void: play_button.pressed.connect(play_or_pause) restart_button.pressed.connect(restart) + ButtonBounce.add_to(play_button) + ButtonBounce.add_to(restart_button) progress_slider.drag_started.connect(_on_drag_started) progress_slider.drag_ended.connect(_on_drag_ended) @@ -43,6 +48,19 @@ func _process(_delta: float) -> void: music_time_label.text = format_text(progress_slider.value, 0.0) +## Whether the play button shows pause, because a song is playing. +func shows_playing() -> bool: + return play_button.icon == pause_icon + + +# Shows pause on the play button while a song plays, and play otherwise +func _show_playing(playing: bool) -> void: + var icon: Texture = pause_icon if playing else play_icon + if play_button.icon != icon: + play_button.icon = icon + ButtonBounce.pop(play_button) + + func format_time(time: float) -> String: var total_seconds: int = roundi(time) @warning_ignore("integer_division") @@ -64,7 +82,7 @@ func play_or_pause() -> void: if song_finished: restart() elif audio_player.stream_paused: - play_button.texture_normal = pause_icon + _show_playing(true) audio_player.stream_paused = false song_finished = false audio_player.seek(playback_position) @@ -81,7 +99,7 @@ func play_or_pause() -> void: func pause() -> void: audio_player.stream_paused = true playback_position = audio_player.get_playback_position() - play_button.texture_normal = play_icon + _show_playing(false) func restart() -> void: @@ -91,12 +109,12 @@ func restart() -> void: progress_slider.value = 0 playback_position = audio_player.get_playback_position() audio_player.seek(playback_position) - play_button.texture_normal = pause_icon + _show_playing(true) audio_player.play() func _on_finished() -> void: - play_button.texture_normal = play_icon + _show_playing(false) song_finished = true if !Settings.stop_after_current: play_next_song.emit() @@ -150,7 +168,7 @@ func play_sound(sound_name: AudioStream) -> void: audio_player.stop() sound_player.play() song_finished = true - play_button.texture_normal = play_icon + _show_playing(false) progress_slider.value = 0 @@ -159,7 +177,7 @@ func play_song(song: AudioStream) -> void: audio_player.play() sound_player.stop() song_finished = false - play_button.texture_normal = pause_icon + _show_playing(true) stream = audio_player.stream progress_slider.value = 0 progress_slider.max_value = round(stream.get_length()) @@ -179,7 +197,7 @@ func play_song_object(song_object_no: int) -> void: sound_player.stop() audio_player.play() song_finished = false - play_button.texture_normal = pause_icon + _show_playing(true) stream = audio_player.stream progress_slider.value = 0 progress_slider.max_value = round(stream.get_length()) diff --git a/quiz/Quiz.tscn b/quiz/Quiz.tscn index 3d79b56..b34c7bc 100644 --- a/quiz/Quiz.tscn +++ b/quiz/Quiz.tscn @@ -48,9 +48,9 @@ offset_bottom = 384.0 [node name="MusicPlayer" parent="." unique_id=1990459832 instance=ExtResource("5_emn36")] layout_mode = 0 offset_left = 64.0 -offset_top = 992.0 +offset_top = 984.0 offset_right = 1856.0 -offset_bottom = 1063.0 +offset_bottom = 1026.0 [node name="PlayersPanel" parent="." unique_id=1708375945 instance=ExtResource("scene_PlayersPanel")] layout_mode = 0 diff --git a/tests/data/layout_baseline.json b/tests/data/layout_baseline.json index 7d5f613..7d88e0d 100644 --- a/tests/data/layout_baseline.json +++ b/tests/data/layout_baseline.json @@ -1056,71 +1056,80 @@ "Quiz/MusicPlayer": { "rect": [ 64.0, - 992.0, + 984.0, 1792.0, - 71.0 + 42.0 ], "visible": true }, "Quiz/MusicPlayer/PlayerBar": { "rect": [ - 65.0, - 992.0, - 1791.0, - 32.0 + 64.0, + 984.0, + 1792.0, + 42.0 ], "visible": true }, "Quiz/MusicPlayer/PlayerBar/MusicPlayerContainer": { "rect": [ - 65.0, - 992.0, - 1791.0, - 32.0 + 70.0, + 988.0, + 1772.0, + 34.0 ], "visible": true }, "Quiz/MusicPlayer/PlayerBar/MusicPlayerContainer/MusicPlayerSlider": { "rect": [ - 137.0, - 1000.0, - 1532.0, + 158.0, + 997.0, + 1402.0, 16.0 ], "visible": true }, "Quiz/MusicPlayer/PlayerBar/MusicPlayerContainer/MusicTimeLabel": { "rect": [ - 1673.0, - 996.0, - 79.0, - 23.0 + 1570.0, + 992.0, + 120.0, + 26.0 ], "visible": true }, - "Quiz/MusicPlayer/PlayerBar/MusicPlayerContainer/PlayTextureButton": { + "Quiz/MusicPlayer/PlayerBar/MusicPlayerContainer/PlayButton": { "rect": [ - 65.0, - 992.0, - 32.0, - 32.0 + 70.0, + 988.0, + 34.0, + 34.0 ], "visible": true }, - "Quiz/MusicPlayer/PlayerBar/MusicPlayerContainer/RestartTextureButton": { + "Quiz/MusicPlayer/PlayerBar/MusicPlayerContainer/RestartButton": { "rect": [ - 101.0, - 992.0, - 32.0, - 32.0 + 114.0, + 988.0, + 34.0, + 34.0 + ], + "visible": true + }, + "Quiz/MusicPlayer/PlayerBar/MusicPlayerContainer/VolumeIcon": { + "rect": [ + 1700.0, + 994.0, + 22.0, + 22.0 ], "visible": true }, "Quiz/MusicPlayer/PlayerBar/MusicPlayerContainer/VolumeSlider": { "rect": [ - 1756.0, - 1000.0, - 100.0, + 1732.0, + 997.0, + 110.0, 16.0 ], "visible": true @@ -4685,71 +4694,80 @@ "Quiz/MusicPlayer": { "rect": [ 64.0, - 992.0, + 984.0, 1792.0, - 71.0 + 42.0 ], "visible": true }, "Quiz/MusicPlayer/PlayerBar": { "rect": [ - 65.0, - 992.0, - 1791.0, - 32.0 + 64.0, + 984.0, + 1792.0, + 42.0 ], "visible": true }, "Quiz/MusicPlayer/PlayerBar/MusicPlayerContainer": { "rect": [ - 65.0, - 992.0, - 1791.0, - 32.0 + 70.0, + 988.0, + 1772.0, + 34.0 ], "visible": true }, "Quiz/MusicPlayer/PlayerBar/MusicPlayerContainer/MusicPlayerSlider": { "rect": [ - 137.0, - 1000.0, - 1514.0, + 158.0, + 997.0, + 1402.0, 16.0 ], "visible": true }, "Quiz/MusicPlayer/PlayerBar/MusicPlayerContainer/MusicTimeLabel": { "rect": [ - 1655.0, - 996.0, - 97.0, - 23.0 + 1570.0, + 992.0, + 120.0, + 26.0 ], "visible": true }, - "Quiz/MusicPlayer/PlayerBar/MusicPlayerContainer/PlayTextureButton": { + "Quiz/MusicPlayer/PlayerBar/MusicPlayerContainer/PlayButton": { "rect": [ - 65.0, - 992.0, - 32.0, - 32.0 + 70.0, + 988.0, + 34.0, + 34.0 ], "visible": true }, - "Quiz/MusicPlayer/PlayerBar/MusicPlayerContainer/RestartTextureButton": { + "Quiz/MusicPlayer/PlayerBar/MusicPlayerContainer/RestartButton": { "rect": [ - 101.0, - 992.0, - 32.0, - 32.0 + 114.0, + 988.0, + 34.0, + 34.0 + ], + "visible": true + }, + "Quiz/MusicPlayer/PlayerBar/MusicPlayerContainer/VolumeIcon": { + "rect": [ + 1700.0, + 994.0, + 22.0, + 22.0 ], "visible": true }, "Quiz/MusicPlayer/PlayerBar/MusicPlayerContainer/VolumeSlider": { "rect": [ - 1756.0, - 1000.0, - 100.0, + 1732.0, + 997.0, + 110.0, 16.0 ], "visible": true @@ -11482,71 +11500,80 @@ "Quiz/MusicPlayer": { "rect": [ 64.0, - 992.0, + 984.0, 1792.0, - 71.0 + 42.0 ], "visible": true }, "Quiz/MusicPlayer/PlayerBar": { "rect": [ - 65.0, - 992.0, - 1791.0, - 32.0 + 64.0, + 984.0, + 1792.0, + 42.0 ], "visible": true }, "Quiz/MusicPlayer/PlayerBar/MusicPlayerContainer": { "rect": [ - 65.0, - 992.0, - 1791.0, - 32.0 + 70.0, + 988.0, + 1772.0, + 34.0 ], "visible": true }, "Quiz/MusicPlayer/PlayerBar/MusicPlayerContainer/MusicPlayerSlider": { "rect": [ - 137.0, - 1000.0, - 1532.0, + 158.0, + 997.0, + 1402.0, 16.0 ], "visible": true }, "Quiz/MusicPlayer/PlayerBar/MusicPlayerContainer/MusicTimeLabel": { "rect": [ - 1673.0, - 996.0, - 79.0, - 23.0 + 1570.0, + 992.0, + 120.0, + 26.0 ], "visible": true }, - "Quiz/MusicPlayer/PlayerBar/MusicPlayerContainer/PlayTextureButton": { + "Quiz/MusicPlayer/PlayerBar/MusicPlayerContainer/PlayButton": { "rect": [ - 65.0, - 992.0, - 32.0, - 32.0 + 70.0, + 988.0, + 34.0, + 34.0 ], "visible": true }, - "Quiz/MusicPlayer/PlayerBar/MusicPlayerContainer/RestartTextureButton": { + "Quiz/MusicPlayer/PlayerBar/MusicPlayerContainer/RestartButton": { "rect": [ - 101.0, - 992.0, - 32.0, - 32.0 + 114.0, + 988.0, + 34.0, + 34.0 + ], + "visible": true + }, + "Quiz/MusicPlayer/PlayerBar/MusicPlayerContainer/VolumeIcon": { + "rect": [ + 1700.0, + 994.0, + 22.0, + 22.0 ], "visible": true }, "Quiz/MusicPlayer/PlayerBar/MusicPlayerContainer/VolumeSlider": { "rect": [ - 1756.0, - 1000.0, - 100.0, + 1732.0, + 997.0, + 110.0, 16.0 ], "visible": true @@ -15480,71 +15507,80 @@ "Quiz/MusicPlayer": { "rect": [ 64.0, - 992.0, + 984.0, 1792.0, - 71.0 + 42.0 ], "visible": true }, "Quiz/MusicPlayer/PlayerBar": { "rect": [ - 65.0, - 992.0, - 1791.0, - 32.0 + 64.0, + 984.0, + 1792.0, + 42.0 ], "visible": true }, "Quiz/MusicPlayer/PlayerBar/MusicPlayerContainer": { "rect": [ - 65.0, - 992.0, - 1791.0, - 32.0 + 70.0, + 988.0, + 1772.0, + 34.0 ], "visible": true }, "Quiz/MusicPlayer/PlayerBar/MusicPlayerContainer/MusicPlayerSlider": { "rect": [ - 137.0, - 1000.0, - 1514.0, + 158.0, + 997.0, + 1402.0, 16.0 ], "visible": true }, "Quiz/MusicPlayer/PlayerBar/MusicPlayerContainer/MusicTimeLabel": { "rect": [ - 1655.0, - 996.0, - 97.0, - 23.0 + 1570.0, + 992.0, + 120.0, + 26.0 ], "visible": true }, - "Quiz/MusicPlayer/PlayerBar/MusicPlayerContainer/PlayTextureButton": { + "Quiz/MusicPlayer/PlayerBar/MusicPlayerContainer/PlayButton": { "rect": [ - 65.0, - 992.0, - 32.0, - 32.0 + 70.0, + 988.0, + 34.0, + 34.0 ], "visible": true }, - "Quiz/MusicPlayer/PlayerBar/MusicPlayerContainer/RestartTextureButton": { + "Quiz/MusicPlayer/PlayerBar/MusicPlayerContainer/RestartButton": { "rect": [ - 101.0, - 992.0, - 32.0, - 32.0 + 114.0, + 988.0, + 34.0, + 34.0 + ], + "visible": true + }, + "Quiz/MusicPlayer/PlayerBar/MusicPlayerContainer/VolumeIcon": { + "rect": [ + 1700.0, + 994.0, + 22.0, + 22.0 ], "visible": true }, "Quiz/MusicPlayer/PlayerBar/MusicPlayerContainer/VolumeSlider": { "rect": [ - 1756.0, - 1000.0, - 100.0, + 1732.0, + 997.0, + 110.0, 16.0 ], "visible": true @@ -23540,71 +23576,80 @@ "Quiz/MusicPlayer": { "rect": [ 64.0, - 992.0, + 984.0, 1792.0, - 71.0 + 42.0 ], "visible": true }, "Quiz/MusicPlayer/PlayerBar": { "rect": [ - 65.0, - 992.0, - 1791.0, - 32.0 + 64.0, + 984.0, + 1792.0, + 42.0 ], "visible": true }, "Quiz/MusicPlayer/PlayerBar/MusicPlayerContainer": { "rect": [ - 65.0, - 992.0, - 1791.0, - 32.0 + 70.0, + 988.0, + 1772.0, + 34.0 ], "visible": true }, "Quiz/MusicPlayer/PlayerBar/MusicPlayerContainer/MusicPlayerSlider": { "rect": [ - 137.0, - 1000.0, - 1532.0, + 158.0, + 997.0, + 1402.0, 16.0 ], "visible": true }, "Quiz/MusicPlayer/PlayerBar/MusicPlayerContainer/MusicTimeLabel": { "rect": [ - 1673.0, - 996.0, - 79.0, - 23.0 + 1570.0, + 992.0, + 120.0, + 26.0 ], "visible": true }, - "Quiz/MusicPlayer/PlayerBar/MusicPlayerContainer/PlayTextureButton": { + "Quiz/MusicPlayer/PlayerBar/MusicPlayerContainer/PlayButton": { "rect": [ - 65.0, - 992.0, - 32.0, - 32.0 + 70.0, + 988.0, + 34.0, + 34.0 ], "visible": true }, - "Quiz/MusicPlayer/PlayerBar/MusicPlayerContainer/RestartTextureButton": { + "Quiz/MusicPlayer/PlayerBar/MusicPlayerContainer/RestartButton": { "rect": [ - 101.0, - 992.0, - 32.0, - 32.0 + 114.0, + 988.0, + 34.0, + 34.0 + ], + "visible": true + }, + "Quiz/MusicPlayer/PlayerBar/MusicPlayerContainer/VolumeIcon": { + "rect": [ + 1700.0, + 994.0, + 22.0, + 22.0 ], "visible": true }, "Quiz/MusicPlayer/PlayerBar/MusicPlayerContainer/VolumeSlider": { "rect": [ - 1756.0, - 1000.0, - 100.0, + 1732.0, + 997.0, + 110.0, 16.0 ], "visible": true @@ -36647,71 +36692,80 @@ "Quiz/MusicPlayer": { "rect": [ 64.0, - 992.0, + 984.0, 1792.0, - 71.0 + 42.0 ], "visible": true }, "Quiz/MusicPlayer/PlayerBar": { "rect": [ - 65.0, - 992.0, - 1791.0, - 32.0 + 64.0, + 984.0, + 1792.0, + 42.0 ], "visible": true }, "Quiz/MusicPlayer/PlayerBar/MusicPlayerContainer": { "rect": [ - 65.0, - 992.0, - 1791.0, - 32.0 + 70.0, + 988.0, + 1772.0, + 34.0 ], "visible": true }, "Quiz/MusicPlayer/PlayerBar/MusicPlayerContainer/MusicPlayerSlider": { "rect": [ - 137.0, - 1000.0, - 1514.0, + 158.0, + 997.0, + 1402.0, 16.0 ], "visible": true }, "Quiz/MusicPlayer/PlayerBar/MusicPlayerContainer/MusicTimeLabel": { "rect": [ - 1655.0, - 996.0, - 97.0, - 23.0 + 1570.0, + 992.0, + 120.0, + 26.0 ], "visible": true }, - "Quiz/MusicPlayer/PlayerBar/MusicPlayerContainer/PlayTextureButton": { + "Quiz/MusicPlayer/PlayerBar/MusicPlayerContainer/PlayButton": { "rect": [ - 65.0, - 992.0, - 32.0, - 32.0 + 70.0, + 988.0, + 34.0, + 34.0 ], "visible": true }, - "Quiz/MusicPlayer/PlayerBar/MusicPlayerContainer/RestartTextureButton": { + "Quiz/MusicPlayer/PlayerBar/MusicPlayerContainer/RestartButton": { "rect": [ - 101.0, - 992.0, - 32.0, - 32.0 + 114.0, + 988.0, + 34.0, + 34.0 + ], + "visible": true + }, + "Quiz/MusicPlayer/PlayerBar/MusicPlayerContainer/VolumeIcon": { + "rect": [ + 1700.0, + 994.0, + 22.0, + 22.0 ], "visible": true }, "Quiz/MusicPlayer/PlayerBar/MusicPlayerContainer/VolumeSlider": { "rect": [ - 1756.0, - 1000.0, - 100.0, + 1732.0, + 997.0, + 110.0, 16.0 ], "visible": true @@ -41243,71 +41297,80 @@ "Quiz/MusicPlayer": { "rect": [ 64.0, - 992.0, + 984.0, 1792.0, - 71.0 + 42.0 ], "visible": true }, "Quiz/MusicPlayer/PlayerBar": { "rect": [ - 65.0, - 992.0, - 1791.0, - 32.0 + 64.0, + 984.0, + 1792.0, + 42.0 ], "visible": true }, "Quiz/MusicPlayer/PlayerBar/MusicPlayerContainer": { "rect": [ - 65.0, - 992.0, - 1791.0, - 32.0 + 70.0, + 988.0, + 1772.0, + 34.0 ], "visible": true }, "Quiz/MusicPlayer/PlayerBar/MusicPlayerContainer/MusicPlayerSlider": { "rect": [ - 137.0, - 1000.0, - 1514.0, + 158.0, + 997.0, + 1402.0, 16.0 ], "visible": true }, "Quiz/MusicPlayer/PlayerBar/MusicPlayerContainer/MusicTimeLabel": { "rect": [ - 1655.0, - 996.0, - 97.0, - 23.0 + 1570.0, + 992.0, + 120.0, + 26.0 ], "visible": true }, - "Quiz/MusicPlayer/PlayerBar/MusicPlayerContainer/PlayTextureButton": { + "Quiz/MusicPlayer/PlayerBar/MusicPlayerContainer/PlayButton": { "rect": [ - 65.0, - 992.0, - 32.0, - 32.0 + 70.0, + 988.0, + 34.0, + 34.0 ], "visible": true }, - "Quiz/MusicPlayer/PlayerBar/MusicPlayerContainer/RestartTextureButton": { + "Quiz/MusicPlayer/PlayerBar/MusicPlayerContainer/RestartButton": { "rect": [ - 101.0, - 992.0, - 32.0, - 32.0 + 114.0, + 988.0, + 34.0, + 34.0 + ], + "visible": true + }, + "Quiz/MusicPlayer/PlayerBar/MusicPlayerContainer/VolumeIcon": { + "rect": [ + 1700.0, + 994.0, + 22.0, + 22.0 ], "visible": true }, "Quiz/MusicPlayer/PlayerBar/MusicPlayerContainer/VolumeSlider": { "rect": [ - 1756.0, - 1000.0, - 100.0, + 1732.0, + 997.0, + 110.0, 16.0 ], "visible": true diff --git a/tests/fakes/ui_tour.gd b/tests/fakes/ui_tour.gd index e63c8db..93414b1 100644 --- a/tests/fakes/ui_tour.gd +++ b/tests/fakes/ui_tour.gd @@ -446,6 +446,11 @@ func _freeze_music_player() -> void: music_player.sound_player.stop() music_player.progress_slider.value = 0 music_player.music_time_label.text = "00:00 / 00:01" + # The buttons' animations would otherwise be caught halfway, see ButtonBounce + for button: Button in [music_player.play_button, music_player.restart_button]: + if button.has_meta(&"button_bounce_tween"): + (button.get_meta(&"button_bounce_tween") as Tween).kill() + button.scale = Vector2.ONE ## The log starts each row with the time of day; use a fixed time instead. diff --git a/tests/integration/test_music_player_scene.gd b/tests/integration/test_music_player_scene.gd index dc08d1a..2351ec0 100644 --- a/tests/integration/test_music_player_scene.gd +++ b/tests/integration/test_music_player_scene.gd @@ -26,3 +26,129 @@ func _click(control: Control) -> void: func test_empty_space_lets_clicks_through() -> void: assert_int(_player.mouse_filter).is_equal(Control.MOUSE_FILTER_IGNORE) + + +func _song() -> AudioStreamMP3: + var song: AudioStreamMP3 = AudioStreamMP3.new() + song.data = FileAccess.get_file_as_bytes("res://tests/data/short.mp3") + return song + + +func _buttons() -> Array[Button]: + return [_player.play_button, _player.restart_button] + + +# Waits until the buttons' animations have finished +func _settle() -> void: + await await_millis(int(maxf(ButtonBounce.DURATION, ButtonBounce.POP_DURATION) * 1000) + 80) + + +# Icons + +func test_icons_sit_inside_their_buttons_with_room_around_them() -> void: + await _runner.simulate_frames(1) + for button: Button in _buttons(): + var style: StyleBox = button.get_theme_stylebox(&"normal") + for side: Side in [SIDE_LEFT, SIDE_TOP, SIDE_RIGHT, SIDE_BOTTOM]: + assert_float(style.get_content_margin(side)).override_failure_message(button.name) \ + .is_greater_equal(8.0) + assert_bool(button.expand_icon).override_failure_message(button.name).is_true() + assert_int(button.icon_alignment).is_equal(HORIZONTAL_ALIGNMENT_CENTER) + # A square button, so the round background is a circle around the icon + assert_float(button.size.x).override_failure_message(button.name) \ + .is_equal(button.size.y) + var icon_room: Vector2 = button.size - style.get_minimum_size() + assert_float(icon_room.x).override_failure_message(button.name).is_greater(12.0) + + +func test_buttons_are_centred_in_the_bar() -> void: + await _runner.simulate_frames(1) + var bar: Rect2 = _player.get_node("PlayerBar").get_global_rect() + for button: Button in _buttons(): + var rect: Rect2 = button.get_global_rect() + assert_bool(bar.encloses(rect)).override_failure_message(button.name).is_true() + assert_float(rect.get_center().y).override_failure_message(button.name) \ + .is_equal_approx(bar.get_center().y, 1.0) + + +func test_play_button_stands_out_from_restart() -> void: + assert_str(_player.play_button.theme_type_variation).is_equal("PlayerButtonPrimary") + assert_str(_player.restart_button.theme_type_variation).is_equal("PlayerButton") + assert_str(_player.progress_slider.theme_type_variation).is_equal("ProgressSlider") + assert_str(_player.volume_slider.theme_type_variation).is_equal("VolumeBar") + assert_str(_player.music_time_label.theme_type_variation).is_equal("PlayerTime") + + +func test_buttons_have_tooltips_with_their_keys() -> void: + assert_str(_player.play_button.tooltip_text).contains("Alt+X") + assert_str(_player.restart_button.tooltip_text).contains("Alt+Z") + + +# Play and pause + +func test_play_button_shows_play_until_a_song_plays() -> void: + assert_bool(_player.shows_playing()).is_false() + + _player.play_song(_song()) + + assert_bool(_player.shows_playing()).is_true() + assert_object(_player.play_button.icon).is_same(_player.pause_icon) + + +func test_pausing_shows_play_again() -> void: + await _runner.simulate_frames(1) + _player.play_song(_song()) + + await _click(_player.play_button) + + assert_bool(_player.audio_player.stream_paused).is_true() + assert_bool(_player.shows_playing()).is_false() + + +func test_play_button_pops_when_it_changes() -> void: + await _runner.simulate_frames(1) + + _player.play_song(_song()) + + assert_vector(_player.play_button.scale).is_equal(ButtonBounce.POP_SCALE) + await _settle() + assert_vector(_player.play_button.scale).is_equal_approx(Vector2.ONE, Vector2(0.01, 0.01)) + + +func test_play_button_does_not_pop_when_it_stays_the_same() -> void: + _player.play_song(_song()) + await _settle() + + _player.play_song(_song()) + + assert_vector(_player.play_button.scale).is_equal_approx(Vector2.ONE, Vector2(0.01, 0.01)) + + +# Hover + +func test_buttons_grow_under_the_mouse_and_shrink_back() -> void: + await _runner.simulate_frames(1) + for button: Button in _buttons(): + _runner.set_mouse_position(_player.get_viewport().get_final_transform() + * button.get_global_rect().get_center()) + await _runner.simulate_frames(2) + await _settle() + assert_vector(button.scale).override_failure_message(button.name) \ + .is_equal_approx(ButtonBounce.HOVER_SCALE, Vector2(0.01, 0.01)) + + _runner.set_mouse_position(Vector2.ZERO) + await _runner.simulate_frames(2) + await _settle() + assert_vector(button.scale).override_failure_message(button.name) \ + .is_equal_approx(Vector2.ONE, Vector2(0.01, 0.01)) + + +func test_growing_keeps_the_button_in_place() -> void: + await _runner.simulate_frames(1) + var before: Vector2 = _player.restart_button.get_global_rect().get_center() + + _player.restart_button.mouse_entered.emit() + await _settle() + + assert_vector(_player.restart_button.get_global_rect().get_center()) \ + .is_equal_approx(before, Vector2(0.5, 0.5)) diff --git a/tests/integration/test_scene_split.gd b/tests/integration/test_scene_split.gd index 6a05846..7da3745 100644 --- a/tests/integration/test_scene_split.gd +++ b/tests/integration/test_scene_split.gd @@ -313,3 +313,15 @@ func test_alt_s_opens_search() -> void: await _press_keys([KEY_ALT, KEY_S]) assert_bool((_window.get_node("SearchPopup") as Control).visible).is_true() + + +func test_music_player_fits_between_the_song_buttons_and_quiz_settings() -> void: + var bar: Rect2 = _quiz.get_node("MusicPlayer/PlayerBar").get_global_rect() + var above: Rect2 = _quiz.get_node("SongControls/NextButton").get_global_rect() + var below: Rect2 = _quiz.get_node("QuizSettingsButton").get_global_rect() + + assert_float(bar.position.y).is_greater_equal(above.end.y + 4) + assert_float(bar.end.y).is_less_equal(below.position.y - 2) + # As wide as the music list above it + var list: Rect2 = _quiz.get_node("MusicListPanel").get_global_rect() + assert_float(bar.position.x).is_equal(list.position.x) diff --git a/tests/integration/test_shortcuts_popup.gd b/tests/integration/test_shortcuts_popup.gd index 366cd7c..0db2155 100644 --- a/tests/integration/test_shortcuts_popup.gd +++ b/tests/integration/test_shortcuts_popup.gd @@ -58,8 +58,8 @@ func test_buttons_do_what_the_list_says() -> void: var expected: Dictionary = { "SongControls/NextButton": "Next song", "SongControls/ShowAnswerButton": "Show answer", - "MusicPlayer/PlayerBar/MusicPlayerContainer/PlayTextureButton": "Play or pause", - "MusicPlayer/PlayerBar/MusicPlayerContainer/RestartTextureButton": "Restart song", + "MusicPlayer/PlayerBar/MusicPlayerContainer/PlayButton": "Play or pause", + "MusicPlayer/PlayerBar/MusicPlayerContainer/RestartButton": "Restart song", } for path: String in expected: var button: BaseButton = _quiz.get_node(path) diff --git a/tests/integration/test_theme.gd b/tests/integration/test_theme.gd index ba720e6..4069b6d 100644 --- a/tests/integration/test_theme.gd +++ b/tests/integration/test_theme.gd @@ -37,6 +37,12 @@ const STYLES: Dictionary = { "SongRowPlaying": "PanelContainer", "SongRowPictureLine": "HBoxContainer", "SongRowPictureLines": "VBoxContainer", + "PlayerBar": "PanelContainer", + "PlayerButton": "Button", + "PlayerButtonPrimary": "Button", + "PlayerTime": "Label", + "ProgressSlider": "HSlider", + "VolumeBar": "HSlider", "StatisticsCard": "PanelContainer", "StatisticsCardHover": "PanelContainer", "StatisticsGrid": "GridContainer", diff --git a/tests/unit/test_button_bounce.gd b/tests/unit/test_button_bounce.gd new file mode 100644 index 0000000..169632d --- /dev/null +++ b/tests/unit/test_button_bounce.gd @@ -0,0 +1,80 @@ +extends GdUnitTestSuite +## ButtonBounce: buttons grow under the mouse, shrink while held down, pop when asked to, +## and always around their middle. + +var _button: Button + + +func before_test() -> void: + _button = auto_free(Button.new()) + _button.size = Vector2(40, 40) + add_child(_button) + ButtonBounce.add_to(_button) + + +func _settle() -> void: + await await_millis(int(maxf(ButtonBounce.DURATION, ButtonBounce.POP_DURATION) * 1000) + 80) + + +func test_starts_at_its_normal_size_scaled_around_its_middle() -> void: + assert_vector(_button.scale).is_equal(Vector2.ONE) + assert_vector(_button.pivot_offset).is_equal(Vector2(20, 20)) + + +func test_pivot_follows_the_size() -> void: + _button.size = Vector2(60, 30) + + assert_vector(_button.pivot_offset).is_equal(Vector2(30, 15)) + + +func test_grows_when_the_mouse_enters_and_shrinks_back_when_it_leaves() -> void: + _button.mouse_entered.emit() + await _settle() + assert_vector(_button.scale).is_equal_approx(ButtonBounce.HOVER_SCALE, Vector2(0.01, 0.01)) + + _button.mouse_exited.emit() + await _settle() + assert_vector(_button.scale).is_equal_approx(Vector2.ONE, Vector2(0.01, 0.01)) + + +func test_shrinks_while_held_down() -> void: + _button.button_down.emit() + await _settle() + + assert_vector(_button.scale).is_equal_approx(ButtonBounce.PRESSED_SCALE, Vector2(0.01, 0.01)) + + +func test_goes_back_to_normal_when_let_go_away_from_the_mouse() -> void: + _button.button_down.emit() + await _settle() + + _button.button_up.emit() + await _settle() + + assert_vector(_button.scale).is_equal_approx(Vector2.ONE, Vector2(0.01, 0.01)) + + +func test_disabled_button_does_not_grow() -> void: + _button.disabled = true + + _button.mouse_entered.emit() + await _settle() + + assert_vector(_button.scale).is_equal(Vector2.ONE) + + +func test_pop_starts_small_and_springs_back() -> void: + ButtonBounce.pop(_button) + + assert_vector(_button.scale).is_equal(ButtonBounce.POP_SCALE) + await _settle() + assert_vector(_button.scale).is_equal_approx(Vector2.ONE, Vector2(0.01, 0.01)) + + +func test_a_new_animation_stops_the_one_before() -> void: + _button.mouse_entered.emit() + _button.button_down.emit() + await _settle() + + # Only the last one ran to the end + assert_vector(_button.scale).is_equal_approx(ButtonBounce.PRESSED_SCALE, Vector2(0.01, 0.01)) diff --git a/tests/unit/test_button_bounce.gd.uid b/tests/unit/test_button_bounce.gd.uid new file mode 100644 index 0000000..393a019 --- /dev/null +++ b/tests/unit/test_button_bounce.gd.uid @@ -0,0 +1 @@ +uid://b8tyllgau53y6 diff --git a/themes/README.md b/themes/README.md index 2f849ce..533bbda 100644 --- a/themes/README.md +++ b/themes/README.md @@ -51,6 +51,10 @@ inspector or `theme_type_variation` in code: | `TabButton` | Button | The Quiz and Statistics tabs; the chosen one is orange | | `StatisticsCard`, `StatisticsCardHover` | PanelContainer | A soundtrack card in the statistics, and the one under the mouse | | `StatisticsGrid` | GridContainer | The grid of soundtrack cards, with the space between them | +| `PlayerBar` | PanelContainer | The music player's rounded bar | +| `PlayerButtonPrimary`, `PlayerButton` | Button | The round play button, and the restart button; the icon's colour on hover comes from here | +| `ProgressSlider`, `VolumeBar` | HSlider | How far the song has played (orange), and the volume | +| `PlayerTime` | Label | The time played and the song's length | | `SongStatsHeader` | PanelContainer | The column titles above the statistics song list, spaced like its rows | | `SongStatsText`, `SongStatsSecondary` | RichTextLabel | A song's name and its soundtrack (dimmer) in the statistics song list | diff --git a/themes/main_theme.tres b/themes/main_theme.tres index 05d701c..2cd531b 100644 --- a/themes/main_theme.tres +++ b/themes/main_theme.tres @@ -819,6 +819,158 @@ corner_radius_top_right = 6 corner_radius_bottom_right = 6 corner_radius_bottom_left = 6 +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_playerbar"] +content_margin_left = 6.0 +content_margin_top = 4.0 +content_margin_right = 14.0 +content_margin_bottom = 4.0 +bg_color = Color(0.16, 0.16, 0.16, 1) +corner_radius_top_left = 8 +corner_radius_top_right = 8 +corner_radius_bottom_right = 8 +corner_radius_bottom_left = 8 + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_playerbutton"] +content_margin_left = 8.0 +content_margin_top = 8.0 +content_margin_right = 8.0 +content_margin_bottom = 8.0 +bg_color = Color(1, 1, 1, 0) +corner_radius_top_left = 17 +corner_radius_top_right = 17 +corner_radius_bottom_right = 17 +corner_radius_bottom_left = 17 +corner_detail = 12 + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_playerbuttonhover"] +content_margin_left = 8.0 +content_margin_top = 8.0 +content_margin_right = 8.0 +content_margin_bottom = 8.0 +bg_color = Color(1, 1, 1, 0.12) +corner_radius_top_left = 17 +corner_radius_top_right = 17 +corner_radius_bottom_right = 17 +corner_radius_bottom_left = 17 +corner_detail = 12 + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_playerbuttonpressed"] +content_margin_left = 8.0 +content_margin_top = 8.0 +content_margin_right = 8.0 +content_margin_bottom = 8.0 +bg_color = Color(1, 1, 1, 0.2) +corner_radius_top_left = 17 +corner_radius_top_right = 17 +corner_radius_bottom_right = 17 +corner_radius_bottom_left = 17 +corner_detail = 12 + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_playerprimary"] +content_margin_left = 9.0 +content_margin_top = 9.0 +content_margin_right = 9.0 +content_margin_bottom = 9.0 +bg_color = Color(1, 0.5, 0, 1) +corner_radius_top_left = 17 +corner_radius_top_right = 17 +corner_radius_bottom_right = 17 +corner_radius_bottom_left = 17 +corner_detail = 12 + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_playerprimaryhover"] +content_margin_left = 9.0 +content_margin_top = 9.0 +content_margin_right = 9.0 +content_margin_bottom = 9.0 +bg_color = Color(1, 0.6, 0.18, 1) +corner_radius_top_left = 17 +corner_radius_top_right = 17 +corner_radius_bottom_right = 17 +corner_radius_bottom_left = 17 +corner_detail = 12 + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_playerprimarypressed"] +content_margin_left = 9.0 +content_margin_top = 9.0 +content_margin_right = 9.0 +content_margin_bottom = 9.0 +bg_color = Color(0.82, 0.4, 0, 1) +corner_radius_top_left = 17 +corner_radius_top_right = 17 +corner_radius_bottom_right = 17 +corner_radius_bottom_left = 17 +corner_detail = 12 + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_playerprimarydisabled"] +content_margin_left = 9.0 +content_margin_top = 9.0 +content_margin_right = 9.0 +content_margin_bottom = 9.0 +bg_color = Color(0.4, 0.4, 0.4, 1) +corner_radius_top_left = 17 +corner_radius_top_right = 17 +corner_radius_bottom_right = 17 +corner_radius_bottom_left = 17 +corner_detail = 12 + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_playertrack"] +content_margin_left = 0.0 +content_margin_top = 3.0 +content_margin_right = 0.0 +content_margin_bottom = 3.0 +bg_color = Color(0, 0, 0, 0.35) +corner_radius_top_left = 3 +corner_radius_top_right = 3 +corner_radius_bottom_right = 3 +corner_radius_bottom_left = 3 + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_progressfill"] +content_margin_left = 0.0 +content_margin_top = 3.0 +content_margin_right = 0.0 +content_margin_bottom = 3.0 +bg_color = Color(1, 0.5, 0, 1) +corner_radius_top_left = 3 +corner_radius_top_right = 3 +corner_radius_bottom_right = 3 +corner_radius_bottom_left = 3 + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_progressfillhover"] +content_margin_left = 0.0 +content_margin_top = 3.0 +content_margin_right = 0.0 +content_margin_bottom = 3.0 +bg_color = Color(1, 0.62, 0.22, 1) +corner_radius_top_left = 3 +corner_radius_top_right = 3 +corner_radius_bottom_right = 3 +corner_radius_bottom_left = 3 + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_volumefill"] +content_margin_left = 0.0 +content_margin_top = 3.0 +content_margin_right = 0.0 +content_margin_bottom = 3.0 +bg_color = Color(0.8, 0.8, 0.8, 1) +corner_radius_top_left = 3 +corner_radius_top_right = 3 +corner_radius_bottom_right = 3 +corner_radius_bottom_left = 3 + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_volumefillhover"] +content_margin_left = 0.0 +content_margin_top = 3.0 +content_margin_right = 0.0 +content_margin_bottom = 3.0 +bg_color = Color(1, 1, 1, 1) +corner_radius_top_left = 3 +corner_radius_top_right = 3 +corner_radius_bottom_right = 3 +corner_radius_bottom_left = 3 + +[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_playerfocus"] + [resource] default_font = ExtResource("1_default_font") Button/colors/font_color = Color(0.875, 0.875, 0.875, 1) @@ -940,6 +1092,32 @@ OptionButton/styles/pressed_mirrored = SubResource("StyleBoxFlat_a0ksy") Palette/colors/backdrop = Color(0, 0, 0, 0.55) Panel/styles/panel = SubResource("StyleBoxFlat_hwwyi") PanelContainer/styles/panel = SubResource("StyleBoxFlat_wl3gy") +PlayerBar/base_type = &"PanelContainer" +PlayerBar/styles/panel = SubResource("StyleBoxFlat_playerbar") +PlayerButton/base_type = &"Button" +PlayerButton/colors/icon_hover_color = Color(1, 0.5, 0, 1) +PlayerButton/colors/icon_hover_pressed_color = Color(1, 0.5, 0, 1) +PlayerButton/colors/icon_normal_color = Color(0.88, 0.88, 0.88, 1) +PlayerButton/colors/icon_pressed_color = Color(1, 0.6, 0.2, 1) +PlayerButton/styles/focus = SubResource("StyleBoxEmpty_playerfocus") +PlayerButton/styles/hover = SubResource("StyleBoxFlat_playerbuttonhover") +PlayerButton/styles/hover_pressed = SubResource("StyleBoxFlat_playerbuttonpressed") +PlayerButton/styles/normal = SubResource("StyleBoxFlat_playerbutton") +PlayerButton/styles/pressed = SubResource("StyleBoxFlat_playerbuttonpressed") +PlayerButtonPrimary/base_type = &"Button" +PlayerButtonPrimary/colors/icon_hover_color = Color(1, 1, 1, 1) +PlayerButtonPrimary/colors/icon_hover_pressed_color = Color(1, 1, 1, 1) +PlayerButtonPrimary/colors/icon_normal_color = Color(1, 1, 1, 1) +PlayerButtonPrimary/colors/icon_pressed_color = Color(1, 1, 1, 1) +PlayerButtonPrimary/styles/disabled = SubResource("StyleBoxFlat_playerprimarydisabled") +PlayerButtonPrimary/styles/focus = SubResource("StyleBoxEmpty_playerfocus") +PlayerButtonPrimary/styles/hover = SubResource("StyleBoxFlat_playerprimaryhover") +PlayerButtonPrimary/styles/hover_pressed = SubResource("StyleBoxFlat_playerprimarypressed") +PlayerButtonPrimary/styles/normal = SubResource("StyleBoxFlat_playerprimary") +PlayerButtonPrimary/styles/pressed = SubResource("StyleBoxFlat_playerprimarypressed") +PlayerTime/base_type = &"Label" +PlayerTime/colors/font_color = Color(0.82, 0.82, 0.82, 1) +PlayerTime/font_sizes/font_size = 18 PopupBackground/base_type = &"Panel" PopupBackground/styles/panel = SubResource("StyleBoxFlat_vputg") PopupMenu/colors/font_accelerator_color = Color(0.7, 0.7, 0.7, 0.8) @@ -990,6 +1168,10 @@ ScrollContainer/colors/scroll_hint_horizontal_color = Color(0, 0, 0, 1) ScrollContainer/colors/scroll_hint_vertical_color = Color(0, 0, 0, 1) ScrollContainer/styles/focus = SubResource("StyleBoxFlat_35dwi") ScrollContainer/styles/panel = SubResource("StyleBoxEmpty_r3gy8") +ProgressSlider/base_type = &"HSlider" +ProgressSlider/styles/grabber_area = SubResource("StyleBoxFlat_progressfill") +ProgressSlider/styles/grabber_area_highlight = SubResource("StyleBoxFlat_progressfillhover") +ProgressSlider/styles/slider = SubResource("StyleBoxFlat_playertrack") SearchField/base_type = &"LineEdit" SearchField/font_sizes/font_size = 28 SearchResult/base_type = &"RichTextLabel" @@ -1079,5 +1261,9 @@ VScrollBar/styles/scroll = SubResource("StyleBoxFlat_6fedo") VScrollBar/styles/scroll_focus = SubResource("StyleBoxFlat_8sdcv") VSeparator/constants/separation = 4 VSeparator/styles/separator = SubResource("StyleBoxLine_r3erx") +VolumeBar/base_type = &"HSlider" +VolumeBar/styles/grabber_area = SubResource("StyleBoxFlat_volumefill") +VolumeBar/styles/grabber_area_highlight = SubResource("StyleBoxFlat_volumefillhover") +VolumeBar/styles/slider = SubResource("StyleBoxFlat_playertrack") WindowBackground/base_type = &"Panel" WindowBackground/styles/panel = SubResource("StyleBoxFlat_2mdtn")