Minecraft Transit Railway (Automated trains, planes, and more!)

Minecraft Transit Railway (Automated trains, planes, and more!)

2M Downloads

Improve elevator floor selection – make entire row clickable

lucasfgodoy opened this issue · 3 comments

commented

Suggestion Type

User experience

Suggestion

Hello,
First of all, thank you for this amazing mod.
I have a suggestion to improve accessibility, especially when using a game controller:
Currently, in the elevator menu, only a small button area can be clicked to select a floor. Would it be possible to make the entire row (where the floor name is) clickable instead of just the small button?
This would greatly improve usability for controller users.
Thank you for your time!

Assets

No response

Implementation Details and References

No response

commented

Hello MTR community!
I wanted to share some UI improvements I've made to the elevator selection screen. I used AI to help me implement these changes, focusing only on modifying the LiftSelectionScreen.java file in the src\main\java\org\mtr\mod\screen directory.
Changes implemented:

  1. Made the entire row clickable - Previously you could only click the small "Square" Image button to select a floor, now you can click anywhere on the row
  2. Repositioned the selection button - Moved the "Find" button to appear before the floor number and indicator square
  3. Added hover highlighting - Now when you hover over a floor row, it highlights with a subtle background color for better visibility

Note that this approach may not be the most elegant since we limited ourselves to modifying only one file, but it works well for the current functionality.
Below is the code if anyone wants to review or implement it in their build:

Let me know what you think! I believe these changes make the elevator interface much more user-friendly

Download (backup your stuff first as always):
https://drive.google.com/drive/folders/1sgwEpWwtiqgNSkQez7LgPI0C2PxY9PgR?usp=drive_link

Image

src\main\java\org\mtr\mod\screen directory
LiftSelectionScreen.java

package org.mtr.mod.screen;

import org.mtr.core.data.Lift;
import org.mtr.core.data.LiftDirection;
import org.mtr.core.operation.PressLift;
import org.mtr.libraries.it.unimi.dsi.fastutil.objects.ObjectArrayList;
import org.mtr.libraries.it.unimi.dsi.fastutil.objects.ObjectObjectImmutablePair;
import org.mtr.mapping.holder.*;
import org.mtr.mapping.mapper.GraphicsHolder;
import org.mtr.mapping.mapper.GuiDrawing;
import org.mtr.mapping.mapper.TexturedButtonWidgetExtension;
import org.mtr.mod.Init;
import org.mtr.mod.InitClient;
import org.mtr.mod.client.IDrawing;
import org.mtr.mod.client.MinecraftClientData;
import org.mtr.mod.data.IGui;
import org.mtr.mod.packet.PacketPressLiftButton;
import org.mtr.mod.render.RenderLifts;

public class LiftSelectionScreen extends MTRScreenBase implements IGui {

    private final DashboardList selectionList;
    private final ObjectArrayList<BlockPos> floorLevels = new ObjectArrayList<>();
    private final ObjectArrayList<String> floorDescriptions = new ObjectArrayList<>();
    private final long liftId;
    
    // New button positioned to the left
    private TexturedButtonWidgetExtension leftFindButton;
    // Store mouse position
    private int mouseX, mouseY;
    // Highlight color - Light gray with transparency
    private static final int HIGHLIGHT_COLOR = 0x33AAAAAA;

    public LiftSelectionScreen(long liftId) {
        super();
        this.liftId = liftId;
        final Lift lift = MinecraftClientData.getLift(liftId);
        final ClientWorld clientWorld = MinecraftClient.getInstance().getWorldMapped();
        if (lift != null && clientWorld != null) {
            lift.iterateFloors(floor -> {
                final BlockPos blockPos = Init.positionToBlockPos(floor.getPosition());
                floorLevels.add(blockPos);
                final ObjectObjectImmutablePair<LiftDirection, ObjectObjectImmutablePair<String, String>> liftDetails = RenderLifts.getLiftDetails(new World(clientWorld.data), lift, blockPos);
                floorDescriptions.add(String.format(
                        "%s %s",
                        liftDetails.right().left(),
                        IGui.formatStationName(String.join("|", liftDetails.right().right()))
                ));
            });
        }
        selectionList = new DashboardList(this::onPress, null, null, null, null, null, null, () -> "", text -> {
        });
    }

    @Override
    protected void init2() {
        super.init2();
        selectionList.x = width / 2 - PANEL_WIDTH;
        selectionList.y = SQUARE_SIZE;
        selectionList.width = PANEL_WIDTH * 2;
        selectionList.height = height - SQUARE_SIZE * 2;
        selectionList.init(this::addChild);
        
        // Create our own left-positioned button
        leftFindButton = TexturedButtonWidgetHelper.create(0, 0, SQUARE_SIZE, SQUARE_SIZE, 
                new Identifier("textures/gui/sprites/mtr/icon_find.png"), 
                new Identifier("textures/gui/sprites/mtr/icon_find_highlighted.png"), 
                button -> {
                    int hoverIndex = getHoverIndex();
                    if (hoverIndex >= 0) {
                        onPress(null, hoverIndex);
                    }
                });
        leftFindButton.visible = false;
        addChild(new ClickableWidget(leftFindButton));
    }

    @Override
    public void tick2() {
        final Lift lift = MinecraftClientData.getLift(liftId);
        if (lift == null) {
            onClose2();
        } else {
            selectionList.tick();
            final ObjectArrayList<DashboardListItem> list = new ObjectArrayList<>();
            for (int i = floorLevels.size() - 1; i >= 0; i--) {
                final BlockPos blockPos = floorLevels.get(i);
                list.add(new DashboardListItem(
                        blockPos.asLong(),
                        floorDescriptions.get(i),
                        lift.hasInstruction(lift.getFloorIndex(Init.blockPosToPosition(blockPos))).contains(LiftDirection.NONE) ? 0xFFFF0000 : ARGB_BLACK
                ));
            }
            selectionList.setData(list, false, false, false, false, false, false);
        }
    }

    @Override
    public void render(GraphicsHolder graphicsHolder, int mouseX, int mouseY, float delta) {
        // Update mouse coordinates
        this.mouseX = mouseX;
        this.mouseY = mouseY;
        
        renderBackground(graphicsHolder);
        
        // Draw row highlight before rendering the list
        int hoverIndex = getHoverIndex();
        if (hoverIndex >= 0 && hoverIndex < floorLevels.size() && 
            mouseX >= selectionList.x && mouseX < selectionList.x + selectionList.width && 
            mouseY >= selectionList.y + SQUARE_SIZE + TEXT_FIELD_PADDING && 
            mouseY < selectionList.y + selectionList.height) {
            
            int rowY = selectionList.y + hoverIndex * SQUARE_SIZE + SQUARE_SIZE + TEXT_FIELD_PADDING;
            
            // Draw a translucent rectangle to highlight the row
            GuiDrawing guiDrawing = new GuiDrawing(graphicsHolder);
            guiDrawing.beginDrawingRectangle();
            guiDrawing.drawRectangle(
                selectionList.x - SQUARE_SIZE,  // Extend highlight to cover the left button
                rowY, 
                selectionList.x + selectionList.width,
                rowY + SQUARE_SIZE, 
                HIGHLIGHT_COLOR
            );
            guiDrawing.finishDrawingRectangle();
        }
        
        selectionList.render(graphicsHolder);
        super.render(graphicsHolder, mouseX, mouseY, delta);
    }

    @Override
    public void mouseMoved2(double mouseX, double mouseY) {
        // Update mouse coordinates
        this.mouseX = (int)mouseX;
        this.mouseY = (int)mouseY;
        
        selectionList.mouseMoved(mouseX, mouseY);
        
        // Control visibility of our left button
        leftFindButton.visible = false;
        
        if (mouseX >= selectionList.x && mouseX < selectionList.x + selectionList.width && 
            mouseY >= selectionList.y + SQUARE_SIZE + TEXT_FIELD_PADDING && 
            mouseY < selectionList.y + selectionList.height) {
            
            int hoverIndex = (int)((mouseY - selectionList.y - SQUARE_SIZE - TEXT_FIELD_PADDING) / SQUARE_SIZE);
            if (hoverIndex >= 0 && hoverIndex < floorLevels.size()) {
                // Position the button before the black square indicator
                int renderOffset = selectionList.y + hoverIndex * SQUARE_SIZE + SQUARE_SIZE + TEXT_FIELD_PADDING;
                // Move further left, before the indicator square
                leftFindButton.setX2(selectionList.x - SQUARE_SIZE);
                leftFindButton.setY2(renderOffset);
                leftFindButton.visible = true;
            }
        }
    }

    @Override
    public boolean mouseScrolled2(double mouseX, double mouseY, double amount) {
        selectionList.mouseScrolled(mouseX, mouseY, amount);
        return super.mouseScrolled2(mouseX, mouseY, amount);
    }
    
    @Override
    public boolean mouseClicked2(double mouseX, double mouseY, int button) {
        // Check if the click was in the list area and not on buttons
        if (mouseX >= selectionList.x && mouseX < selectionList.x + selectionList.width && 
            mouseY >= selectionList.y + SQUARE_SIZE + TEXT_FIELD_PADDING && 
            mouseY < selectionList.y + selectionList.height) {
            
            // Calculate which item was clicked
            int clickedIndex = (int)((mouseY - selectionList.y - SQUARE_SIZE - TEXT_FIELD_PADDING) / SQUARE_SIZE);
            
            // Verify the index is valid
            if (clickedIndex >= 0 && clickedIndex < floorLevels.size()) {
                // Simulate button click when clicking anywhere on the row
                selectionList.mouseMoved(mouseX, mouseY); // Update hover index
                onPress(null, clickedIndex); // Call the same handler the button would use
                return true;
            }
        }
        
        return super.mouseClicked2(mouseX, mouseY, button);
    }

    @Override
    public boolean isPauseScreen2() {
        return false;
    }
    
    private int getHoverIndex() {
        // Using stored mouse coordinates
        return (int)((mouseY - selectionList.y - SQUARE_SIZE - TEXT_FIELD_PADDING) / SQUARE_SIZE);
    }

    private void onPress(DashboardListItem dashboardListItem, int index) {
        final PressLift pressLift = new PressLift();
        pressLift.add(Init.blockPosToPosition(floorLevels.get(floorLevels.size() - index - 1)), LiftDirection.NONE);
        InitClient.REGISTRY_CLIENT.sendPacketToServer(new PacketPressLiftButton(pressLift));
        onClose2();
    }
}
commented

Create a pull request

commented

The elevator is a brilliant implementation and I use it all over the place. Any chance that the floor selector could be added to the elevator structure or the door so that a click will call it up rather than the rather clunky Z key press. Also, as I have elevators in loads of tall buildings, could there be some way of renumbering each floor without going to each one separately and using brush to renumber them.