Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions Source/IMaterialAcrylicPage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,10 @@ internal interface IMaterialAcrylicPage {
bool SuppressNormalRender { get; set; }
void RenderMaterialContent(bool acrylicActive);
}

internal interface IMaterialAcrylicOverlay {
bool HasOverlaysForAcrylic { get; }
void HideOverlaysForAcrylicCapture();
void RestoreOverlaysAfterAcrylicCapture();
void RenderOverlaysAboveAcrylic();
}
37 changes: 34 additions & 3 deletions Source/MaterialAcrylicRenderer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -67,13 +67,16 @@ private static void RenderCore(On.Monocle.Engine.orig_RenderCore orig, Engine se

rendering = true;
activePage = page;
IMaterialAcrylicOverlay? overlay = page as IMaterialAcrylicOverlay;
page.SuppressNormalRender = true;
try {
overlay?.HideOverlaysForAcrylicCapture();
// Keep the normal Engine render lifecycle intact. In particular, the
// Overworld must run BeforeRender exactly once so its mountain and HUD
// buffers are ready before RenderScene redirects the final composition.
orig(self);
} finally {
overlay?.RestoreOverlaysAfterAcrylicCapture();
page.SuppressNormalRender = false;
activePage = null;
rendering = false;
Expand All @@ -86,6 +89,7 @@ private static void RenderScene(On.Monocle.Scene.orig_Render orig, Scene self) {
orig(self);
return;
}
IMaterialAcrylicOverlay? overlay = page as IMaterialAcrylicOverlay;

GraphicsDevice graphics = Engine.Graphics.GraphicsDevice;
RenderTargetBinding[] originalTargets = graphics.GetRenderTargets();
Expand All @@ -94,6 +98,7 @@ private static void RenderScene(On.Monocle.Scene.orig_Render orig, Scene self) {
try {
EnsureTargets(Math.Max(1, originalViewport.Width), Math.Max(1, originalViewport.Height));
if (sceneTarget is null || blurTemporary is null || blurredTarget is null) {
overlay?.RestoreOverlaysAfterAcrylicCapture();
RestoreOutput(graphics, originalTargets, originalViewport);
page.SuppressNormalRender = false;
orig(self);
Expand All @@ -103,7 +108,15 @@ private static void RenderScene(On.Monocle.Scene.orig_Render orig, Scene self) {
graphics.SetRenderTarget(sceneTarget);
graphics.Viewport = new Viewport(0, 0, sceneTarget.Width, sceneTarget.Height);
graphics.Clear(Engine.ClearColor);
orig(self);
HiresSnow? snow = (self as Overworld)?.Snow;
bool renderSnow = snow is { Visible: true };
if (renderSnow) snow!.Visible = false;
try {
orig(self);
} finally {
overlay?.RestoreOverlaysAfterAcrylicCapture();
if (renderSnow) snow!.Visible = true;
}
sceneRendered = true;

float sampleScale = MathHelper.Lerp(0.8f, 3.1f,
Expand All @@ -123,9 +136,12 @@ private static void RenderScene(On.Monocle.Scene.orig_Render orig, Scene self) {

page.SuppressNormalRender = false;
DrawMaterialPage(page);
DrawOverlays(overlay);
if (renderSnow) snow!.Render(self);
successfulFrames++;
} catch (Exception exception) {
page.SuppressNormalRender = false;
overlay?.RestoreOverlaysAfterAcrylicCapture();
RestoreOutput(graphics, originalTargets, originalViewport);
failed = true;
Logger.LogDetailed(exception, "MicroblocksQolUtils/MaterialAcrylic");
Expand Down Expand Up @@ -163,6 +179,15 @@ private static void DrawTexture(
}

private static void DrawMaterialPage(IMaterialAcrylicPage page) {
DrawScreenBatch(() => page.RenderMaterialContent(acrylicActive: true));
}

private static void DrawOverlays(IMaterialAcrylicOverlay? overlay) {
if (overlay is null || !overlay.HasOverlaysForAcrylic) return;
DrawScreenBatch(() => overlay.RenderOverlaysAboveAcrylic());
}

private static void DrawScreenBatch(Action render) {
bool begun = false;
try {
Draw.SpriteBatch.Begin(
Expand All @@ -175,9 +200,15 @@ private static void DrawMaterialPage(IMaterialAcrylicPage page) {
Engine.ScreenMatrix
);
begun = true;
page.RenderMaterialContent(acrylicActive: true);
render();
} finally {
if (begun) Draw.SpriteBatch.End();
if (begun) {
try {
Draw.SpriteBatch.End();
} catch (Exception exception) {
Logger.LogDetailed(exception, "MicroblocksQolUtils/MaterialAcrylic");
}
}
}
}

Expand Down
56 changes: 55 additions & 1 deletion Source/MaterialModOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ namespace Celeste.Mod.MicroblocksQolUtils;
/// TextMenu, so custom entries and binding/config sub-pages keep their original behavior;
/// this class only owns grouping, navigation, layout and mouse interaction.
/// </summary>
public sealed class MaterialModOptions : Oui, IMaterialAcrylicPage {
public sealed class MaterialModOptions : Oui, IMaterialAcrylicPage, IMaterialAcrylicOverlay {
private const float ScreenWidth = 1920f;
private const float ScreenHeight = 1080f;
private const float TabHeight = MaterialSplitPageLayout.TabHeight;
Expand Down Expand Up @@ -90,6 +90,9 @@ public sealed class MaterialModOptions : Oui, IMaterialAcrylicPage {
private string settingSearchText = "";
private string imeText = "";
private SearchTarget searchTarget;
private bool configOverlayOpen;
private readonly List<Entity> externalOverlays = [];
private readonly List<(Entity Entity, bool Visible)> hiddenOverlays = [];

public bool SuppressNormalRender { get; set; }

Expand Down Expand Up @@ -189,6 +192,7 @@ public override void Removed(Scene scene) {
public override void Update() {
MaterialTouch.BeginUpdate(this);
base.Update();
RefreshExternalOverlays();
ModOptionsLayout layout = ModOptionsLayout.Create(1f - Ease.CubeOut(ease));
tabScroll.Update(MaxTabScroll(layout));
rowScroll.Update(MaxRowScroll(layout));
Expand All @@ -206,6 +210,11 @@ public override void Update() {
}

if (menu is null) return;
if (configOverlayOpen) {
// The key/button config screen owns input while it is open.
menu.Focused = false;
return;
}
bool acceptsInput = pauseLevel is not null || Selected && Focused;
if (acceptsInput && inputDelay <= 0f && dropdownItem is null && compositePopupItem is null
&& UpdateSearchInput(layout)) {
Expand Down Expand Up @@ -1004,6 +1013,46 @@ private void EnsureCompositePopupSelectionVisible(ModOptionsLayout layout, TextM
}
}

private void RefreshExternalOverlays() {
externalOverlays.Clear();
configOverlayOpen = false;
if (!Visible || Scene is not { } scene) return;
foreach (Entity entity in scene.Entities) {
if (entity is KeyboardConfigUI or ButtonConfigUI) {
configOverlayOpen = true;
if (entity.Depth >= Depth) entity.Depth = Depth - 1;
}
if (!entity.Visible || !entity.TagCheck(Tags.HUD) || entity.Depth >= Depth) continue;
externalOverlays.Add(entity);
}
}

bool IMaterialAcrylicOverlay.HasOverlaysForAcrylic => externalOverlays.Count > 0;

void IMaterialAcrylicOverlay.HideOverlaysForAcrylicCapture() {
hiddenOverlays.Clear();
foreach (Entity entity in externalOverlays) {
hiddenOverlays.Add((entity, entity.Visible));
entity.Visible = false;
}
}

void IMaterialAcrylicOverlay.RestoreOverlaysAfterAcrylicCapture() {
foreach ((Entity entity, bool visible) in hiddenOverlays) entity.Visible = visible;
hiddenOverlays.Clear();
}

void IMaterialAcrylicOverlay.RenderOverlaysAboveAcrylic() {
foreach (Entity entity in externalOverlays) {
if (!entity.Visible) continue;
try {
entity.Render();
} catch (Exception exception) {
Logger.LogDetailed(exception, "MicroblocksQolUtils/MaterialAcrylic");
}
}
}

private void SelectItem(TextMenu.Item item) {
if (menu is null || menu.Current == item) return;
menu.Current?.OnLeave?.Invoke();
Expand Down Expand Up @@ -1034,6 +1083,11 @@ private void EnsureTabVisible(ModOptionsLayout layout) {
}

private void UpdateInteractions(ModOptionsLayout layout) {
if (configOverlayOpen) {
// Do not leave hidden setting rows alive as touch targets under the config UI.
MaterialTouch.Suppress(this, motion, TouchContext);
return;
}
List<MaterialInteractionTarget> targets = [
new MaterialInteractionTarget("mod-options.search.tabs", layout.TabSearch,
Focused: searchTarget == SearchTarget.Tabs, TouchKind: "text", TouchText: tabSearchText,
Expand Down
5 changes: 5 additions & 0 deletions Source/MaterialTouch.cs
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,11 @@ internal static void Publish(Entity owner, IEnumerable<MaterialInteractionTarget
surface.Targets = targets.ToArray();
}

internal static void Suppress(Entity owner, MaterialMotionController motion, string context) {
Publish(owner, [], context);
motion.Update([]);
}

private static void FinishSignature(Action<string> orig, string mode) {
Entity? owner = ActiveOwner();
if (owner is null || !surfaces.TryGetValue(owner, out Surface? surface)) { orig(mode); return; }
Expand Down
7 changes: 6 additions & 1 deletion Source/QolSettingsOverlay.cs
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,12 @@ public override void Update() {
recordingRefreshTimer -= Engine.RawDeltaTime;
if (IsRecorderTab && IsRecorderLibrary && recordingRefreshTimer <= 0f) RefreshRecordingFiles();

if (bindingConfig is not null) return;
if (bindingConfig is not null) {
// The key/button config screen handles its own input; do not leave the hidden
// setting rows alive as touch targets underneath it.
MaterialTouch.Suppress(this, motion, TouchContext);
return;
}

OverlayLayout layout = OverlayLayout.Create(1f - Ease.CubeOut(ease));
rowScroll.Update(MaxRowScroll(layout));
Expand Down
Loading