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:
parent
6fa36f8b32
commit
7e8b6b6467
4 changed files with 392 additions and 262 deletions
|
|
@ -4,56 +4,32 @@ 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.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.util.ClickGuiI18n;
|
||||
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.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 {
|
||||
private float scroll;
|
||||
private float smoothedScroll;
|
||||
private final ConfigDiffHeaderRenderer header = new ConfigDiffHeaderRenderer();
|
||||
private final ConfigDiffScrollbar scrollbar = new ConfigDiffScrollbar();
|
||||
private float openAnim;
|
||||
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 panelY;
|
||||
private float panelW;
|
||||
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() {
|
||||
scroll = 0f;
|
||||
smoothedScroll = 0f;
|
||||
draggingScrollbar = false;
|
||||
scrollbar.reset();
|
||||
}
|
||||
|
||||
public void open() {
|
||||
|
|
@ -62,7 +38,7 @@ public final class ConfigDiffPanelComponent {
|
|||
|
||||
public void close() {
|
||||
openTarget = false;
|
||||
draggingScrollbar = false;
|
||||
scrollbar.release();
|
||||
}
|
||||
|
||||
public boolean isActive() {
|
||||
|
|
@ -127,264 +103,62 @@ public final class ConfigDiffPanelComponent {
|
|||
text,
|
||||
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 bodyY = y + 27f * scale;
|
||||
float bodyW = w - 16f * 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);
|
||||
updateScrollbarMetrics(bodyX, bodyY, bodyW, bodyH, maxScroll, scale);
|
||||
if (draggingScrollbar) scrollToMouse(ClickGuiRenderer.getMouseY());
|
||||
scrollbar.begin(bodyX, bodyY, bodyW, bodyH, maxScroll, scale);
|
||||
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);
|
||||
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();
|
||||
|
||||
renderScrollbar(scale, palette);
|
||||
scrollbar.draw(scale, palette, openAnim);
|
||||
}
|
||||
|
||||
public HeaderAction headerAction(float mx, float my, int button) {
|
||||
if (!openTarget || openAnim <= 0.01f) return HeaderAction.NONE;
|
||||
if (button != 0) return HeaderAction.NONE;
|
||||
if (ClickGuiMath.insideRect(mx, my, closeX, closeY, closeW, closeH)) return HeaderAction.CLOSE;
|
||||
if (ClickGuiMath.insideRect(mx, my, saveX, saveY, saveW, saveH)) return HeaderAction.SAVE;
|
||||
if (header.closeHit(mx, my)) return HeaderAction.CLOSE;
|
||||
if (header.saveHit(mx, my)) return HeaderAction.SAVE;
|
||||
return HeaderAction.NONE;
|
||||
}
|
||||
|
||||
public boolean mousePressedScrollbar(float mx, float my, int button) {
|
||||
if (!openTarget || openAnim <= 0.01f) return false;
|
||||
if (button != 0) return false;
|
||||
if (!isScrollbarHovered(mx, my)) return false;
|
||||
draggingScrollbar = true;
|
||||
scrollbarDragOffset = ClickGuiMath.insideRect(mx, my, scrollbarX, scrollbarThumbY, scrollbarW, scrollbarThumbH)
|
||||
? my - scrollbarThumbY
|
||||
: scrollbarThumbH * 0.5f;
|
||||
scrollToMouse(my);
|
||||
return true;
|
||||
return scrollbar.press(mx, my);
|
||||
}
|
||||
|
||||
public void mouseReleased(int button) {
|
||||
if (button == 0) draggingScrollbar = false;
|
||||
if (button == 0) scrollbar.release();
|
||||
}
|
||||
|
||||
public boolean scroll(float mx, float my, double amount) {
|
||||
if (!openTarget || openAnim <= 0.01f) return false;
|
||||
if (!ClickGuiMath.insideRect(mx, my, panelX, panelY, panelW, panelH)) return false;
|
||||
if (!scrollbarVisible && scrollbarMaxScroll <= 0.5f) return true;
|
||||
scroll += (float) (amount * 20f);
|
||||
scrollbar.wheel(amount);
|
||||
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 {
|
||||
NONE,
|
||||
SAVE,
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue