// Copyright (c) 2026 Dominic Masters // // This software is released under the MIT License. // https://opensource.org/licenses/MIT // A tweened full-screen color quad -- covers both the old uifullbox // (full-screen fade) and uitransition (fade transition with a // completion callback): same shape, just parameterized, so there's no // separate transition class/global. // // extends UIElement composing a child Rectangle (not extends Rectangle) // for the same reason as FpsCounter: only a UIElement/SCRIPTED // instance's render() override is actually dispatched by the engine. // Overrides render() (not update()) so the tween advances every real // frame, not just on fixed-timestep ticks. // // A finished Fullbox is only removed from the overlay roots, never // disposed -- disposing an element from inside its own render() // callback would free its JS instance registration while a native C // caller further up the same call stack still holds it, risking the // exact kind of delayed JerryScript refcount corruption this project // has already hit once. Fullbox.under()/over() lazily create and cache // one shared, reusable instance each (mirroring the old UNDER/OVER // singletons), so repeated fades cost zero additional pool slots. class Fullbox extends UIElement { init() { this.rect = new Rectangle(); this.add(this.rect); this._from = Fullbox.COLOR_TRANSPARENT; this._to = Fullbox.COLOR_TRANSPARENT; this._duration = 0; this._t = 0; this._onComplete = null; this._active = false; } // Starts (or restarts) this Fullbox tweening rect.color from // options.fromColor to options.toColor over options.duration seconds, // calling options.onComplete() once when done. Adds itself to the // overlay roots if it isn't already there. play(options) { this._from = options.fromColor || Fullbox.COLOR_TRANSPARENT; this._to = options.toColor || Fullbox.COLOR_TRANSPARENT; this._duration = options.duration || 0; this._t = 0; this._onComplete = options.onComplete || null; this._active = true; this.rect.width = Screen.width; this.rect.height = Screen.height; this.rect.color = this._from; UI.addOverlay(this); } render() { if(this._active) { this._t += Time.renderDelta; const ratio = this._duration > 0 ? Math.min(1, this._t / this._duration) : 1; this.rect.color = Fullbox._lerpColor(this._from, this._to, ratio); if(ratio >= 1) { this._active = false; UI.removeOverlay(this); if(this._onComplete) this._onComplete(); } } this.renderChildren(); } } Fullbox.COLOR_TRANSPARENT = { r: 0, g: 0, b: 0, a: 0 }; Fullbox._lerpColor = function(from, to, t) { return { r: from.r + (to.r - from.r) * t, g: from.g + (to.g - from.g) * t, b: from.b + (to.b - from.b) * t, a: from.a + (to.a - from.a) * t }; }; Fullbox.under = function() { if(!Fullbox._under) Fullbox._under = new Fullbox(); return Fullbox._under; }; Fullbox.over = function() { if(!Fullbox._over) Fullbox._over = new Fullbox(); return Fullbox._over; }; module.exports = Fullbox;