Added scroll with wheel on PC and touch scroll on Android/IOS

This commit is contained in:
2026-04-26 15:47:52 +02:00
parent bc82f5c091
commit 4cfaa43d77
6 changed files with 275 additions and 136 deletions

View File

@@ -35,6 +35,17 @@ void GuiElementContainer::removeChild( GuiElement* element ) {
children.erase(it); children.erase(it);
} }
bool GuiElementContainer::containsPointInChildren(int x, int y) const {
for (std::vector<GuiElement*>::const_iterator it = children.begin(); it != children.end(); ++it) {
GuiElement* child = *it;
if (child == NULL || !child->visible) continue;
if (child->pointInside(x, y)) return true;
const GuiElementContainer* container = dynamic_cast<const GuiElementContainer*>(child);
if (container != NULL && container->containsPointInChildren(x, y)) return true;
}
return false;
}
void GuiElementContainer::tick( Minecraft* minecraft ) { void GuiElementContainer::tick( Minecraft* minecraft ) {
for(std::vector<GuiElement*>::iterator it = children.begin(); it != children.end(); ++it) { for(std::vector<GuiElement*>::iterator it = children.begin(); it != children.end(); ++it) {
(*it)->tick(minecraft); (*it)->tick(minecraft);

View File

@@ -5,16 +5,17 @@
class Tesselator; class Tesselator;
class Minecraft; class Minecraft;
class GuiElementContainer : public GuiElement { class GuiElementContainer : public GuiElement {
public: public:
GuiElementContainer(bool active=false, bool visible=true, int x = 0, int y = 0, int width=24, int height=24); GuiElementContainer(bool active=false, bool visible=true, int x = 0, int y = 0, int width=24, int height=24);
virtual ~GuiElementContainer(); virtual ~GuiElementContainer();
virtual void render(Minecraft* minecraft, int xm, int ym); virtual void render(Minecraft* minecraft, int xm, int ym);
virtual void setupPositions(); virtual void setupPositions();
virtual void addChild(GuiElement* element); virtual void addChild(GuiElement* element);
virtual void removeChild(GuiElement* element); virtual void removeChild(GuiElement* element);
bool containsPointInChildren(int x, int y) const;
virtual void tick( Minecraft* minecraft );
virtual void tick( Minecraft* minecraft );
virtual void mouseClicked( Minecraft* minecraft, int x, int y, int buttonNum ); virtual void mouseClicked( Minecraft* minecraft, int x, int y, int buttonNum );
virtual void mouseReleased( Minecraft* minecraft, int x, int y, int buttonNum ); virtual void mouseReleased( Minecraft* minecraft, int x, int y, int buttonNum );

View File

@@ -1,115 +1,217 @@
#include "OptionsGroup.h" #include "OptionsGroup.h"
#include "../../Minecraft.h" #include "../../Minecraft.h"
#include "ImageButton.h" #include "ImageButton.h"
#include "OptionsItem.h" #include "OptionsItem.h"
#include "Slider.h" #include "Slider.h"
#include "../../../locale/I18n.h" #include "../../../locale/I18n.h"
#include "TextOption.h" #include "TextOption.h"
#include "KeyOption.h" #include "KeyOption.h"
#include <algorithm>
OptionsGroup::OptionsGroup( std::string labelID ) { #include "../Screen.h"
label = I18n::get(labelID); #include "../../../platform/input/Mouse.h"
} #include "../../../util/Mth.h"
void OptionsGroup::setupPositions() { OptionsGroup::OptionsGroup( std::string labelID )
// First we write the header and then we add the items : contentHeight(0),
int curY = y + 18; scrollOffsetY(0.0f),
for(std::vector<GuiElement*>::iterator it = children.begin(); it != children.end(); ++it) { maxScrollOffsetY(0.0f),
(*it)->width = width - 5; trackingScrollGesture(false),
scrollingGesture(false),
(*it)->y = curY; touchDispatched(false),
(*it)->x = x + 10; dragStartX(0),
(*it)->setupPositions(); dragStartY(0),
curY += (*it)->height + 3; lastDragY(0),
} touchStartX(0),
height = curY; touchStartY(0) {
} label = I18n::get(labelID);
}
void OptionsGroup::render( Minecraft* minecraft, int xm, int ym ) {
float padX = 10.0f; void OptionsGroup::setupPositions() {
float padY = 5.0f; const int labelHeight = 18;
const float requestedScroll = scrollOffsetY;
minecraft->font->draw(label, (float)x + padX, (float)y + padY, 0xffffffff, false); const int scrollOffset = (int)requestedScroll;
int curY = y + labelHeight - scrollOffset;
super::render(minecraft, xm, ym); const int contentStartY = y + labelHeight;
}
// First we write the header and then we add the items
OptionsGroup& OptionsGroup::addOptionItem(OptionId optId, Minecraft* minecraft ) { for(std::vector<GuiElement*>::iterator it = children.begin(); it != children.end(); ++it) {
auto option = minecraft->options.getOpt(optId); (*it)->width = width - 5;
if (option == nullptr) return *this; (*it)->y = curY;
(*it)->x = x + 10;
// TODO: do a options key class to check it faster via dynamic_cast (*it)->setupPositions();
if (option->getStringId().find("options.key") != std::string::npos) createKey(optId, minecraft); curY += (*it)->height + 3;
else if (dynamic_cast<OptionBool*>(option)) createToggle(optId, minecraft); }
else if (dynamic_cast<OptionFloat*>(option)) createProgressSlider(optId, minecraft); contentHeight = std::max(0, curY - contentStartY + scrollOffset);
else if (dynamic_cast<OptionInt*>(option)) createStepSlider(optId, minecraft); maxScrollOffsetY = std::max(0, contentHeight - (height - labelHeight));
else if (dynamic_cast<OptionString*>(option)) createTextbox(optId, minecraft); const float clampedScroll = Mth::clamp(requestedScroll, 0.0f, maxScrollOffsetY);
if (clampedScroll != requestedScroll) {
return *this; scrollOffsetY = clampedScroll;
} setupPositions();
}
// TODO: wrap this copypaste shit into templates }
void OptionsGroup::createToggle(OptionId optId, Minecraft* minecraft ) { void OptionsGroup::render( Minecraft* minecraft, int xm, int ym ) {
ImageDef def; float padX = 10.0f;
float padY = 5.0f;
def.setSrc(IntRectangle(160, 206, 39, 20));
def.name = "gui/touchgui.png"; minecraft->font->draw(label, (float)x + padX, (float)y + padY, 0xffffffff, false);
def.width = 39 * 0.7f;
def.height = 20 * 0.7f; super::render(minecraft, xm, ym);
}
OptionButton* element = new OptionButton(optId);
element->setImageDef(def, true); void OptionsGroup::tick(Minecraft* minecraft) {
element->updateImage(&minecraft->options); int xm = Mouse::getX();
int ym = Mouse::getY();
std::string itemLabel = I18n::get(minecraft->options.getOpt(optId)->getStringId()); if (minecraft->screen != NULL) {
minecraft->screen->toGUICoordinate(xm, ym);
OptionsItem* item = new OptionsItem(optId, itemLabel, element); }
addChild(item); bool leftDown = Mouse::isButtonDown(MouseAction::ACTION_LEFT);
setupPositions();
} if (trackingScrollGesture && leftDown) {
int dy = ym - lastDragY;
void OptionsGroup::createProgressSlider(OptionId optId, Minecraft* minecraft ) { int dx = xm - dragStartX;
Slider* element = new SliderFloat(minecraft, optId); if (!scrollingGesture) {
element->width = 100; int totalDx = xm - dragStartX;
element->height = 20; int totalDy = ym - dragStartY;
if (std::abs(totalDx) >= ScrollStartThreshold || std::abs(totalDy) >= ScrollStartThreshold) {
std::string itemLabel = I18n::get(minecraft->options.getOpt(optId)->getStringId()); if (std::abs(totalDy) >= std::abs(totalDx)) {
OptionsItem* item = new OptionsItem(optId, itemLabel, element); scrollingGesture = true;
addChild(item); } else if (!touchDispatched) {
setupPositions(); super::mouseClicked(minecraft, touchStartX, touchStartY, MouseAction::ACTION_LEFT);
} touchDispatched = true;
}
void OptionsGroup::createStepSlider(OptionId optId, Minecraft* minecraft ) { }
Slider* element = new SliderInt(minecraft, optId); }
element->width = 100; if (scrollingGesture && dy != 0) {
element->height = 20; scrollByPixels((float)dy);
std::string itemLabel = I18n::get(minecraft->options.getOpt(optId)->getStringId()); }
OptionsItem* item = new OptionsItem(optId, itemLabel, element); lastDragY = ym;
addChild(item); }
setupPositions(); super::tick(minecraft);
} }
void OptionsGroup::createTextbox(OptionId optId, Minecraft* minecraft) { void OptionsGroup::mouseClicked(Minecraft* minecraft, int x, int y, int buttonNum) {
TextBox* element = new TextOption(minecraft, optId); trackingScrollGesture = false;
element->width = 100; scrollingGesture = false;
element->height = 20; touchDispatched = false;
std::string itemLabel = I18n::get(minecraft->options.getOpt(optId)->getStringId()); if (buttonNum == MouseAction::ACTION_LEFT && pointInside(x, y)) {
OptionsItem* item = new OptionsItem(optId, itemLabel, element); trackingScrollGesture = true;
addChild(item); dragStartX = x;
setupPositions(); dragStartY = y;
} lastDragY = y;
touchStartX = x;
void OptionsGroup::createKey(OptionId optId, Minecraft* minecraft) { touchStartY = y;
KeyOption* element = new KeyOption(minecraft, optId); return;
element->width = 50; }
element->height = 20;
super::mouseClicked(minecraft, x, y, buttonNum);
std::string itemLabel = I18n::get(minecraft->options.getOpt(optId)->getStringId()); }
OptionsItem* item = new OptionsItem(optId, itemLabel, element);
addChild(item); void OptionsGroup::mouseReleased(Minecraft* minecraft, int x, int y, int buttonNum) {
setupPositions(); bool wasScrolling = scrollingGesture;
bool wasTracking = trackingScrollGesture;
trackingScrollGesture = false;
scrollingGesture = false;
if (buttonNum == MouseAction::ACTION_LEFT && wasTracking && !touchDispatched && pointInside(touchStartX, touchStartY)) {
super::mouseClicked(minecraft, touchStartX, touchStartY, buttonNum);
touchDispatched = true;
}
if (!wasScrolling) {
super::mouseReleased(minecraft, x, y, buttonNum);
}
}
void OptionsGroup::scrollByPixels(float deltaY) {
if (deltaY == 0.0f || maxScrollOffsetY <= 0.0f) return;
scrollOffsetY = Mth::clamp(scrollOffsetY - deltaY, 0.0f, maxScrollOffsetY);
setupPositions();
}
bool OptionsGroup::isScrollingGestureActive() const {
return trackingScrollGesture || scrollingGesture;
}
OptionsGroup& OptionsGroup::addOptionItem(OptionId optId, Minecraft* minecraft ) {
auto option = minecraft->options.getOpt(optId);
if (option == nullptr) return *this;
// TODO: do a options key class to check it faster via dynamic_cast
if (option->getStringId().find("options.key") != std::string::npos) createKey(optId, minecraft);
else if (dynamic_cast<OptionBool*>(option)) createToggle(optId, minecraft);
else if (dynamic_cast<OptionFloat*>(option)) createProgressSlider(optId, minecraft);
else if (dynamic_cast<OptionInt*>(option)) createStepSlider(optId, minecraft);
else if (dynamic_cast<OptionString*>(option)) createTextbox(optId, minecraft);
return *this;
}
// TODO: wrap this copypaste shit into templates
void OptionsGroup::createToggle(OptionId optId, Minecraft* minecraft ) {
ImageDef def;
def.setSrc(IntRectangle(160, 206, 39, 20));
def.name = "gui/touchgui.png";
def.width = 39 * 0.7f;
def.height = 20 * 0.7f;
OptionButton* element = new OptionButton(optId);
element->setImageDef(def, true);
element->updateImage(&minecraft->options);
std::string itemLabel = I18n::get(minecraft->options.getOpt(optId)->getStringId());
OptionsItem* item = new OptionsItem(optId, itemLabel, element);
addChild(item);
setupPositions();
}
void OptionsGroup::createProgressSlider(OptionId optId, Minecraft* minecraft ) {
Slider* element = new SliderFloat(minecraft, optId);
element->width = 100;
element->height = 20;
std::string itemLabel = I18n::get(minecraft->options.getOpt(optId)->getStringId());
OptionsItem* item = new OptionsItem(optId, itemLabel, element);
addChild(item);
setupPositions();
}
void OptionsGroup::createStepSlider(OptionId optId, Minecraft* minecraft ) {
Slider* element = new SliderInt(minecraft, optId);
element->width = 100;
element->height = 20;
std::string itemLabel = I18n::get(minecraft->options.getOpt(optId)->getStringId());
OptionsItem* item = new OptionsItem(optId, itemLabel, element);
addChild(item);
setupPositions();
}
void OptionsGroup::createTextbox(OptionId optId, Minecraft* minecraft) {
TextBox* element = new TextOption(minecraft, optId);
element->width = 100;
element->height = 20;
std::string itemLabel = I18n::get(minecraft->options.getOpt(optId)->getStringId());
OptionsItem* item = new OptionsItem(optId, itemLabel, element);
addChild(item);
setupPositions();
}
void OptionsGroup::createKey(OptionId optId, Minecraft* minecraft) {
KeyOption* element = new KeyOption(minecraft, optId);
element->width = 50;
element->height = 20;
std::string itemLabel = I18n::get(minecraft->options.getOpt(optId)->getStringId());
OptionsItem* item = new OptionsItem(optId, itemLabel, element);
addChild(item);
setupPositions();
} }

View File

@@ -11,22 +11,39 @@
class Font; class Font;
class Minecraft; class Minecraft;
class OptionsGroup: public GuiElementContainer { class OptionsGroup: public GuiElementContainer {
typedef GuiElementContainer super; typedef GuiElementContainer super;
public: public:
OptionsGroup(std::string labelID); OptionsGroup(std::string labelID);
virtual void setupPositions(); virtual void setupPositions();
virtual void render(Minecraft* minecraft, int xm, int ym); virtual void render(Minecraft* minecraft, int xm, int ym);
OptionsGroup& addOptionItem(OptionId optId, Minecraft* minecraft); virtual void tick(Minecraft* minecraft);
protected: virtual void mouseClicked(Minecraft* minecraft, int x, int y, int buttonNum);
virtual void mouseReleased(Minecraft* minecraft, int x, int y, int buttonNum);
OptionsGroup& addOptionItem(OptionId optId, Minecraft* minecraft);
void scrollByPixels(float deltaY);
bool isScrollingGestureActive() const;
protected:
void createToggle(OptionId optId, Minecraft* minecraft); void createToggle(OptionId optId, Minecraft* minecraft);
void createProgressSlider(OptionId optId, Minecraft* minecraft); void createProgressSlider(OptionId optId, Minecraft* minecraft);
void createStepSlider(OptionId optId, Minecraft* minecraft); void createStepSlider(OptionId optId, Minecraft* minecraft);
void createTextbox(OptionId optId, Minecraft* minecraft); void createTextbox(OptionId optId, Minecraft* minecraft);
void createKey(OptionId optId, Minecraft* minecraft); void createKey(OptionId optId, Minecraft* minecraft);
std::string label; std::string label;
}; int contentHeight;
float scrollOffsetY;
float maxScrollOffsetY;
bool trackingScrollGesture;
bool scrollingGesture;
bool touchDispatched;
int dragStartX;
int dragStartY;
int lastDragY;
int touchStartX;
int touchStartY;
static const int ScrollStartThreshold = 5;
};
#endif /*NET_MINECRAFT_CLIENT_GUI_COMPONENTS__OptionsGroup_H__*/ #endif /*NET_MINECRAFT_CLIENT_GUI_COMPONENTS__OptionsGroup_H__*/

View File

@@ -121,6 +121,7 @@ void OptionsScreen::setupPositions() {
(*it)->x = categoryButtons[0]->width; (*it)->x = categoryButtons[0]->width;
(*it)->y = bHeader->height; (*it)->y = bHeader->height;
(*it)->width = width - categoryButtons[0]->width; (*it)->width = width - categoryButtons[0]->width;
(*it)->height = height - bHeader->height;
(*it)->setupPositions(); (*it)->setupPositions();
} }
@@ -253,6 +254,12 @@ void OptionsScreen::mouseReleased(int x, int y, int buttonNum) {
super::mouseReleased(x, y, buttonNum); super::mouseReleased(x, y, buttonNum);
} }
void OptionsScreen::mouseWheel(int dx, int dy, int xm, int ym) {
if (currentOptionsGroup != NULL && currentOptionsGroup->pointInside(xm, ym) && dy != 0) {
currentOptionsGroup->scrollByPixels((float)dy * 18.0f);
}
}
void OptionsScreen::keyPressed(int eventKey) { void OptionsScreen::keyPressed(int eventKey) {
if (currentOptionsGroup != NULL) if (currentOptionsGroup != NULL)
currentOptionsGroup->keyPressed(minecraft, eventKey); currentOptionsGroup->keyPressed(minecraft, eventKey);

View File

@@ -27,6 +27,7 @@ public:
virtual void mouseClicked(int x, int y, int buttonNum); virtual void mouseClicked(int x, int y, int buttonNum);
virtual void mouseReleased(int x, int y, int buttonNum); virtual void mouseReleased(int x, int y, int buttonNum);
virtual void mouseWheel(int dx, int dy, int xm, int ym);
virtual void keyPressed(int eventKey); virtual void keyPressed(int eventKey);
virtual void charPressed(char inputChar); virtual void charPressed(char inputChar);