From b7542f97f6bf529dae09b2ef9c769c1e2bed39b5 Mon Sep 17 00:00:00 2001 From: Jurre Groenendijk Date: Sat, 18 Jul 2026 08:08:24 +0000 Subject: [PATCH 1/4] Add commit logging workflow (#64) * Add commit logging workflow * 1.21 -> 1.21.1 and add nosync: check * TARGET_REPO -> REPO and rename secret to secrets.PAT --- .../workflows/log-commits-version-sync.yml | 127 ++++++++++++++++++ 1 file changed, 127 insertions(+) create mode 100644 .github/workflows/log-commits-version-sync.yml diff --git a/.github/workflows/log-commits-version-sync.yml b/.github/workflows/log-commits-version-sync.yml new file mode 100644 index 0000000..be44d02 --- /dev/null +++ b/.github/workflows/log-commits-version-sync.yml @@ -0,0 +1,127 @@ +name: Log Commits for Version Sync + +on: + push: + branches: + - '1.20.1' + - '1.21.1' + +permissions: + contents: read + +concurrency: + group: log-commits-to-project + cancel-in-progress: false + +jobs: + log: + runs-on: ubuntu-latest + steps: + - name: Create an issue on the project board for this push + env: + GH_TOKEN: ${{ secrets.PAT }} + PROJECT_BOARD_URL: ${{ secrets.VERSION_SYNC_PROJECT_BOARD_URL }} + BRANCH: ${{ github.ref_name }} + REPO: ${{ github.repository }} + AFTER: ${{ github.event.after }} + HEAD_COMMIT_MSG: ${{ github.event.head_commit.message }} + COMMITS_JSON: ${{ toJSON(github.event.commits) }} + run: | + set -euo pipefail + + if [ -z "${GH_TOKEN:-}" ]; then + echo "::error::PROJECT_TOKEN secret is not set; a PAT with the 'project' scope is required." + exit 1 + fi + if ! gh api user --jq '.login' >/dev/null 2>&1; then + echo "::error::PROJECT_TOKEN failed to authenticate against the GitHub API." + exit 1 + fi + + if [ -z "${PROJECT_BOARD_URL:-}" ]; then + echo "::error::PROJECT_BOARD_URL secret is not set." + exit 1 + fi + if [[ "$PROJECT_BOARD_URL" =~ github\.com/(users|orgs)/([^/]+)/projects/([0-9]+) ]]; then + OWNER="${BASH_REMATCH[2]}" + NUMBER="${BASH_REMATCH[3]}" + else + echo "::error::PROJECT_BOARD_URL '$PROJECT_BOARD_URL' is not a valid project board URL." + exit 1 + fi + + # each branch's commits need porting to the other branch + case "$BRANCH" in + 1.20.1) STATUS_NAME="To Port 1.20->1.21" ;; + 1.21.1) STATUS_NAME="To Port 1.21->1.20" ;; + *) STATUS_NAME="" ;; + esac + + # build a markdown list item for each commit in this push, skipping any + # commit titled "nosync:..." or whose associated PR carries the + # "Ignore Version Sync" label + LINES_FILE="$(mktemp)" + while read -r commit; do + [ -n "$commit" ] || continue + sha="$(jq -r '.id' <<<"$commit")" + short="${sha:0:7}" + title="$(jq -r '.message' <<<"$commit" | head -n1)" + url="$(jq -r '.url' <<<"$commit")" + + if [[ "$title" == nosync:* ]]; then + echo "Commit $short is marked 'nosync:', skipping." + continue + fi + + ignored="$(gh api "repos/$REPO/commits/$sha/pulls" \ + --jq 'any(.[]; .labels[].name == "Ignore Version Sync")')" + if [ "$ignored" = "true" ]; then + echo "Commit $short belongs to a PR labeled 'Ignore Version Sync', skipping." + continue + fi + + printf -- '- [`%s`](%s) %s\n' "$short" "$url" "$title" >>"$LINES_FILE" + done < <(jq -c '.[]' <<<"$COMMITS_JSON") + + if [ ! -s "$LINES_FILE" ]; then + echo "No commits to add to the board." + exit 0 + fi + COUNT="$(wc -l <"$LINES_FILE" | tr -d ' ')" + + # resolve the project's node id and its "Status" field/option ids by name + PROJECT_ID="$(gh project view "$NUMBER" --owner "$OWNER" --format json --jq '.id')" + FIELDS_JSON="$(gh project field-list "$NUMBER" --owner "$OWNER" --format json)" + FIELD_ID="$(jq -r '.fields[] | select(.name == "Status") | .id' <<<"$FIELDS_JSON")" + OPTION_ID="" + if [ -n "$STATUS_NAME" ]; then + OPTION_ID="$(jq -r --arg n "$STATUS_NAME" \ + '.fields[] | select(.name == "Status") | .options[] | select(.name == $n) | .id' \ + <<<"$FIELDS_JSON")" + if [ -z "$OPTION_ID" ]; then + echo "::error::Board has no 'Status' option named '$STATUS_NAME'." + exit 1 + fi + fi + + # create a single issue for this push + BODY="$(printf 'Commits pushed to `%s` (up to `%s`):\n\n%s\n' \ + "$BRANCH" "${AFTER:0:7}" "$(cat "$LINES_FILE")")" + HEAD_TITLE="$(printf '%s' "$HEAD_COMMIT_MSG" | head -n1)" + ISSUE_URL="$(gh issue create --repo "$REPO" \ + --title "$HEAD_TITLE" \ + --body "$BODY")" + echo "Created issue $ISSUE_URL" + + # add it to the board + ITEM_ID="$(gh project item-add "$NUMBER" --owner "$OWNER" --url "$ISSUE_URL" \ + --format json --jq '.id')" + + # set its label to the correct port direction for this branch + if [ -n "$OPTION_ID" ]; then + gh project item-edit --id "$ITEM_ID" --project-id "$PROJECT_ID" \ + --field-id "$FIELD_ID" --single-select-option-id "$OPTION_ID" + echo "Set status to '$STATUS_NAME'." + fi + + echo "Added issue to board with $COUNT commit(s)." From 1ebdaeebfa10d253147befca0b5a2317f8e8c63a Mon Sep 17 00:00:00 2001 From: brachy84 Date: Sat, 18 Jul 2026 10:23:11 +0200 Subject: [PATCH 2/4] fix fluid slot overlay --- src/main/java/brachy/modularui/widgets/slot/FluidSlot.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/brachy/modularui/widgets/slot/FluidSlot.java b/src/main/java/brachy/modularui/widgets/slot/FluidSlot.java index 488b390..44a02c1 100644 --- a/src/main/java/brachy/modularui/widgets/slot/FluidSlot.java +++ b/src/main/java/brachy/modularui/widgets/slot/FluidSlot.java @@ -163,6 +163,7 @@ protected boolean displayAmountText() { @Override public void drawOverlay(ModularGuiContext context, WidgetThemeEntry widgetTheme) { + super.drawOverlay(context, widgetTheme); if (isHovering()) { RenderSystem.colorMask(true, true, true, false); GuiDraw.drawRect(context.getGraphics(), 1, 1, getArea().w() - 2, getArea().h() - 2, getSlotHoverColor()); From 247baee22f77c6016c7bbb307a737b06a75a6333 Mon Sep 17 00:00:00 2001 From: brachy84 Date: Sat, 18 Jul 2026 16:41:18 +0200 Subject: [PATCH 3/4] improve fluid slot amount display --- .../brachy/modularui/api/value/IValue.java | 5 ++ .../brachy/modularui/drawable/GuiDraw.java | 6 +- .../modularui/utils/FormattingUtil.java | 58 ------------------- .../modularui/utils/math/NumberFormat.java | 5 +- .../brachy/modularui/utils/math/SIPrefix.java | 2 +- .../brachy/modularui/widget/WidgetTree.java | 6 +- .../widgets/AbstractFluidDisplayWidget.java | 12 ++-- .../modularui/widgets/FluidDisplayWidget.java | 2 +- .../modularui/widgets/slot/FluidSlot.java | 3 +- 9 files changed, 21 insertions(+), 78 deletions(-) diff --git a/src/main/java/brachy/modularui/api/value/IValue.java b/src/main/java/brachy/modularui/api/value/IValue.java index d51b671..7e2d7a3 100644 --- a/src/main/java/brachy/modularui/api/value/IValue.java +++ b/src/main/java/brachy/modularui/api/value/IValue.java @@ -25,6 +25,11 @@ public interface IValue extends ISyncOrValue { */ void setValue(@UnknownNullability T value); + default T getOrDefault(T defaultValue) { + T t = getValue(); + return t != null ? t : defaultValue; + } + Class getValueType(); default boolean isValueOfType(Class type) { diff --git a/src/main/java/brachy/modularui/drawable/GuiDraw.java b/src/main/java/brachy/modularui/drawable/GuiDraw.java index 6217843..a0b122c 100644 --- a/src/main/java/brachy/modularui/drawable/GuiDraw.java +++ b/src/main/java/brachy/modularui/drawable/GuiDraw.java @@ -1,6 +1,5 @@ package brachy.modularui.drawable; -import brachy.modularui.utils.MUIRenderTypes; import brachy.modularui.api.drawable.IRichTextBuilder; import brachy.modularui.drawable.text.TextRenderer; import brachy.modularui.screen.event.RichTooltipEvent; @@ -8,8 +7,9 @@ import brachy.modularui.screen.viewport.ModularGuiContext; import brachy.modularui.utils.Alignment; import brachy.modularui.utils.Color; -import brachy.modularui.utils.FormattingUtil; +import brachy.modularui.utils.MUIRenderTypes; import brachy.modularui.utils.RectangleF; +import brachy.modularui.utils.math.NumberFormat; import brachy.modularui.widget.sizer.Area; import net.minecraft.client.Minecraft; @@ -705,7 +705,7 @@ public static void drawStandardSlotAmountText(GuiContext context, int amount, St public static void drawAmountText(ModularGuiContext context, int amount, String format, int x, int y, int width, int height, Alignment alignment, float z) { if (amount <= 1) return; - String amountText = FormattingUtil.formatNumberReadable(amount, false); + String amountText = NumberFormat.AMOUNT_TEXT.format(amount); if (format != null) { amountText = format + amountText; } diff --git a/src/main/java/brachy/modularui/utils/FormattingUtil.java b/src/main/java/brachy/modularui/utils/FormattingUtil.java index 2a1987d..6bc35b4 100755 --- a/src/main/java/brachy/modularui/utils/FormattingUtil.java +++ b/src/main/java/brachy/modularui/utils/FormattingUtil.java @@ -6,9 +6,7 @@ import org.apache.commons.lang3.StringUtils; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import java.math.BigInteger; import java.text.DecimalFormat; import java.text.NumberFormat; import java.util.Arrays; @@ -190,62 +188,6 @@ public static String formatNumbers(Object number) { return NUMBER_FORMAT.format(number); } - public static String formatNumberReadable(long number) { - return formatNumberReadable(number, false); - } - - public static String formatNumberReadable(long number, boolean milli) { - return formatNumberReadable(number, milli, DECIMAL_FORMAT_1F, null); - } - - public static String formatNumberReadable2F(double number, boolean milli) { - return formatNumberReadable(number, milli, DECIMAL_FORMAT_2F, null); - } - - /** - * Format number in engineering notation with SI prefixes [m, k, M, G, T, P, E, Z] - * - * @param number Number to format - * @param milli Whether the passed number is already in millis (e.g., mB) - * @param fmt Formatter to use for compacted number - * @param unit Optional unit to append - * @return Compacted number with SI prefix - */ - public static String formatNumberReadable(double number, boolean milli, NumberFormat fmt, @Nullable String unit) { - StringBuilder sb = new StringBuilder(); - if (number < 0) { - number = -number; - sb.append('-'); - } - - if (milli && number >= 1e3) { - milli = false; - number /= 1e3; - } - - int exp = 0; - if (number >= 1e3) { - exp = (int) Math.log10(number) / 3; - if (exp > 7) exp = 7; - if (exp > 0) number /= Math.pow(1e3, exp); - } - - sb.append(fmt.format(number)); - if (exp > 0) sb.append("kMGTPEZ".charAt(exp - 1)); - else if (milli && number != 0) sb.append('m'); - - if (unit != null) sb.append(unit); - return sb.toString(); - } - - public static String formatNumberOrSic(BigInteger number, BigInteger threshold) { - return number.compareTo(threshold) > 0 ? DECIMAL_FORMAT_SIC_2F.format(number) : formatNumbers(number); - } - - public static String formatBuckets(long mB) { - return formatNumberReadable(mB, true, DECIMAL_FORMAT_2F, "B"); - } - @NotNull public static String formatNumber2Places(float number) { return DECIMAL_FORMAT_2F.format(number); diff --git a/src/main/java/brachy/modularui/utils/math/NumberFormat.java b/src/main/java/brachy/modularui/utils/math/NumberFormat.java index 41ddc87..3b46cfe 100644 --- a/src/main/java/brachy/modularui/utils/math/NumberFormat.java +++ b/src/main/java/brachy/modularui/utils/math/NumberFormat.java @@ -31,6 +31,7 @@ public class NumberFormat { public static final Params AMOUNT_TEXT = DEFAULT.copyToBuilder() .roundingMode(RoundingMode.DOWN) + .maxLength(3) .build(); public static Params params(DecimalFormat format, int maxLength, boolean considerOnlyDecimalsForLength, @@ -56,8 +57,8 @@ public Params(DecimalFormat format, int maxLength, boolean considerOnlyDecimalsF this.considerMinusForLength = considerMinusForLength; this.considerSuffixForLength = considerSuffixForLength; this.spaceAfterNumber = spaceAfterNumber; - if (!this.considerOnlyDecimalsForLength && this.maxLength < 4) { - throw new IllegalArgumentException("Max length must be at least 4 characters"); + if (!this.considerOnlyDecimalsForLength && this.maxLength < 1) { + throw new IllegalArgumentException("Max length must be at least 1 character"); } } diff --git a/src/main/java/brachy/modularui/utils/math/SIPrefix.java b/src/main/java/brachy/modularui/utils/math/SIPrefix.java index 84c0418..744225f 100644 --- a/src/main/java/brachy/modularui/utils/math/SIPrefix.java +++ b/src/main/java/brachy/modularui/utils/math/SIPrefix.java @@ -11,7 +11,7 @@ public enum SIPrefix { Ronna('R', 27), Yotta('Y', 24), Zetta('Z', 21), - Exa('X', 18), // this should actually be E, but this clashes with euler's number e = 2.71... + Exa('X', 18), // this should actually be E, but this clashes with Euler's number e = 2.71... Peta('P', 15), Tera('T', 12), Giga('G', 9), diff --git a/src/main/java/brachy/modularui/widget/WidgetTree.java b/src/main/java/brachy/modularui/widget/WidgetTree.java index ebc06b9..75a138d 100644 --- a/src/main/java/brachy/modularui/widget/WidgetTree.java +++ b/src/main/java/brachy/modularui/widget/WidgetTree.java @@ -6,8 +6,8 @@ import brachy.modularui.api.widget.IWidget; import brachy.modularui.screen.ModularPanel; import brachy.modularui.screen.viewport.ModularGuiContext; -import brachy.modularui.utils.FormattingUtil; import brachy.modularui.utils.TreeUtil; +import brachy.modularui.utils.math.NumberFormat; import brachy.modularui.value.sync.ModularSyncManager; import brachy.modularui.value.sync.PanelSyncManager; import brachy.modularui.widget.sizer.ResizeNode; @@ -302,8 +302,8 @@ public static void resizeInternal(ResizeNode parent, boolean onOpen) { if (WidgetTree.logResizeTime) { time = Util.getNanos() - time; ModularUI.LOGGER.info("Resized widget tree in {} ns and {} ns for full resize.", - FormattingUtil.formatNumbers(time), - FormattingUtil.formatNumbers(time)); + NumberFormat.formatNanos(time), + NumberFormat.formatNanos(time)); } } diff --git a/src/main/java/brachy/modularui/widgets/AbstractFluidDisplayWidget.java b/src/main/java/brachy/modularui/widgets/AbstractFluidDisplayWidget.java index 4efbb1b..3091a84 100644 --- a/src/main/java/brachy/modularui/widgets/AbstractFluidDisplayWidget.java +++ b/src/main/java/brachy/modularui/widgets/AbstractFluidDisplayWidget.java @@ -9,8 +9,8 @@ import brachy.modularui.screen.viewport.ModularGuiContext; import brachy.modularui.theme.WidgetThemeEntry; import brachy.modularui.utils.Alignment; -import brachy.modularui.utils.FormattingUtil; import brachy.modularui.utils.math.MathUtils; +import brachy.modularui.utils.math.NumberFormat; import brachy.modularui.utils.math.SIPrefix; import brachy.modularui.widget.Widget; import brachy.modularui.widget.sizer.Box; @@ -19,7 +19,6 @@ import lombok.Getter; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; public abstract class AbstractFluidDisplayWidget> extends Widget implements IngredientProvider { @@ -63,12 +62,10 @@ public void draw(ModularGuiContext context, WidgetThemeEntry widgetTheme) { public void drawOverlay(ModularGuiContext context, WidgetThemeEntry widgetTheme) { super.drawOverlay(context, widgetTheme); FluidStack fluid = getFluidStack(); - if (fluid != null && displayAmountText()) { - String s = FormattingUtil.formatNumberReadable2F(getBaseUnitAmount(fluid.getAmount()), false) + - getBaseUnit(); + if (!fluid.isEmpty() && displayAmountText()) { + String s = NumberFormat.format(getBaseUnitAmount(fluid.getAmount()), NumberFormat.AMOUNT_TEXT) + getBaseUnit(); // mc doesn't consider the 1px border in item slots for amount text, but it looks weird when it touches the - // left border, so - // we only apply padding there + // left border, so we only apply padding there GuiDraw.drawScaledAlignedTextInBox(context, s, this.contentPadding.left(), 0, getArea().width - this.contentPadding.left(), getArea().height, Alignment.BottomRight); } @@ -76,7 +73,6 @@ public void drawOverlay(ModularGuiContext context, WidgetThemeEntry widgetThe protected abstract boolean displayAmountText(); - @Nullable protected abstract FluidStack getFluidStack(); /** diff --git a/src/main/java/brachy/modularui/widgets/FluidDisplayWidget.java b/src/main/java/brachy/modularui/widgets/FluidDisplayWidget.java index 6b51226..1c15a44 100644 --- a/src/main/java/brachy/modularui/widgets/FluidDisplayWidget.java +++ b/src/main/java/brachy/modularui/widgets/FluidDisplayWidget.java @@ -37,7 +37,7 @@ protected boolean displayAmountText() { @Override protected @Nullable FluidStack getFluidStack() { - return this.value != null ? this.value.getValue() : null; + return this.value != null ? this.value.getOrDefault(FluidStack.EMPTY) : FluidStack.EMPTY; } public FluidDisplayWidget value(IValue value) { diff --git a/src/main/java/brachy/modularui/widgets/slot/FluidSlot.java b/src/main/java/brachy/modularui/widgets/slot/FluidSlot.java index 44a02c1..68e8e9f 100644 --- a/src/main/java/brachy/modularui/widgets/slot/FluidSlot.java +++ b/src/main/java/brachy/modularui/widgets/slot/FluidSlot.java @@ -227,9 +227,8 @@ public int getCapacity() { return this.alwaysShowFull ? 0 : getFluidTank().getCapacity(); } - @Nullable public FluidStack getFluidStack() { - return this.syncHandler == null ? null : this.syncHandler.getValue(); + return this.syncHandler == null ? FluidStack.EMPTY : this.syncHandler.getOrDefault(FluidStack.EMPTY); } public IFluidTank getFluidTank() { From 10ab102d0bd5ba90f9cf193c6d3896af4ddd4fed Mon Sep 17 00:00:00 2001 From: Gustavo <77560533+gustovafing@users.noreply.github.com> Date: Sun, 19 Jul 2026 20:07:10 +1000 Subject: [PATCH 4/4] mark tooltip dirty when accessed through emi with auto update on (#73) --- .../modularui/integration/emi/recipe/ModularUIEmiRecipe.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/brachy/modularui/integration/emi/recipe/ModularUIEmiRecipe.java b/src/main/java/brachy/modularui/integration/emi/recipe/ModularUIEmiRecipe.java index c8ab6df..702ab7b 100644 --- a/src/main/java/brachy/modularui/integration/emi/recipe/ModularUIEmiRecipe.java +++ b/src/main/java/brachy/modularui/integration/emi/recipe/ModularUIEmiRecipe.java @@ -178,6 +178,7 @@ public List getTooltip(int mouseX, int mouseY) { IWidget hovered = screen.getContext().getTopHovered(); if (hovered instanceof ITooltip tooltip && tooltip.getTooltip() != null) { RichTooltip richTooltip = tooltip.getTooltip(); + if (richTooltip.autoUpdate()) richTooltip.markDirty(); richTooltip.isEmpty(); // causes the tooltip to rebuild if necessary IRichTextBuilder richTextBuilder = richTooltip.getRichText(); if (richTextBuilder instanceof RichText richText) {