refactor(clickgui): split ConfigDiffPanelComponent to 167 lines

Header buttons, body text and scrollbar move to package-private helpers
under config/diff/; the public API, rendering order and math stay as-is.
This commit is contained in:
loki5512344 2026-09-30 11:07:33 +02:00
parent 6fa36f8b32
commit 7e8b6b6467
Signed by: boba
GPG key ID: 253067914055423B
4 changed files with 392 additions and 262 deletions

View file

@ -4,56 +4,32 @@ import dev.loki.lovisual.config.profile.diff.ConfigProfileDiff;
import dev.loki.lovisual.config.profile.snapshot.ConfigProfileMeta; import dev.loki.lovisual.config.profile.snapshot.ConfigProfileMeta;
import dev.loki.lovisual.features.gui.clickgui.render.ClickGuiRenderer; import dev.loki.lovisual.features.gui.clickgui.render.ClickGuiRenderer;
import dev.loki.lovisual.features.gui.clickgui.layout.screen.settings.SettingsGuiPalette; import dev.loki.lovisual.features.gui.clickgui.layout.screen.settings.SettingsGuiPalette;
import dev.loki.lovisual.features.gui.clickgui.layout.screen.settings.implement.config.diff.ConfigDiffBodyRenderer;
import dev.loki.lovisual.features.gui.clickgui.layout.screen.settings.implement.config.diff.ConfigDiffHeaderRenderer;
import dev.loki.lovisual.features.gui.clickgui.layout.screen.settings.implement.config.diff.ConfigDiffScrollbar;
import dev.loki.lovisual.features.gui.clickgui.layout.screen.settings.render.LayoutRender2D; import dev.loki.lovisual.features.gui.clickgui.layout.screen.settings.render.LayoutRender2D;
import dev.loki.lovisual.features.gui.clickgui.util.ClickGuiI18n; import dev.loki.lovisual.features.gui.clickgui.util.ClickGuiI18n;
import dev.loki.lovisual.features.gui.clickgui.util.ClickGuiMath; import dev.loki.lovisual.features.gui.clickgui.util.ClickGuiMath;
import dev.loki.lovisual.render.engine.animation.AnimationUtility; import dev.loki.lovisual.render.engine.animation.AnimationUtility;
import dev.loki.lovisual.render.engine.renderer.Renderer2D;
import dev.loki.lovisual.render.engine.svg.model.SvgRenderOptions;
import dev.loki.lovisual.render.helpers.clip.ScissorFunction; import dev.loki.lovisual.render.helpers.clip.ScissorFunction;
import dev.loki.lovisual.render.helpers.util.SystemCursor;
/**
* Facade of the config diff popup: open/close animation, panel chrome, input
* dispatch. The header buttons, body text and scrollbar live in the
* {@code config.diff} helpers (see mod/TODO.md 8.5.3).
*/
public final class ConfigDiffPanelComponent { public final class ConfigDiffPanelComponent {
private float scroll; private final ConfigDiffHeaderRenderer header = new ConfigDiffHeaderRenderer();
private float smoothedScroll; private final ConfigDiffScrollbar scrollbar = new ConfigDiffScrollbar();
private float openAnim; private float openAnim;
private boolean openTarget; private boolean openTarget;
private float saveHoverAnim;
private float closeHoverAnim;
private boolean scrollbarVisible;
private boolean draggingScrollbar;
private float scrollbarX;
private float scrollbarY;
private float scrollbarW;
private float scrollbarH;
private float scrollbarThumbY;
private float scrollbarThumbH;
private float scrollbarMaxScroll;
private float scrollbarDragOffset;
private float panelX; private float panelX;
private float panelY; private float panelY;
private float panelW; private float panelW;
private float panelH; private float panelH;
private float saveX;
private float saveY;
private float saveW;
private float saveH;
private float closeX;
private float closeY;
private float closeW;
private float closeH;
private static String stringify(Object value) {
if (value == null) return "null";
String text = String.valueOf(value);
if (text.length() > 34) text = text.substring(0, 31) + "...";
return text;
}
public void resetScroll() { public void resetScroll() {
scroll = 0f; scrollbar.reset();
smoothedScroll = 0f;
draggingScrollbar = false;
} }
public void open() { public void open() {
@ -62,7 +38,7 @@ public final class ConfigDiffPanelComponent {
public void close() { public void close() {
openTarget = false; openTarget = false;
draggingScrollbar = false; scrollbar.release();
} }
public boolean isActive() { public boolean isActive() {
@ -127,264 +103,62 @@ public final class ConfigDiffPanelComponent {
text, text,
false false
); );
renderHeaderActions(x, y, w, mx(), my(), scale, palette); header.render(x, y, w, ClickGuiRenderer.getMouseX(), ClickGuiRenderer.getMouseY(), scale, palette, openAnim);
float bodyX = x + 8f * scale; float bodyX = x + 8f * scale;
float bodyY = y + 27f * scale; float bodyY = y + 27f * scale;
float bodyW = w - 16f * scale; float bodyW = w - 16f * scale;
float bodyH = h - 31f * scale; float bodyH = h - 31f * scale;
float contentH = contentHeight(selected, diff, error, scale); float contentH = ConfigDiffBodyRenderer.contentHeight(selected, diff, error, scale);
float maxScroll = Math.max(0f, contentH - bodyH); float maxScroll = Math.max(0f, contentH - bodyH);
updateScrollbarMetrics(bodyX, bodyY, bodyW, bodyH, maxScroll, scale); scrollbar.begin(bodyX, bodyY, bodyW, bodyH, maxScroll, scale);
if (draggingScrollbar) scrollToMouse(ClickGuiRenderer.getMouseY()); if (scrollbar.dragging()) scrollbar.dragToMouse(ClickGuiRenderer.getMouseY());
scrollbar.smooth();
scroll = ClickGuiMath.clamp(scroll, -maxScroll, 0f);
smoothedScroll = AnimationUtility.approach(smoothedScroll, scroll, draggingScrollbar ? 0.55f : 0.2f);
smoothedScroll = AnimationUtility.snap(smoothedScroll, scroll, draggingScrollbar ? 0.01f : 0.05f);
boolean clipped = ScissorFunction.pushRaw(bodyX, bodyY, bodyW, bodyH); boolean clipped = ScissorFunction.pushRaw(bodyX, bodyY, bodyW, bodyH);
renderBody(bodyX, bodyY + smoothedScroll, bodyW - (scrollbarVisible ? 7f * scale : 0f), bodyY, bodyH, selected, diff, error, scale, palette); ConfigDiffBodyRenderer.render(
bodyX,
bodyY + scrollbar.offset(),
bodyW - scrollbar.gutter(scale),
bodyY,
bodyH,
selected,
diff,
error,
scale,
palette,
openAnim
);
if (clipped) ScissorFunction.pop(); if (clipped) ScissorFunction.pop();
renderScrollbar(scale, palette); scrollbar.draw(scale, palette, openAnim);
} }
public HeaderAction headerAction(float mx, float my, int button) { public HeaderAction headerAction(float mx, float my, int button) {
if (!openTarget || openAnim <= 0.01f) return HeaderAction.NONE; if (!openTarget || openAnim <= 0.01f) return HeaderAction.NONE;
if (button != 0) return HeaderAction.NONE; if (button != 0) return HeaderAction.NONE;
if (ClickGuiMath.insideRect(mx, my, closeX, closeY, closeW, closeH)) return HeaderAction.CLOSE; if (header.closeHit(mx, my)) return HeaderAction.CLOSE;
if (ClickGuiMath.insideRect(mx, my, saveX, saveY, saveW, saveH)) return HeaderAction.SAVE; if (header.saveHit(mx, my)) return HeaderAction.SAVE;
return HeaderAction.NONE; return HeaderAction.NONE;
} }
public boolean mousePressedScrollbar(float mx, float my, int button) { public boolean mousePressedScrollbar(float mx, float my, int button) {
if (!openTarget || openAnim <= 0.01f) return false; if (!openTarget || openAnim <= 0.01f) return false;
if (button != 0) return false; if (button != 0) return false;
if (!isScrollbarHovered(mx, my)) return false; return scrollbar.press(mx, my);
draggingScrollbar = true;
scrollbarDragOffset = ClickGuiMath.insideRect(mx, my, scrollbarX, scrollbarThumbY, scrollbarW, scrollbarThumbH)
? my - scrollbarThumbY
: scrollbarThumbH * 0.5f;
scrollToMouse(my);
return true;
} }
public void mouseReleased(int button) { public void mouseReleased(int button) {
if (button == 0) draggingScrollbar = false; if (button == 0) scrollbar.release();
} }
public boolean scroll(float mx, float my, double amount) { public boolean scroll(float mx, float my, double amount) {
if (!openTarget || openAnim <= 0.01f) return false; if (!openTarget || openAnim <= 0.01f) return false;
if (!ClickGuiMath.insideRect(mx, my, panelX, panelY, panelW, panelH)) return false; if (!ClickGuiMath.insideRect(mx, my, panelX, panelY, panelW, panelH)) return false;
if (!scrollbarVisible && scrollbarMaxScroll <= 0.5f) return true; scrollbar.wheel(amount);
scroll += (float) (amount * 20f);
return true; return true;
} }
private void renderHeaderActions(float x,
float y,
float w,
float mx,
float my,
float scale,
SettingsGuiPalette palette) {
float btn = 16f * scale;
closeX = x + w - btn - 4f * scale;
closeY = y + 3f * scale;
closeW = btn;
closeH = btn;
saveX = closeX - btn - scale;
saveY = closeY;
saveW = btn;
saveH = btn;
boolean saveHover = ClickGuiMath.insideRect(mx, my, saveX, saveY, saveW, saveH);
boolean closeHover = ClickGuiMath.insideRect(mx, my, closeX, closeY, closeW, closeH);
saveHoverAnim = AnimationUtility.approach(saveHoverAnim, saveHover ? 1f : 0f, AnimationUtility.deltaTime(), 10f);
closeHoverAnim = AnimationUtility.approach(closeHoverAnim, closeHover ? 1f : 0f, AnimationUtility.deltaTime(), 10f);
drawHeaderIcon(saveX, saveY, btn, "save", saveHoverAnim, scale, palette);
LayoutRender2D.rect(closeX - scale, y + 6f * scale, 0.5f * scale, 10f * scale, LayoutRender2D.alpha(palette.panelDivider(), openAnim));
drawHeaderIcon(closeX, closeY, btn, "x", closeHoverAnim, scale, palette);
if (saveHover || closeHover) SystemCursor.set(SystemCursor.CursorType.MOVE);
}
private void drawHeaderIcon(float x,
float y,
float size,
String icon,
float hover,
float scale,
SettingsGuiPalette palette) {
boolean save = "save".equals(icon);
int bgA = LayoutRender2D.alpha(SettingsGuiPalette.mix(palette.moduleCardTop(), save ? palette.menuCategorySelectedLeft() : palette.menuCategoryHoverLeft(), 0.14f + hover * 0.22f), openAnim);
int bgB = LayoutRender2D.alpha(SettingsGuiPalette.mix(palette.moduleCardTopStrong(), save ? palette.menuCategorySelectedRight() : palette.menuCategoryHoverRight(), 0.12f + hover * 0.18f), openAnim);
int bgC = LayoutRender2D.alpha(SettingsGuiPalette.mix(SettingsGuiPalette.darken(palette.moduleCardTopStrong(), 0.12f), save ? palette.menuCategorySelectedRight() : palette.menuCategoryHoverRight(), 0.08f + hover * 0.15f), openAnim);
LayoutRender2D.roundedSoftShadow(
x,
y + 0.4f * scale,
size,
size,
3.5f * scale,
4f * scale,
0.015f + hover * 0.02f,
LayoutRender2D.alpha(palette.menuShadow(), openAnim * (0.50f + hover * 0.45f))
);
LayoutRender2D.roundedQuad(x, y, size, size, 3.5f * scale, bgA, bgB, bgC, bgA);
LayoutRender2D.roundedStrokeQuad(
x,
y,
size,
size,
3.5f * scale,
(0.35f + hover * 0.18f) * scale,
LayoutRender2D.alpha(palette.moduleDividerStart(), openAnim * (0.54f + hover * 0.24f)),
LayoutRender2D.alpha(palette.moduleDividerEnd(), openAnim * (0.62f + hover * 0.24f)),
LayoutRender2D.alpha(palette.moduleDividerEnd(), openAnim * (0.48f + hover * 0.20f)),
LayoutRender2D.alpha(palette.moduleDividerStart(), openAnim * (0.54f + hover * 0.24f))
);
int color = LayoutRender2D.alpha(SettingsGuiPalette.mix(palette.panelText(), palette.menuCategoryText(), 0.16f + hover * 0.38f), openAnim);
Renderer2D.COLOR.svg(
icon,
x + 4.6f * scale,
y + 4.6f * scale,
size - 9.2f * scale,
size - 9.2f * scale,
SvgRenderOptions.overrideColor(color)
);
}
private float mx() {
return ClickGuiRenderer.getMouseX();
}
private float my() {
return ClickGuiRenderer.getMouseY();
}
private void renderBody(float x,
float y,
float w,
float clipY,
float clipH,
ConfigProfileMeta selected,
ConfigProfileDiff diff,
String error,
float scale,
SettingsGuiPalette palette) {
if (error != null && !error.isBlank()) {
drawLine(ClickGuiI18n.tr("clickgui.settings.config.diff.error", "Diff error: %s", error), x, y, 7.2f * scale, palette.panelMuted());
return;
}
if (selected == null) {
drawLine(ClickGuiI18n.tr("clickgui.settings.config.diff.select_config", "Select a config and press diff."), x, y, 7.2f * scale, palette.panelMuted());
return;
}
if (diff == null) {
drawLine(selected.name(), x, y, 7.2f * scale, palette.panelText());
drawLine(ClickGuiI18n.tr("clickgui.settings.config.diff.not_loaded", "Diff is not loaded."), x, y + 10f * scale, 7.2f * scale, palette.panelMuted());
return;
}
if (diff.isEmpty()) {
drawLine(selected.name(), x, y, 7.2f * scale, palette.panelText());
drawLine(ClickGuiI18n.tr("clickgui.settings.config.diff.no_differences", "No differences."), x, y + 11f * scale, 7.2f * scale, palette.panelMuted());
return;
}
float cy = y;
drawVisibleLine(ClickGuiRenderer.fitText(ClickGuiRenderer.getInterMedium(), selected.name(), 7.4f * scale, w), x, cy, 7.4f * scale, palette.panelText(), clipY, clipH, scale);
cy += 11f * scale;
drawVisibleLine(ClickGuiI18n.tr("clickgui.settings.config.diff.changed", "Changes: %s", diff.valueChangeCount()), x, cy, 7f * scale, palette.panelMuted(), clipY, clipH, scale);
cy += 13f * scale;
for (ConfigProfileDiff.Entry entry : diff.entries()) {
String owner = ClickGuiRenderer.fitText(ClickGuiRenderer.getInterMedium(), entry.displayName(), 7.4f * scale, w);
drawVisibleLine(owner, x, cy, 7.4f * scale, palette.panelText(), clipY, clipH, scale);
cy += 9.5f * scale;
for (ConfigProfileDiff.Value value : entry.values()) {
String text = value.key() + ": " + stringify(value.currentValue()) + " -> " + stringify(value.selectedValue());
text = ClickGuiRenderer.fitText(ClickGuiRenderer.getInterRegular(), text, 6.7f * scale, w - 5f * scale);
drawVisibleLine(text, x + 5f * scale, cy, 6.7f * scale, palette.panelMuted(), clipY, clipH, scale);
cy += 8.5f * scale;
}
cy += 4f * scale;
}
}
private float contentHeight(ConfigProfileMeta selected, ConfigProfileDiff diff, String error, float scale) {
if (error != null && !error.isBlank()) return 11f * scale;
if (selected == null) return 11f * scale;
if (diff == null || diff.isEmpty()) return 23f * scale;
float h = 24f * scale;
for (ConfigProfileDiff.Entry entry : diff.entries()) {
h += 9.5f * scale;
h += entry.values().size() * 8.5f * scale;
h += 4f * scale;
}
return h;
}
private void renderScrollbar(float scale, SettingsGuiPalette palette) {
if (!scrollbarVisible) return;
float mx = ClickGuiRenderer.getMouseX();
float my = ClickGuiRenderer.getMouseY();
if (isScrollbarHovered(mx, my) || draggingScrollbar) SystemCursor.set(SystemCursor.CursorType.SCROLL);
LayoutRender2D.roundedQuad(scrollbarX, scrollbarY, scrollbarW, scrollbarH, 2f * scale,
LayoutRender2D.alpha(palette.moduleScrollTrackA(), openAnim),
LayoutRender2D.alpha(palette.moduleScrollTrackB(), openAnim),
LayoutRender2D.alpha(palette.moduleScrollTrackB(), openAnim),
LayoutRender2D.alpha(palette.moduleScrollTrackA(), openAnim));
LayoutRender2D.roundedQuad(scrollbarX, scrollbarThumbY, scrollbarW, scrollbarThumbH, 2f * scale,
LayoutRender2D.alpha(palette.moduleScrollHandleA(), openAnim),
LayoutRender2D.alpha(palette.moduleScrollHandleB(), openAnim),
LayoutRender2D.alpha(palette.moduleScrollHandleB(), openAnim),
LayoutRender2D.alpha(palette.moduleScrollHandleA(), openAnim));
}
private void updateScrollbarMetrics(float x, float y, float w, float h, float maxScroll, float scale) {
scrollbarVisible = maxScroll > 0.5f;
scrollbarMaxScroll = Math.max(0f, maxScroll);
if (!scrollbarVisible) {
draggingScrollbar = false;
scrollbarX = scrollbarY = scrollbarW = scrollbarH = scrollbarThumbY = scrollbarThumbH = 0f;
return;
}
scrollbarW = 3f * scale;
scrollbarX = x + w - scrollbarW - 1.5f * scale;
scrollbarY = y + 2f * scale;
scrollbarH = Math.max(1f, h - 4f * scale);
scrollbarThumbH = Math.max(16f * scale, scrollbarH * (scrollbarH / (scrollbarMaxScroll + scrollbarH)));
float scrollRatio = scrollbarMaxScroll <= 0f ? 0f : (-smoothedScroll / scrollbarMaxScroll);
scrollbarThumbY = scrollbarY + (scrollbarH - scrollbarThumbH) * AnimationUtility.clamp(scrollRatio, 0f, 1f);
}
private boolean isScrollbarHovered(float mx, float my) {
if (!scrollbarVisible) return false;
float pad = 4f;
return ClickGuiMath.insideRect(mx, my, scrollbarX - pad, scrollbarY - pad, scrollbarW + pad * 2f, scrollbarH + pad * 2f);
}
private void scrollToMouse(float my) {
if (!scrollbarVisible || scrollbarMaxScroll <= 0f) return;
float span = scrollbarH - scrollbarThumbH;
if (span <= 0.5f) return;
float thumbTop = AnimationUtility.clamp(my - scrollbarDragOffset, scrollbarY, scrollbarY + span);
float ratio = (thumbTop - scrollbarY) / span;
scroll = -scrollbarMaxScroll * AnimationUtility.clamp(ratio, 0f, 1f);
}
private void drawVisibleLine(String text, float x, float y, float size, int color, float clipY, float clipH, float scale) {
if (y + 9f * scale < clipY || y > clipY + clipH) return;
drawLine(text, x, y, size, color);
}
private void drawLine(String text, float x, float y, float size, int color) {
ClickGuiRenderer.drawText(ClickGuiRenderer.getInterRegular(), text, x, y, size, LayoutRender2D.alpha(color, openAnim), false);
}
public enum HeaderAction { public enum HeaderAction {
NONE, NONE,
SAVE, SAVE,

View file

@ -0,0 +1,107 @@
package dev.loki.lovisual.features.gui.clickgui.layout.screen.settings.implement.config.diff;
import dev.loki.lovisual.config.profile.diff.ConfigProfileDiff;
import dev.loki.lovisual.config.profile.snapshot.ConfigProfileMeta;
import dev.loki.lovisual.features.gui.clickgui.render.ClickGuiRenderer;
import dev.loki.lovisual.features.gui.clickgui.layout.screen.settings.SettingsGuiPalette;
import dev.loki.lovisual.features.gui.clickgui.layout.screen.settings.render.LayoutRender2D;
import dev.loki.lovisual.features.gui.clickgui.util.ClickGuiI18n;
/**
* Body of the diff panel: the empty-state messages and the per-setting change
* lines, plus the height math the scroller needs before painting. Stateless —
* the panel passes its fade value in on every call.
*/
public final class ConfigDiffBodyRenderer {
private ConfigDiffBodyRenderer() {
}
public static void render(float x,
float y,
float w,
float clipY,
float clipH,
ConfigProfileMeta selected,
ConfigProfileDiff diff,
String error,
float scale,
SettingsGuiPalette palette,
float openAnim) {
if (error != null && !error.isBlank()) {
drawLine(ClickGuiI18n.tr("clickgui.settings.config.diff.error", "Diff error: %s", error), x, y, 7.2f * scale, palette.panelMuted(), openAnim);
return;
}
if (selected == null) {
drawLine(ClickGuiI18n.tr("clickgui.settings.config.diff.select_config", "Select a config and press diff."), x, y, 7.2f * scale, palette.panelMuted(), openAnim);
return;
}
if (diff == null) {
drawLine(selected.name(), x, y, 7.2f * scale, palette.panelText(), openAnim);
drawLine(ClickGuiI18n.tr("clickgui.settings.config.diff.not_loaded", "Diff is not loaded."), x, y + 10f * scale, 7.2f * scale, palette.panelMuted(), openAnim);
return;
}
if (diff.isEmpty()) {
drawLine(selected.name(), x, y, 7.2f * scale, palette.panelText(), openAnim);
drawLine(ClickGuiI18n.tr("clickgui.settings.config.diff.no_differences", "No differences."), x, y + 11f * scale, 7.2f * scale, palette.panelMuted(), openAnim);
return;
}
float cy = y;
drawVisibleLine(ClickGuiRenderer.fitText(ClickGuiRenderer.getInterMedium(), selected.name(), 7.4f * scale, w), x, cy, 7.4f * scale, palette.panelText(), clipY, clipH, scale, openAnim);
cy += 11f * scale;
drawVisibleLine(ClickGuiI18n.tr("clickgui.settings.config.diff.changed", "Changes: %s", diff.valueChangeCount()), x, cy, 7f * scale, palette.panelMuted(), clipY, clipH, scale, openAnim);
cy += 13f * scale;
for (ConfigProfileDiff.Entry entry : diff.entries()) {
String owner = ClickGuiRenderer.fitText(ClickGuiRenderer.getInterMedium(), entry.displayName(), 7.4f * scale, w);
drawVisibleLine(owner, x, cy, 7.4f * scale, palette.panelText(), clipY, clipH, scale, openAnim);
cy += 9.5f * scale;
for (ConfigProfileDiff.Value value : entry.values()) {
String text = value.key() + ": " + stringify(value.currentValue()) + " -> " + stringify(value.selectedValue());
text = ClickGuiRenderer.fitText(ClickGuiRenderer.getInterRegular(), text, 6.7f * scale, w - 5f * scale);
drawVisibleLine(text, x + 5f * scale, cy, 6.7f * scale, palette.panelMuted(), clipY, clipH, scale, openAnim);
cy += 8.5f * scale;
}
cy += 4f * scale;
}
}
public static float contentHeight(ConfigProfileMeta selected, ConfigProfileDiff diff, String error, float scale) {
if (error != null && !error.isBlank()) return 11f * scale;
if (selected == null) return 11f * scale;
if (diff == null || diff.isEmpty()) return 23f * scale;
float h = 24f * scale;
for (ConfigProfileDiff.Entry entry : diff.entries()) {
h += 9.5f * scale;
h += entry.values().size() * 8.5f * scale;
h += 4f * scale;
}
return h;
}
private static String stringify(Object value) {
if (value == null) return "null";
String text = String.valueOf(value);
if (text.length() > 34) text = text.substring(0, 31) + "...";
return text;
}
private static void drawVisibleLine(String text,
float x,
float y,
float size,
int color,
float clipY,
float clipH,
float scale,
float openAnim) {
if (y + 9f * scale < clipY || y > clipY + clipH) return;
drawLine(text, x, y, size, color, openAnim);
}
private static void drawLine(String text, float x, float y, float size, int color, float openAnim) {
ClickGuiRenderer.drawText(ClickGuiRenderer.getInterRegular(), text, x, y, size, LayoutRender2D.alpha(color, openAnim), false);
}
}

View file

@ -0,0 +1,112 @@
package dev.loki.lovisual.features.gui.clickgui.layout.screen.settings.implement.config.diff;
import dev.loki.lovisual.features.gui.clickgui.render.ClickGuiRenderer;
import dev.loki.lovisual.features.gui.clickgui.layout.screen.settings.SettingsGuiPalette;
import dev.loki.lovisual.features.gui.clickgui.layout.screen.settings.render.LayoutRender2D;
import dev.loki.lovisual.features.gui.clickgui.util.ClickGuiMath;
import dev.loki.lovisual.render.engine.animation.AnimationUtility;
import dev.loki.lovisual.render.engine.renderer.Renderer2D;
import dev.loki.lovisual.render.engine.svg.model.SvgRenderOptions;
import dev.loki.lovisual.render.helpers.util.SystemCursor;
/**
* Header chrome of the diff panel: the save/close icon buttons — their rects,
* hover animation, painting and hit-testing. Owned by
* {@code ConfigDiffPanelComponent}, which forwards the panel's fade value here.
*/
public final class ConfigDiffHeaderRenderer {
private float saveX;
private float saveY;
private float saveW;
private float saveH;
private float closeX;
private float closeY;
private float closeW;
private float closeH;
private float saveHoverAnim;
private float closeHoverAnim;
public void render(float x,
float y,
float w,
float mx,
float my,
float scale,
SettingsGuiPalette palette,
float openAnim) {
float btn = 16f * scale;
closeX = x + w - btn - 4f * scale;
closeY = y + 3f * scale;
closeW = btn;
closeH = btn;
saveX = closeX - btn - scale;
saveY = closeY;
saveW = btn;
saveH = btn;
boolean saveHover = ClickGuiMath.insideRect(mx, my, saveX, saveY, saveW, saveH);
boolean closeHover = ClickGuiMath.insideRect(mx, my, closeX, closeY, closeW, closeH);
saveHoverAnim = AnimationUtility.approach(saveHoverAnim, saveHover ? 1f : 0f, AnimationUtility.deltaTime(), 10f);
closeHoverAnim = AnimationUtility.approach(closeHoverAnim, closeHover ? 1f : 0f, AnimationUtility.deltaTime(), 10f);
drawIcon(saveX, saveY, btn, "save", saveHoverAnim, scale, palette, openAnim);
LayoutRender2D.rect(closeX - scale, y + 6f * scale, 0.5f * scale, 10f * scale, LayoutRender2D.alpha(palette.panelDivider(), openAnim));
drawIcon(closeX, closeY, btn, "x", closeHoverAnim, scale, palette, openAnim);
if (saveHover || closeHover) SystemCursor.set(SystemCursor.CursorType.MOVE);
}
public boolean saveHit(float mx, float my) {
return ClickGuiMath.insideRect(mx, my, saveX, saveY, saveW, saveH);
}
public boolean closeHit(float mx, float my) {
return ClickGuiMath.insideRect(mx, my, closeX, closeY, closeW, closeH);
}
private void drawIcon(float x,
float y,
float size,
String icon,
float hover,
float scale,
SettingsGuiPalette palette,
float openAnim) {
boolean save = "save".equals(icon);
int bgA = LayoutRender2D.alpha(SettingsGuiPalette.mix(palette.moduleCardTop(), save ? palette.menuCategorySelectedLeft() : palette.menuCategoryHoverLeft(), 0.14f + hover * 0.22f), openAnim);
int bgB = LayoutRender2D.alpha(SettingsGuiPalette.mix(palette.moduleCardTopStrong(), save ? palette.menuCategorySelectedRight() : palette.menuCategoryHoverRight(), 0.12f + hover * 0.18f), openAnim);
int bgC = LayoutRender2D.alpha(SettingsGuiPalette.mix(SettingsGuiPalette.darken(palette.moduleCardTopStrong(), 0.12f), save ? palette.menuCategorySelectedRight() : palette.menuCategoryHoverRight(), 0.08f + hover * 0.15f), openAnim);
LayoutRender2D.roundedSoftShadow(
x,
y + 0.4f * scale,
size,
size,
3.5f * scale,
4f * scale,
0.015f + hover * 0.02f,
LayoutRender2D.alpha(palette.menuShadow(), openAnim * (0.50f + hover * 0.45f))
);
LayoutRender2D.roundedQuad(x, y, size, size, 3.5f * scale, bgA, bgB, bgC, bgA);
LayoutRender2D.roundedStrokeQuad(
x,
y,
size,
size,
3.5f * scale,
(0.35f + hover * 0.18f) * scale,
LayoutRender2D.alpha(palette.moduleDividerStart(), openAnim * (0.54f + hover * 0.24f)),
LayoutRender2D.alpha(palette.moduleDividerEnd(), openAnim * (0.62f + hover * 0.24f)),
LayoutRender2D.alpha(palette.moduleDividerEnd(), openAnim * (0.48f + hover * 0.20f)),
LayoutRender2D.alpha(palette.moduleDividerStart(), openAnim * (0.54f + hover * 0.24f))
);
int color = LayoutRender2D.alpha(SettingsGuiPalette.mix(palette.panelText(), palette.menuCategoryText(), 0.16f + hover * 0.38f), openAnim);
Renderer2D.COLOR.svg(
icon,
x + 4.6f * scale,
y + 4.6f * scale,
size - 9.2f * scale,
size - 9.2f * scale,
SvgRenderOptions.overrideColor(color)
);
}
}

View file

@ -0,0 +1,137 @@
package dev.loki.lovisual.features.gui.clickgui.layout.screen.settings.implement.config.diff;
import dev.loki.lovisual.features.gui.clickgui.render.ClickGuiRenderer;
import dev.loki.lovisual.features.gui.clickgui.layout.screen.settings.SettingsGuiPalette;
import dev.loki.lovisual.features.gui.clickgui.layout.screen.settings.render.LayoutRender2D;
import dev.loki.lovisual.features.gui.clickgui.util.ClickGuiMath;
import dev.loki.lovisual.render.engine.animation.AnimationUtility;
import dev.loki.lovisual.render.helpers.util.SystemCursor;
/**
* Scroll state of the diff panel: wheel accumulation, clamping/smoothing of the
* offset, thumb geometry, drag handling and painting. The panel drives it in
* phases — {@link #begin} (geometry), {@link #dragToMouse} while dragging,
* {@link #smooth} (offset), then {@link #draw}.
*/
public final class ConfigDiffScrollbar {
private boolean visible;
private boolean dragging;
private float x;
private float y;
private float w;
private float h;
private float thumbY;
private float thumbH;
private float maxScroll;
private float dragOffset;
private float scroll;
private float smoothed;
public void reset() {
scroll = 0f;
smoothed = 0f;
dragging = false;
}
public void release() {
dragging = false;
}
public boolean visible() {
return visible;
}
public boolean dragging() {
return dragging;
}
/** Smoothed offset the body is painted at. */
public float offset() {
return smoothed;
}
/** Wheel input: true when a scrollbar exists (a bare panel absorbs the event). */
public boolean wheel(double amount) {
if (!visible && maxScroll <= 0.5f) return false;
scroll += (float) (amount * 20f);
return true;
}
public boolean press(float mx, float my) {
if (!hovered(mx, my)) return false;
dragging = true;
dragOffset = ClickGuiMath.insideRect(mx, my, x, thumbY, w, thumbH)
? my - thumbY
: thumbH * 0.5f;
dragToMouse(my);
return true;
}
/** Thumb geometry for the current body rect and content height. */
public void begin(float x, float y, float w, float h, float maxScroll, float scale) {
this.x = x;
this.y = y;
this.w = w;
this.h = h;
visible = maxScroll > 0.5f;
this.maxScroll = Math.max(0f, maxScroll);
if (!visible) {
dragging = false;
this.x = this.y = this.w = this.h = thumbY = thumbH = 0f;
return;
}
this.w = 3f * scale;
this.x = x + w - this.w - 1.5f * scale;
this.y = y + 2f * scale;
this.h = Math.max(1f, h - 4f * scale);
thumbH = Math.max(16f * scale, this.h * (this.h / (this.maxScroll + this.h)));
float ratio = this.maxScroll <= 0f ? 0f : (-smoothed / this.maxScroll);
thumbY = this.y + (this.h - thumbH) * AnimationUtility.clamp(ratio, 0f, 1f);
}
/** Clamp + smooth the offset toward the target for this frame. */
public void smooth() {
scroll = ClickGuiMath.clamp(scroll, -maxScroll, 0f);
smoothed = AnimationUtility.approach(smoothed, scroll, dragging ? 0.55f : 0.2f);
smoothed = AnimationUtility.snap(smoothed, scroll, dragging ? 0.01f : 0.05f);
}
public void dragToMouse(float my) {
if (!dragging) return;
if (!visible || maxScroll <= 0f) return;
float span = h - thumbH;
if (span <= 0.5f) return;
float thumbTop = AnimationUtility.clamp(my - dragOffset, y, y + span);
float ratio = (thumbTop - y) / span;
scroll = -maxScroll * AnimationUtility.clamp(ratio, 0f, 1f);
}
public void draw(float scale, SettingsGuiPalette palette, float openAnim) {
if (!visible) return;
float mx = ClickGuiRenderer.getMouseX();
float my = ClickGuiRenderer.getMouseY();
if (hovered(mx, my) || dragging) SystemCursor.set(SystemCursor.CursorType.SCROLL);
LayoutRender2D.roundedQuad(x, y, w, h, 2f * scale,
LayoutRender2D.alpha(palette.moduleScrollTrackA(), openAnim),
LayoutRender2D.alpha(palette.moduleScrollTrackB(), openAnim),
LayoutRender2D.alpha(palette.moduleScrollTrackB(), openAnim),
LayoutRender2D.alpha(palette.moduleScrollTrackA(), openAnim));
LayoutRender2D.roundedQuad(x, thumbY, w, thumbH, 2f * scale,
LayoutRender2D.alpha(palette.moduleScrollHandleA(), openAnim),
LayoutRender2D.alpha(palette.moduleScrollHandleB(), openAnim),
LayoutRender2D.alpha(palette.moduleScrollHandleB(), openAnim),
LayoutRender2D.alpha(palette.moduleScrollHandleA(), openAnim));
}
/** Body width lost to the scrollbar column, when there is one. */
public float gutter(float scale) {
return visible ? 7f * scale : 0f;
}
private boolean hovered(float mx, float my) {
if (!visible) return false;
float pad = 4f;
return ClickGuiMath.insideRect(mx, my, x - pad, y - pad, w + pad * 2f, h + pad * 2f);
}
}