
Improve elevator floor selection – make entire row clickable
lucasfgodoy opened this issue · 3 comments
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
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:
- Made the entire row clickable - Previously you could only click the small "Square"
button to select a floor, now you can click anywhere on the row
- Repositioned the selection button - Moved the "Find" button to appear before the floor number and indicator square
- 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
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();
}
}
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.