diff --git a/src/client/gui/components/GuiElementContainer.cpp b/src/client/gui/components/GuiElementContainer.cpp index 9cfbb09..4fdaf22 100755 --- a/src/client/gui/components/GuiElementContainer.cpp +++ b/src/client/gui/components/GuiElementContainer.cpp @@ -35,6 +35,17 @@ void GuiElementContainer::removeChild( GuiElement* element ) { children.erase(it); } +bool GuiElementContainer::containsPointInChildren(int x, int y) const { + for (std::vector::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(child); + if (container != NULL && container->containsPointInChildren(x, y)) return true; + } + return false; +} + void GuiElementContainer::tick( Minecraft* minecraft ) { for(std::vector::iterator it = children.begin(); it != children.end(); ++it) { (*it)->tick(minecraft); diff --git a/src/client/gui/components/GuiElementContainer.h b/src/client/gui/components/GuiElementContainer.h index d20f9d3..06df761 100755 --- a/src/client/gui/components/GuiElementContainer.h +++ b/src/client/gui/components/GuiElementContainer.h @@ -5,16 +5,17 @@ class Tesselator; class Minecraft; -class GuiElementContainer : public GuiElement { -public: - GuiElementContainer(bool active=false, bool visible=true, int x = 0, int y = 0, int width=24, int height=24); - virtual ~GuiElementContainer(); - virtual void render(Minecraft* minecraft, int xm, int ym); - virtual void setupPositions(); - virtual void addChild(GuiElement* element); - virtual void removeChild(GuiElement* element); - - virtual void tick( Minecraft* minecraft ); +class GuiElementContainer : public GuiElement { +public: + GuiElementContainer(bool active=false, bool visible=true, int x = 0, int y = 0, int width=24, int height=24); + virtual ~GuiElementContainer(); + virtual void render(Minecraft* minecraft, int xm, int ym); + virtual void setupPositions(); + virtual void addChild(GuiElement* element); + virtual void removeChild(GuiElement* element); + bool containsPointInChildren(int x, int y) const; + + virtual void tick( Minecraft* minecraft ); virtual void mouseClicked( Minecraft* minecraft, int x, int y, int buttonNum ); virtual void mouseReleased( Minecraft* minecraft, int x, int y, int buttonNum ); diff --git a/src/client/gui/components/OptionsGroup.cpp b/src/client/gui/components/OptionsGroup.cpp index 54212d5..3674a3f 100755 --- a/src/client/gui/components/OptionsGroup.cpp +++ b/src/client/gui/components/OptionsGroup.cpp @@ -1,115 +1,217 @@ -#include "OptionsGroup.h" -#include "../../Minecraft.h" -#include "ImageButton.h" -#include "OptionsItem.h" -#include "Slider.h" -#include "../../../locale/I18n.h" -#include "TextOption.h" -#include "KeyOption.h" - -OptionsGroup::OptionsGroup( std::string labelID ) { - label = I18n::get(labelID); -} - -void OptionsGroup::setupPositions() { - // First we write the header and then we add the items - int curY = y + 18; - for(std::vector::iterator it = children.begin(); it != children.end(); ++it) { - (*it)->width = width - 5; - - (*it)->y = curY; - (*it)->x = x + 10; - (*it)->setupPositions(); - curY += (*it)->height + 3; - } - height = curY; -} - -void OptionsGroup::render( Minecraft* minecraft, int xm, int ym ) { - float padX = 10.0f; - float padY = 5.0f; - - minecraft->font->draw(label, (float)x + padX, (float)y + padY, 0xffffffff, false); - - super::render(minecraft, xm, ym); -} - -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(option)) createToggle(optId, minecraft); - else if (dynamic_cast(option)) createProgressSlider(optId, minecraft); - else if (dynamic_cast(option)) createStepSlider(optId, minecraft); - else if (dynamic_cast(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(); +#include "OptionsGroup.h" +#include "../../Minecraft.h" +#include "ImageButton.h" +#include "OptionsItem.h" +#include "Slider.h" +#include "../../../locale/I18n.h" +#include "TextOption.h" +#include "KeyOption.h" +#include +#include "../Screen.h" +#include "../../../platform/input/Mouse.h" +#include "../../../util/Mth.h" + +OptionsGroup::OptionsGroup( std::string labelID ) +: contentHeight(0), + scrollOffsetY(0.0f), + maxScrollOffsetY(0.0f), + trackingScrollGesture(false), + scrollingGesture(false), + touchDispatched(false), + dragStartX(0), + dragStartY(0), + lastDragY(0), + touchStartX(0), + touchStartY(0) { + label = I18n::get(labelID); +} + +void OptionsGroup::setupPositions() { + const int labelHeight = 18; + const float requestedScroll = scrollOffsetY; + const int scrollOffset = (int)requestedScroll; + int curY = y + labelHeight - scrollOffset; + const int contentStartY = y + labelHeight; + + // First we write the header and then we add the items + for(std::vector::iterator it = children.begin(); it != children.end(); ++it) { + (*it)->width = width - 5; + + (*it)->y = curY; + (*it)->x = x + 10; + (*it)->setupPositions(); + curY += (*it)->height + 3; + } + contentHeight = std::max(0, curY - contentStartY + scrollOffset); + maxScrollOffsetY = std::max(0, contentHeight - (height - labelHeight)); + const float clampedScroll = Mth::clamp(requestedScroll, 0.0f, maxScrollOffsetY); + if (clampedScroll != requestedScroll) { + scrollOffsetY = clampedScroll; + setupPositions(); + } +} + +void OptionsGroup::render( Minecraft* minecraft, int xm, int ym ) { + float padX = 10.0f; + float padY = 5.0f; + + minecraft->font->draw(label, (float)x + padX, (float)y + padY, 0xffffffff, false); + + super::render(minecraft, xm, ym); +} + +void OptionsGroup::tick(Minecraft* minecraft) { + int xm = Mouse::getX(); + int ym = Mouse::getY(); + if (minecraft->screen != NULL) { + minecraft->screen->toGUICoordinate(xm, ym); + } + + bool leftDown = Mouse::isButtonDown(MouseAction::ACTION_LEFT); + + if (trackingScrollGesture && leftDown) { + int dy = ym - lastDragY; + int dx = xm - dragStartX; + if (!scrollingGesture) { + int totalDx = xm - dragStartX; + int totalDy = ym - dragStartY; + if (std::abs(totalDx) >= ScrollStartThreshold || std::abs(totalDy) >= ScrollStartThreshold) { + if (std::abs(totalDy) >= std::abs(totalDx)) { + scrollingGesture = true; + } else if (!touchDispatched) { + super::mouseClicked(minecraft, touchStartX, touchStartY, MouseAction::ACTION_LEFT); + touchDispatched = true; + } + } + } + if (scrollingGesture && dy != 0) { + scrollByPixels((float)dy); + } + lastDragY = ym; + } + super::tick(minecraft); +} + +void OptionsGroup::mouseClicked(Minecraft* minecraft, int x, int y, int buttonNum) { + trackingScrollGesture = false; + scrollingGesture = false; + touchDispatched = false; + + if (buttonNum == MouseAction::ACTION_LEFT && pointInside(x, y)) { + trackingScrollGesture = true; + dragStartX = x; + dragStartY = y; + lastDragY = y; + touchStartX = x; + touchStartY = y; + return; + } + + super::mouseClicked(minecraft, x, y, buttonNum); +} + +void OptionsGroup::mouseReleased(Minecraft* minecraft, int x, int y, int buttonNum) { + 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(option)) createToggle(optId, minecraft); + else if (dynamic_cast(option)) createProgressSlider(optId, minecraft); + else if (dynamic_cast(option)) createStepSlider(optId, minecraft); + else if (dynamic_cast(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(); } \ No newline at end of file diff --git a/src/client/gui/components/OptionsGroup.h b/src/client/gui/components/OptionsGroup.h index 24d28bd..ac90e3d 100755 --- a/src/client/gui/components/OptionsGroup.h +++ b/src/client/gui/components/OptionsGroup.h @@ -11,22 +11,39 @@ class Font; class Minecraft; -class OptionsGroup: public GuiElementContainer { - typedef GuiElementContainer super; -public: - OptionsGroup(std::string labelID); - virtual void setupPositions(); - virtual void render(Minecraft* minecraft, int xm, int ym); - OptionsGroup& addOptionItem(OptionId optId, Minecraft* minecraft); -protected: +class OptionsGroup: public GuiElementContainer { + typedef GuiElementContainer super; +public: + OptionsGroup(std::string labelID); + virtual void setupPositions(); + virtual void render(Minecraft* minecraft, int xm, int ym); + virtual void tick(Minecraft* minecraft); + 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 createProgressSlider(OptionId optId, Minecraft* minecraft); void createStepSlider(OptionId optId, Minecraft* minecraft); void createTextbox(OptionId optId, Minecraft* minecraft); - void createKey(OptionId optId, Minecraft* minecraft); - - std::string label; -}; + void createKey(OptionId optId, Minecraft* minecraft); + + 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__*/ diff --git a/src/client/gui/screens/OptionsScreen.cpp b/src/client/gui/screens/OptionsScreen.cpp index 88fbb63..b58f3fe 100755 --- a/src/client/gui/screens/OptionsScreen.cpp +++ b/src/client/gui/screens/OptionsScreen.cpp @@ -121,6 +121,7 @@ void OptionsScreen::setupPositions() { (*it)->x = categoryButtons[0]->width; (*it)->y = bHeader->height; (*it)->width = width - categoryButtons[0]->width; + (*it)->height = height - bHeader->height; (*it)->setupPositions(); } @@ -253,6 +254,12 @@ void OptionsScreen::mouseReleased(int x, int y, int 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) { if (currentOptionsGroup != NULL) currentOptionsGroup->keyPressed(minecraft, eventKey); diff --git a/src/client/gui/screens/OptionsScreen.h b/src/client/gui/screens/OptionsScreen.h index b02c7b8..dd1e73e 100755 --- a/src/client/gui/screens/OptionsScreen.h +++ b/src/client/gui/screens/OptionsScreen.h @@ -27,6 +27,7 @@ public: virtual void mouseClicked(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 charPressed(char inputChar);