Split the main window into TopBar and Quiz scenes
The quiz (song, playlist, players, music player and the popups opened from it) moves into Quiz.tscn and the top panel into TopBar.tscn. The main window keeps only the top bar, the quiz and the popups opened from the top bar, so other views can be added next to the quiz later. Every node keeps its place on screen. Screenshots of every UI state before and after are pixel-identical, and a layout test checks each node against a baseline taken before the split.
This commit is contained in:
1 parent
75c59eb223
commit
3c7b4e5c9d
27 files changed
+3432
-980
No files matched your search
Executable
+55
@@ -0,0 +1,55 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Compares two folders of screenshots from tools/take_screenshots.gd pixel by pixel.
|
||||
|
||||
Usage: tools/compare_screenshots.py <before dir> <after dir> [<diff dir>]
|
||||
|
||||
Prints how many pixels differ in each screenshot. With a diff dir, writes an image
|
||||
for each screenshot that differs, with the changed pixels in red over a faded copy of
|
||||
the after picture. Exits with 1 if any screenshot differs or is missing.
|
||||
"""
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
from PIL import Image, ImageChops
|
||||
|
||||
|
||||
def main() -> int:
|
||||
if len(sys.argv) < 3:
|
||||
print(__doc__)
|
||||
return 2
|
||||
before_dir, after_dir = Path(sys.argv[1]), Path(sys.argv[2])
|
||||
diff_dir = Path(sys.argv[3]) if len(sys.argv) > 3 else None
|
||||
if diff_dir:
|
||||
diff_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
names = sorted({p.name for p in before_dir.glob("*.png")}
|
||||
| {p.name for p in after_dir.glob("*.png")})
|
||||
failed = False
|
||||
for name in names:
|
||||
before_path, after_path = before_dir / name, after_dir / name
|
||||
if not before_path.exists() or not after_path.exists():
|
||||
print(f"{name}: missing in {'before' if not before_path.exists() else 'after'}")
|
||||
failed = True
|
||||
continue
|
||||
before = Image.open(before_path).convert("RGB")
|
||||
after = Image.open(after_path).convert("RGB")
|
||||
if before.size != after.size:
|
||||
print(f"{name}: size {before.size} -> {after.size}")
|
||||
failed = True
|
||||
continue
|
||||
mask = ImageChops.difference(before, after).convert("L").point(
|
||||
lambda value: 255 if value else 0)
|
||||
changed = mask.histogram()[255]
|
||||
print(f"{name}: {changed} pixels differ")
|
||||
if changed:
|
||||
failed = True
|
||||
if diff_dir:
|
||||
faded = Image.blend(after, Image.new("RGB", after.size, "white"), 0.7)
|
||||
faded.paste(Image.new("RGB", after.size, "red"), mask=mask)
|
||||
faded.save(diff_dir / name)
|
||||
return 1 if failed else 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -0,0 +1,53 @@
|
||||
extends SceneTree
|
||||
## Takes a screenshot of the main window in every UiTour state, against a fake server.
|
||||
##
|
||||
## Usage:
|
||||
## godot --path . -s tools/take_screenshots.gd -- <output dir>
|
||||
##
|
||||
## Writes [code]<state>.png[/code] for each state and [code]layout.json[/code] with where
|
||||
## every node is in each state. With [code]--headless[/code] only the layout is written.
|
||||
## Compare two runs with [code]tools/compare_screenshots.py[/code].
|
||||
|
||||
const SIZE: Vector2i = Vector2i(1920, 1080)
|
||||
|
||||
|
||||
func _initialize() -> void:
|
||||
_take.call_deferred()
|
||||
|
||||
|
||||
func _take() -> void:
|
||||
var args: PackedStringArray = OS.get_cmdline_user_args()
|
||||
var out_dir: String = args[0] if !args.is_empty() else "user://screenshots"
|
||||
DirAccess.make_dir_recursive_absolute(out_dir)
|
||||
var draws: bool = DisplayServer.get_name() != "headless"
|
||||
|
||||
# Loaded here, not named as a class: autoloads such as Playlist do not exist yet
|
||||
# when this script is compiled
|
||||
var ui_tour: GDScript = load("res://tests/fakes/ui_tour.gd")
|
||||
ui_tour.prepare_autoloads()
|
||||
var server: RefCounted = ui_tour.make_server().install()
|
||||
|
||||
# Drawn at the layout size, so the pictures do not depend on the screen size
|
||||
var viewport: SubViewport = SubViewport.new()
|
||||
viewport.size = SIZE
|
||||
viewport.gui_embed_subwindows = true
|
||||
viewport.render_target_update_mode = SubViewport.UPDATE_ALWAYS
|
||||
root.add_child(viewport)
|
||||
var main: Control = load("res://MainWindow.tscn").instantiate()
|
||||
viewport.add_child(main)
|
||||
|
||||
var layout: Dictionary = {}
|
||||
var save_state: Callable = func(state: String, popup: Node) -> void:
|
||||
layout[state] = ui_tour.dump_layout(main, main if popup == null else popup)
|
||||
if draws:
|
||||
await RenderingServer.frame_post_draw
|
||||
viewport.get_texture().get_image().save_png(out_dir.path_join(state + ".png"))
|
||||
print("Saved state ", state)
|
||||
await ui_tour.new(main).run(save_state)
|
||||
|
||||
var file: FileAccess = FileAccess.open(out_dir.path_join("layout.json"), FileAccess.WRITE)
|
||||
file.store_string(JSON.stringify(layout, " ", true))
|
||||
file.close()
|
||||
server.uninstall()
|
||||
main.queue_free()
|
||||
quit()
|
||||
@@ -0,0 +1 @@
|
||||
uid://0837em1gsrrx
|
||||
Reference in new issue
Block a user