From 64de934dd393d05b32dfd7657c75502914263fb0 Mon Sep 17 00:00:00 2001 From: Vantha Date: Sun, 4 Jan 2026 15:22:19 +0100 Subject: [PATCH] Revamp the cinema GUI code This patch introduces a new class for managing the cinema overlay (two black bars) and cutscene mode. This makes it more extensible for the future, e.g. allowing to display text on the bars. Since elements can only be placed inside an , this patch needs to restructure session.xml a bit and also adds some explanation comments while at it. --- .../gui/session/cinema/CinemaOverlay.js | 92 ++++++++++ .../gui/session/cinema/CinemaOverlay.xml | 5 + .../data/mods/public/gui/session/input.js | 4 +- .../data/mods/public/gui/session/messages.js | 1 - .../data/mods/public/gui/session/session.js | 48 +---- .../data/mods/public/gui/session/session.xml | 173 +++++++++--------- .../simulation/components/GuiInterface.js | 2 +- 7 files changed, 198 insertions(+), 127 deletions(-) create mode 100644 binaries/data/mods/public/gui/session/cinema/CinemaOverlay.js create mode 100644 binaries/data/mods/public/gui/session/cinema/CinemaOverlay.xml diff --git a/binaries/data/mods/public/gui/session/cinema/CinemaOverlay.js b/binaries/data/mods/public/gui/session/cinema/CinemaOverlay.js new file mode 100644 index 0000000000..8d11956adc --- /dev/null +++ b/binaries/data/mods/public/gui/session/cinema/CinemaOverlay.js @@ -0,0 +1,92 @@ +/** + * This class manages the cinematic overlay, which is responsible for showing black bars at the top and bottom while + * cinema paths are playing. Cinema paths are predefined camera animations, which block player input of any kind while + * playing; cutscenes, essentially. Whether one is playing is communicated through the simulation state. + */ +class CinemaOverlay +{ + constructor() + { + this.overlay = Engine.GetGUIObjectByName("cinemaOverlay"); + this.barTop = Engine.GetGUIObjectByName("cinemaOverlayBarTop"); + this.barBottom = Engine.GetGUIObjectByName("cinemaOverlayBarBottom"); + + // Objects to hide while showing the overlay. + this.primarySessionOverlays = Engine.GetGUIObjectByName("primaryOverlays"); + this.bandbox = Engine.GetGUIObjectByName("bandbox"); + this.hotkeys = Engine.GetGUIObjectByName("hotkeys"); + + this.overlay.onSimulationUpdate = this.onSimulationUpdate.bind(this); + this.overlay.onWindowResized = () => + { + this.recalculateBarSizes(); + }; + this.overlay.hidden = true; + this.isCutsceneModeEnabled = Engine.Renderer_GetCutsceneModeEnabled(); + + this.recalculateBarSizes(); + } + + /** + * Enable or disable cutscene mode and remember it in order to save unnecessary calls to the engine. + * This, however, assumes that the mode isn't modified anywhere else. + */ + setCutsceneModeEnabled(enabled) + { + if (this.isCutsceneModeEnabled == enabled) + return; + + Engine.Renderer_SetCutsceneModeEnabled(!!enabled); + this.isCutsceneModeEnabled = enabled; + } + + isInCutsceneMode() + { + return this.isCutsceneModeEnabled; + } + + onSimulationUpdate() + { + const cinemaPathPlaying = GetSimState().cinemaPathPlaying; + if (this.overlay.hidden && cinemaPathPlaying) + this.show(); + else if (!this.overlay.hidden && !cinemaPathPlaying) + this.hide(); + } + + show() + { + if (!this.overlay.hidden) + return; + + this.primarySessionOverlays.hidden = true; + this.bandbox.hidden = true; + this.hotkeys.hidden = true; + this.overlay.hidden = false; + + this.setCutsceneModeEnabled(true); + } + + hide() + { + if (this.overlay.hidden) + return; + + this.primarySessionOverlays.hidden = !g_ShowGUI; + this.hotkeys.hidden = false; + this.overlay.hidden = true; + + this.setCutsceneModeEnabled(false); + } + + recalculateBarSizes() + { + const minHeight = 115; + const width = this.overlay.getComputedSize().right; + const height = this.overlay.getComputedSize().bottom; + // The aspect ratio of 2.39:1 is typical in films and has a cinematic feel to it. + const barHeight = Math.max(minHeight, (height - width / 2.39) / 2); + this.barTop.size.bottom = barHeight; + this.barBottom.size.top = -barHeight; + } +} diff --git a/binaries/data/mods/public/gui/session/cinema/CinemaOverlay.xml b/binaries/data/mods/public/gui/session/cinema/CinemaOverlay.xml new file mode 100644 index 0000000000..d0afe5b825 --- /dev/null +++ b/binaries/data/mods/public/gui/session/cinema/CinemaOverlay.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/binaries/data/mods/public/gui/session/input.js b/binaries/data/mods/public/gui/session/input.js index a8f453faa5..2585b80c16 100644 --- a/binaries/data/mods/public/gui/session/input.js +++ b/binaries/data/mods/public/gui/session/input.js @@ -527,7 +527,7 @@ function getPreferredEntities(ents) function handleInputBeforeGui(ev, hoveredObject) { - if (GetSimState().cinemaPlaying) + if (g_CinemaOverlay.isInCutsceneMode()) return false; // Capture cursor position so we can use it for displaying cursors, @@ -843,7 +843,7 @@ function handleInputBeforeGui(ev, hoveredObject) } function handleInputAfterGui(ev) { - if (GetSimState().cinemaPlaying) + if (g_CinemaOverlay.isInCutsceneMode()) return false; if (ev.hotkey === undefined) ev.hotkey = null; diff --git a/binaries/data/mods/public/gui/session/messages.js b/binaries/data/mods/public/gui/session/messages.js index 34952dda32..4b9bf16e70 100644 --- a/binaries/data/mods/public/gui/session/messages.js +++ b/binaries/data/mods/public/gui/session/messages.js @@ -444,7 +444,6 @@ function handleNetStatusMessage(message) if (message.status == "disconnected") { g_Disconnected = true; - updateCinemaPath(); closeOpenDialogs(); } diff --git a/binaries/data/mods/public/gui/session/session.js b/binaries/data/mods/public/gui/session/session.js index 8bd672926e..c3b437ef4d 100644 --- a/binaries/data/mods/public/gui/session/session.js +++ b/binaries/data/mods/public/gui/session/session.js @@ -12,6 +12,7 @@ var g_Ambient; var g_AutoFormation; var g_Chat; var g_Cheats; +var g_CinemaOverlay; var g_DeveloperOverlay; var g_DiplomacyColors; var g_DiplomacyDialog; @@ -296,6 +297,7 @@ function init(initData, hotloadData) g_Ambient = new Ambient(); g_AutoFormation = new AutoFormation(); g_Chat = new Chat(g_PlayerViewControl, g_Cheats); + g_CinemaOverlay = new CinemaOverlay(); g_DeveloperOverlay = new DeveloperOverlay(g_PlayerViewControl, g_Selection); g_DiplomacyDialog = new DiplomacyDialog(g_PlayerViewControl, g_DiplomacyColors); g_GameSpeedControl = new GameSpeedControl(g_PlayerViewControl); @@ -631,6 +633,10 @@ function onTick() handleNetMessages(); updateCursorAndTooltip(); + updateTimers(); + + if (g_CinemaOverlay.isInCutsceneMode()) + return; if (g_Selection.dirty) { @@ -650,12 +656,7 @@ function onTick() else if (g_ShowAllStatusBars && now % g_StatusBarUpdate <= tickLength) recalculateStatusBarDisplay(); - updateTimers(); Engine.GuiInterfaceCall("ClearRenamedEntities"); - - const isPlayingCinemaPath = GetSimState().cinemaPlaying && !g_Disconnected; - if (isPlayingCinemaPath) - updateCinemaOverlay(); } function onSimulationUpdate() @@ -686,7 +687,6 @@ function onSimulationUpdate() handler(); // TODO: Move to handlers - updateCinemaPath(); handleNotifications(); updateGUIObjects(); } @@ -694,40 +694,8 @@ function onSimulationUpdate() function toggleGUI() { g_ShowGUI = !g_ShowGUI; - updateCinemaPath(); -} - -// TODO: The whole cinema UI should be handled by its own class. -var g_CutsceneModeEnabled = false; -function updateCinemaPath() -{ - const isPlayingCinemaPath = GetSimState().cinemaPlaying && !g_Disconnected; - - Engine.GetGUIObjectByName("session").hidden = !g_ShowGUI || isPlayingCinemaPath; - Engine.GetGUIObjectByName("cinemaOverlay").hidden = !isPlayingCinemaPath; - if (isPlayingCinemaPath && !g_CutsceneModeEnabled) - { - Engine.Renderer_SetCutsceneModeEnabled(true); - g_CutsceneModeEnabled = true; - } - else if (!isPlayingCinemaPath && g_CutsceneModeEnabled) - { - Engine.Renderer_SetCutsceneModeEnabled(false); - g_CutsceneModeEnabled = false; - } -} - -function updateCinemaOverlay() -{ - const cinemaOverlay = Engine.GetGUIObjectByName("cinemaOverlay"); - const width = cinemaOverlay.getComputedSize().right; - const height = cinemaOverlay.getComputedSize().bottom; - let barHeight = (height - width / 2.39) / 2; - if (barHeight < 0) - barHeight = 0; - - Engine.GetGUIObjectByName("cinemaBarTop").size.bottom = barHeight; - Engine.GetGUIObjectByName("cinemaBarBottom").size.top = -barHeight; + Engine.GetGUIObjectByName("primaryOverlays").hidden = !g_ShowGUI; + Engine.GetGUIObjectByName("supplementaryOverlays").hidden = !g_ShowGUI; } // TODO: Use event subscription onSimulationUpdate, onEntitySelectionChange, onPlayerViewChange, ... instead diff --git a/binaries/data/mods/public/gui/session/session.xml b/binaries/data/mods/public/gui/session/session.xml index 179b663213..e5bc790821 100644 --- a/binaries/data/mods/public/gui/session/session.xml +++ b/binaries/data/mods/public/gui/session/session.xml @@ -8,6 +8,7 @@