class_name LoadingScreen extends Control # Forgejo #169: "create a loading screen after clicking on the main menu # buttons." # # Pressing Play used to do nothing visible for as long as the scene took to # build - several seconds in a browser - and a button that does nothing reads # as a button that was not pressed, so the player presses it again. The whole # job of this is to answer the press immediately: a cover over the menu, the # name of what is being opened, and a bar that moves. # # Two things make the answer immediate rather than merely eventual: # # 1. It goes up BEFORE the load starts, and the load waits for it to have # been on screen for a frame or two (see `frames_shown` and # MainMenu._process). A screen raised in the same frame as a blocking # load is a screen nobody ever sees. # 2. The bar reports nothing it does not know. The load is one blocking call # (see MainMenu._process for why it is not a threaded one), so there is # no progress to report and the bar does not pretend there is: it is a # sweep, which says "working" without claiming to say how far. # # A Control built in code rather than a scene: it is one panel of geometry # with nothing to author, and building it here keeps MainMenu.tscn - which the # player's own editor touches - out of the change. const BACKDROP: Color = Color(0.01, 0.02, 0.06, 0.94) const GLOW: Color = Color(0.42, 0.86, 0.95) const TEXT: Color = Color(0.82, 0.94, 1.0) const MUTED: Color = Color(0.52, 0.66, 0.76) const TITLE_SIZE: int = 44 const SUBTITLE_SIZE: int = 24 const BAR_WIDTH: float = 520.0 const BAR_HEIGHT: float = 16.0 const BAR_GAP: float = 46.0 # The travelling band inside the bar, and how much of the bar it covers. const SWEEP_SECONDS: float = 1.2 const SWEEP_WIDTH: float = 0.22 var title_label: Label var subtitle_label: Label # How many frames this has been up and running. MainMenu waits for this before # it starts the blocking part - see the note above. Counted in _process rather # than in _draw so the count still advances under the headless renderer, where # nothing paints and a test would otherwise wait for ever. var frames_shown: int = 0 var _elapsed: float = 0.0 func _ready() -> void: # Covers whatever is behind it and swallows nothing: the menu's buttons # check for a modal themselves (see IconButton._blocked_by_modal), and # MainMenu refuses a second press while a load is running anyway. mouse_filter = Control.MOUSE_FILTER_IGNORE set_anchors_preset(Control.PRESET_FULL_RECT) title_label = _make_label("LOADING", TITLE_SIZE, TEXT) subtitle_label = _make_label("", SUBTITLE_SIZE, MUTED) resized.connect(_layout) _layout() func _make_label(text_value: String, font_size: int, colour: Color) -> Label: var label := Label.new() label.text = text_value label.mouse_filter = Control.MOUSE_FILTER_IGNORE label.add_theme_font_size_override("font_size", font_size) label.add_theme_color_override("font_color", colour) label.add_theme_color_override("font_outline_color", Color(0.01, 0.02, 0.06, 0.95)) label.add_theme_constant_override("outline_size", 3) label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER add_child(label) return label # What is being opened, in the player's words rather than a file name. func begin(what: String) -> void: if subtitle_label != null: subtitle_label.text = what func _bar_rect() -> Rect2: var width: float = minf(BAR_WIDTH, maxf(size.x - 120.0, 40.0)) return Rect2(Vector2((size.x - width) * 0.5, size.y * 0.5 + BAR_GAP), Vector2(width, BAR_HEIGHT)) func _layout() -> void: if title_label == null: return title_label.position = Vector2(0.0, size.y * 0.5 - float(TITLE_SIZE) * 1.4) title_label.size = Vector2(size.x, float(TITLE_SIZE) * 1.4) subtitle_label.position = Vector2(0.0, size.y * 0.5) subtitle_label.size = Vector2(size.x, float(SUBTITLE_SIZE) * 1.6) queue_redraw() func _process(delta: float) -> void: _elapsed += delta frames_shown += 1 queue_redraw() func _draw() -> void: draw_rect(Rect2(Vector2.ZERO, size), BACKDROP) var bar: Rect2 = _bar_rect() draw_rect(bar, Color(GLOW, 0.14)) # A band travelling the length of the bar. It stops moving while the load # blocks, which is honest - the whole engine has stopped - and it is the # thing that says "working" on the frames either side of that. var head: float = fposmod(_elapsed, SWEEP_SECONDS) / SWEEP_SECONDS var band: float = bar.size.x * SWEEP_WIDTH var x: float = bar.position.x + (bar.size.x + band) * head - band var left: float = maxf(x, bar.position.x) var right: float = minf(x + band, bar.end.x) if right > left: draw_rect(Rect2(Vector2(left, bar.position.y), Vector2(right - left, bar.size.y)), Color(GLOW, 0.8)) NeonDraw.draw_glowing_path(self, PackedVector2Array([ bar.position, Vector2(bar.end.x, bar.position.y), bar.end, Vector2(bar.position.x, bar.end.y), bar.position, ]), Color(GLOW, 0.8), 0.7, 0.5)