summaryrefslogtreecommitdiff
path: root/src/gui
diff options
context:
space:
mode:
Diffstat (limited to 'src/gui')
-rw-r--r--src/gui/CMakeLists.txt8
-rw-r--r--src/gui/StyleSpec.h103
-rw-r--r--src/gui/guiAnimatedImage.cpp73
-rw-r--r--src/gui/guiAnimatedImage.h28
-rw-r--r--src/gui/guiBackgroundImage.cpp69
-rw-r--r--src/gui/guiBackgroundImage.h38
-rw-r--r--src/gui/guiBox.cpp38
-rw-r--r--src/gui/guiBox.h34
-rw-r--r--src/gui/guiButton.cpp233
-rw-r--r--src/gui/guiButton.h104
-rw-r--r--src/gui/guiButtonImage.cpp160
-rw-r--r--src/gui/guiButtonImage.h59
-rw-r--r--src/gui/guiButtonItemImage.cpp57
-rw-r--r--src/gui/guiButtonItemImage.h45
-rw-r--r--src/gui/guiConfirmRegistration.cpp18
-rw-r--r--src/gui/guiEditBoxWithScrollbar.cpp11
-rw-r--r--src/gui/guiFormSpecMenu.cpp1517
-rw-r--r--src/gui/guiFormSpecMenu.h270
-rw-r--r--src/gui/guiHyperText.cpp1158
-rw-r--r--src/gui/guiHyperText.h229
-rw-r--r--src/gui/guiInventoryList.cpp217
-rw-r--r--src/gui/guiInventoryList.h130
-rw-r--r--src/gui/guiItemImage.cpp52
-rw-r--r--src/gui/guiItemImage.h46
-rw-r--r--src/gui/guiKeyChangeMenu.cpp103
-rw-r--r--src/gui/guiKeyChangeMenu.h3
-rw-r--r--src/gui/guiPasswordChange.cpp5
-rw-r--r--src/gui/guiScrollBar.cpp27
-rw-r--r--src/gui/guiScrollBar.h9
-rw-r--r--src/gui/guiVolumeChange.cpp4
-rw-r--r--src/gui/modalMenu.cpp2
-rw-r--r--src/gui/touchscreengui.h4
32 files changed, 3876 insertions, 978 deletions
diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt
index 2307856a4..110a00595 100644
--- a/src/gui/CMakeLists.txt
+++ b/src/gui/CMakeLists.txt
@@ -1,16 +1,24 @@
set(gui_SRCS
+ ${CMAKE_CURRENT_SOURCE_DIR}/guiAnimatedImage.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/guiBackgroundImage.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/guiBox.cpp
${CMAKE_CURRENT_SOURCE_DIR}/guiButton.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/guiButtonImage.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/guiButtonItemImage.cpp
${CMAKE_CURRENT_SOURCE_DIR}/guiChatConsole.cpp
${CMAKE_CURRENT_SOURCE_DIR}/guiConfirmRegistration.cpp
${CMAKE_CURRENT_SOURCE_DIR}/guiEditBoxWithScrollbar.cpp
${CMAKE_CURRENT_SOURCE_DIR}/guiEngine.cpp
${CMAKE_CURRENT_SOURCE_DIR}/guiFormSpecMenu.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/guiInventoryList.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/guiItemImage.cpp
${CMAKE_CURRENT_SOURCE_DIR}/guiKeyChangeMenu.cpp
${CMAKE_CURRENT_SOURCE_DIR}/guiPasswordChange.cpp
${CMAKE_CURRENT_SOURCE_DIR}/guiPathSelectMenu.cpp
${CMAKE_CURRENT_SOURCE_DIR}/guiScrollBar.cpp
${CMAKE_CURRENT_SOURCE_DIR}/guiSkin.cpp
${CMAKE_CURRENT_SOURCE_DIR}/guiTable.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/guiHyperText.cpp
${CMAKE_CURRENT_SOURCE_DIR}/guiVolumeChange.cpp
${CMAKE_CURRENT_SOURCE_DIR}/intlGUIEditBox.cpp
${CMAKE_CURRENT_SOURCE_DIR}/modalMenu.cpp
diff --git a/src/gui/StyleSpec.h b/src/gui/StyleSpec.h
index 29aae0836..999c1d237 100644
--- a/src/gui/StyleSpec.h
+++ b/src/gui/StyleSpec.h
@@ -17,7 +17,9 @@ with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "client/tile.h" // ITextureSource
#include "irrlichttypes_extrabloated.h"
+#include "util/string.h"
#include <array>
#pragma once
@@ -29,17 +31,24 @@ public:
{
TEXTCOLOR,
BGCOLOR,
+ BGCOLOR_HOVERED,
+ BGCOLOR_PRESSED,
NOCLIP,
BORDER,
BGIMG,
+ BGIMG_HOVERED,
+ BGIMG_MIDDLE,
BGIMG_PRESSED,
+ FGIMG,
+ FGIMG_HOVERED,
+ FGIMG_PRESSED,
ALPHA,
NUM_PROPERTIES,
NONE
};
private:
- std::array<bool, NUM_PROPERTIES> property_set;
+ std::array<bool, NUM_PROPERTIES> property_set{};
std::array<std::string, NUM_PROPERTIES> properties;
public:
@@ -49,14 +58,28 @@ public:
return TEXTCOLOR;
} else if (name == "bgcolor") {
return BGCOLOR;
+ } else if (name == "bgcolor_hovered") {
+ return BGCOLOR_HOVERED;
+ } else if (name == "bgcolor_pressed") {
+ return BGCOLOR_PRESSED;
} else if (name == "noclip") {
return NOCLIP;
} else if (name == "border") {
return BORDER;
} else if (name == "bgimg") {
return BGIMG;
+ } else if (name == "bgimg_hovered") {
+ return BGIMG_HOVERED;
+ } else if (name == "bgimg_middle") {
+ return BGIMG_MIDDLE;
} else if (name == "bgimg_pressed") {
return BGIMG_PRESSED;
+ } else if (name == "fgimg") {
+ return FGIMG;
+ } else if (name == "fgimg_hovered") {
+ return FGIMG_HOVERED;
+ } else if (name == "fgimg_pressed") {
+ return FGIMG_PRESSED;
} else if (name == "alpha") {
return ALPHA;
} else {
@@ -97,6 +120,52 @@ public:
return color;
}
+ irr::core::rect<s32> getRect(Property prop, irr::core::rect<s32> def) const
+ {
+ const auto &val = properties[prop];
+ if (val.empty())
+ return def;
+
+ irr::core::rect<s32> rect;
+ if (!parseRect(val, &rect))
+ return def;
+
+ return rect;
+ }
+
+ irr::core::rect<s32> getRect(Property prop) const
+ {
+ const auto &val = properties[prop];
+ FATAL_ERROR_IF(val.empty(), "Unexpected missing property");
+
+ irr::core::rect<s32> rect;
+ parseRect(val, &rect);
+ return rect;
+ }
+
+ video::ITexture *getTexture(Property prop, ISimpleTextureSource *tsrc,
+ video::ITexture *def) const
+ {
+ const auto &val = properties[prop];
+ if (val.empty()) {
+ return def;
+ }
+
+ video::ITexture *texture = tsrc->getTexture(val);
+
+ return texture;
+ }
+
+ video::ITexture *getTexture(Property prop, ISimpleTextureSource *tsrc) const
+ {
+ const auto &val = properties[prop];
+ FATAL_ERROR_IF(val.empty(), "Unexpected missing property");
+
+ video::ITexture *texture = tsrc->getTexture(val);
+
+ return texture;
+ }
+
bool getBool(Property prop, bool def) const
{
const auto &val = properties[prop];
@@ -132,4 +201,36 @@ public:
newspec |= other;
return newspec;
}
+
+private:
+ bool parseRect(const std::string &value, irr::core::rect<s32> *parsed_rect) const
+ {
+ irr::core::rect<s32> rect;
+ std::vector<std::string> v_rect = split(value, ',');
+
+ if (v_rect.size() == 1) {
+ s32 x = stoi(v_rect[0]);
+ rect.UpperLeftCorner = irr::core::vector2di(x, x);
+ rect.LowerRightCorner = irr::core::vector2di(-x, -x);
+ } else if (v_rect.size() == 2) {
+ s32 x = stoi(v_rect[0]);
+ s32 y = stoi(v_rect[1]);
+ rect.UpperLeftCorner = irr::core::vector2di(x, y);
+ rect.LowerRightCorner = irr::core::vector2di(-x, -y);
+ // `-x` is interpreted as `w - x`
+ } else if (v_rect.size() == 4) {
+ rect.UpperLeftCorner = irr::core::vector2di(
+ stoi(v_rect[0]), stoi(v_rect[1]));
+ rect.LowerRightCorner = irr::core::vector2di(
+ stoi(v_rect[2]), stoi(v_rect[3]));
+ } else {
+ warningstream << "Invalid rectangle string format: \"" << value
+ << "\"" << std::endl;
+ return false;
+ }
+
+ *parsed_rect = rect;
+
+ return true;
+ }
};
diff --git a/src/gui/guiAnimatedImage.cpp b/src/gui/guiAnimatedImage.cpp
new file mode 100644
index 000000000..b1447c45f
--- /dev/null
+++ b/src/gui/guiAnimatedImage.cpp
@@ -0,0 +1,73 @@
+#include "guiAnimatedImage.h"
+
+#include "client/guiscalingfilter.h"
+#include "client/tile.h" // ITextureSource
+#include "log.h"
+#include "porting.h"
+#include "util/string.h"
+#include <string>
+#include <vector>
+
+GUIAnimatedImage::GUIAnimatedImage(gui::IGUIEnvironment *env, gui::IGUIElement *parent,
+ s32 id, const core::rect<s32> &rectangle, const std::string &texture_name,
+ s32 frame_count, s32 frame_duration, ISimpleTextureSource *tsrc) :
+ gui::IGUIElement(gui::EGUIET_ELEMENT, env, parent, id, rectangle), m_tsrc(tsrc)
+{
+ m_texture = m_tsrc->getTexture(texture_name);
+
+ m_frame_count = std::max(frame_count, 1);
+ m_frame_duration = std::max(frame_duration, 0);
+
+ if (m_texture != nullptr) {
+ core::dimension2d<u32> size = m_texture->getOriginalSize();
+ if (size.Height < (u64)m_frame_count)
+ m_frame_count = size.Height;
+ } else {
+ // No need to step an animation if we have nothing to draw
+ m_frame_count = 1;
+ }
+}
+
+void GUIAnimatedImage::draw()
+{
+ // Render the current frame
+ if (m_texture != nullptr) {
+ video::IVideoDriver *driver = Environment->getVideoDriver();
+
+ const video::SColor color(255, 255, 255, 255);
+ const video::SColor colors[] = {color, color, color, color};
+
+ core::dimension2d<u32> size = m_texture->getOriginalSize();
+ size.Height /= m_frame_count;
+
+ draw2DImageFilterScaled(driver, m_texture, AbsoluteRect,
+ core::rect<s32>(core::position2d<s32>(0, size.Height * m_frame_idx), size),
+ NoClip ? nullptr : &AbsoluteClippingRect, colors, true);
+ }
+
+ // Step the animation
+ if (m_frame_count > 1 && m_frame_duration > 0) {
+ // Determine the delta time to step
+ u64 new_global_time = porting::getTimeMs();
+ if (m_global_time > 0)
+ m_frame_time += new_global_time - m_global_time;
+
+ m_global_time = new_global_time;
+
+ // Advance by the number of elapsed frames, looping if necessary
+ m_frame_idx += u32(m_frame_time / m_frame_duration);
+ m_frame_idx %= m_frame_count;
+
+ // If 1 or more frames have elapsed, reset the frame time counter with
+ // the remainder
+ m_frame_time %= m_frame_duration;
+ }
+}
+
+
+void GUIAnimatedImage::setFrameIndex(s32 frame)
+{
+ s32 idx = std::max(frame, 0);
+ if (idx > 0 && idx < m_frame_count)
+ m_frame_idx = idx;
+}
diff --git a/src/gui/guiAnimatedImage.h b/src/gui/guiAnimatedImage.h
new file mode 100644
index 000000000..f8e6a506e
--- /dev/null
+++ b/src/gui/guiAnimatedImage.h
@@ -0,0 +1,28 @@
+#pragma once
+
+#include "irrlichttypes_extrabloated.h"
+#include <string>
+
+class ISimpleTextureSource;
+
+class GUIAnimatedImage : public gui::IGUIElement {
+public:
+ GUIAnimatedImage(gui::IGUIEnvironment *env, gui::IGUIElement *parent,
+ s32 id, const core::rect<s32> &rectangle, const std::string &texture_name,
+ s32 frame_count, s32 frame_duration, ISimpleTextureSource *tsrc);
+
+ virtual void draw() override;
+
+ void setFrameIndex(s32 frame);
+ s32 getFrameIndex() const { return m_frame_idx; };
+
+private:
+ ISimpleTextureSource *m_tsrc;
+
+ video::ITexture *m_texture = nullptr;
+ u64 m_global_time = 0;
+ s32 m_frame_idx = 0;
+ s32 m_frame_count = 1;
+ u64 m_frame_duration = 1;
+ u64 m_frame_time = 0;
+};
diff --git a/src/gui/guiBackgroundImage.cpp b/src/gui/guiBackgroundImage.cpp
new file mode 100644
index 000000000..21c1e88cf
--- /dev/null
+++ b/src/gui/guiBackgroundImage.cpp
@@ -0,0 +1,69 @@
+/*
+Part of Minetest
+Copyright (C) 2013 RealBadAngel, Maciej Kasatkin <mk@realbadangel.pl>
+
+Permission to use, copy, modify, and distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+*/
+
+#include "guiBackgroundImage.h"
+#include "client/guiscalingfilter.h"
+#include "log.h"
+
+GUIBackgroundImage::GUIBackgroundImage(gui::IGUIEnvironment *env,
+ gui::IGUIElement *parent, s32 id, const core::rect<s32> &rectangle,
+ const std::string &name, const core::rect<s32> &middle,
+ ISimpleTextureSource *tsrc, bool autoclip) :
+ gui::IGUIElement(gui::EGUIET_ELEMENT, env, parent, id, rectangle),
+ m_name(name), m_middle(middle), m_tsrc(tsrc), m_autoclip(autoclip)
+{
+}
+
+void GUIBackgroundImage::draw()
+{
+ if (!IsVisible)
+ return;
+
+ video::ITexture *texture = m_tsrc->getTexture(m_name);
+
+ if (!texture) {
+ errorstream << "GUIBackgroundImage::draw() Unable to load texture:"
+ << std::endl;
+ errorstream << "\t" << m_name << std::endl;
+ return;
+ }
+
+ core::rect<s32> rect = AbsoluteRect;
+ if (m_autoclip)
+ rect.LowerRightCorner += Parent->getAbsolutePosition().getSize();
+
+ video::IVideoDriver *driver = Environment->getVideoDriver();
+
+ if (m_middle.getArea() == 0) {
+ const video::SColor color(255, 255, 255, 255);
+ const video::SColor colors[] = {color, color, color, color};
+ draw2DImageFilterScaled(driver, texture, rect,
+ core::rect<s32>(core::position2d<s32>(0, 0),
+ core::dimension2di(texture->getOriginalSize())),
+ nullptr, colors, true);
+ } else {
+ core::rect<s32> middle = m_middle;
+ // `-x` is interpreted as `w - x`
+ if (middle.LowerRightCorner.X < 0)
+ middle.LowerRightCorner.X += texture->getOriginalSize().Width;
+ if (middle.LowerRightCorner.Y < 0)
+ middle.LowerRightCorner.Y += texture->getOriginalSize().Height;
+ draw2DImage9Slice(driver, texture, rect, middle);
+ }
+
+ IGUIElement::draw();
+}
diff --git a/src/gui/guiBackgroundImage.h b/src/gui/guiBackgroundImage.h
new file mode 100644
index 000000000..31fbfd09c
--- /dev/null
+++ b/src/gui/guiBackgroundImage.h
@@ -0,0 +1,38 @@
+/*
+Part of Minetest
+Copyright (C) 2013 RealBadAngel, Maciej Kasatkin <mk@realbadangel.pl>
+
+Permission to use, copy, modify, and distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+*/
+
+#pragma once
+
+#include "irrlichttypes_extrabloated.h"
+#include "util/string.h"
+#include "client/tile.h" // ITextureSource
+
+class GUIBackgroundImage : public gui::IGUIElement
+{
+public:
+ GUIBackgroundImage(gui::IGUIEnvironment *env, gui::IGUIElement *parent, s32 id,
+ const core::rect<s32> &rectangle, const std::string &name,
+ const core::rect<s32> &middle, ISimpleTextureSource *tsrc, bool autoclip);
+
+ virtual void draw() override;
+
+private:
+ std::string m_name;
+ core::rect<s32> m_middle;
+ ISimpleTextureSource *m_tsrc;
+ bool m_autoclip;
+};
diff --git a/src/gui/guiBox.cpp b/src/gui/guiBox.cpp
new file mode 100644
index 000000000..7f329cc32
--- /dev/null
+++ b/src/gui/guiBox.cpp
@@ -0,0 +1,38 @@
+/*
+Minetest
+Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "guiBox.h"
+
+GUIBox::GUIBox(gui::IGUIEnvironment *env, gui::IGUIElement *parent, s32 id,
+ const core::rect<s32> &rectangle, const video::SColor &color) :
+ gui::IGUIElement(gui::EGUIET_ELEMENT, env, parent, id, rectangle),
+ m_color(color)
+{
+}
+
+void GUIBox::draw()
+{
+ if (!IsVisible)
+ return;
+
+ Environment->getVideoDriver()->draw2DRectangle(m_color, AbsoluteRect,
+ &AbsoluteClippingRect);
+
+ IGUIElement::draw();
+}
diff --git a/src/gui/guiBox.h b/src/gui/guiBox.h
new file mode 100644
index 000000000..5306fdf65
--- /dev/null
+++ b/src/gui/guiBox.h
@@ -0,0 +1,34 @@
+/*
+Minetest
+Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#pragma once
+
+#include "irrlichttypes_extrabloated.h"
+
+class GUIBox : public gui::IGUIElement
+{
+public:
+ GUIBox(gui::IGUIEnvironment *env, gui::IGUIElement *parent, s32 id,
+ const core::rect<s32> &rectangle, const video::SColor &color);
+
+ virtual void draw() override;
+
+private:
+ video::SColor m_color;
+};
diff --git a/src/gui/guiButton.cpp b/src/gui/guiButton.cpp
index 60d330f4a..4c16ee237 100644
--- a/src/gui/guiButton.cpp
+++ b/src/gui/guiButton.cpp
@@ -5,15 +5,25 @@
#include "guiButton.h"
+#include "client/guiscalingfilter.h"
+#include "client/tile.h"
#include "IGUISkin.h"
#include "IGUIEnvironment.h"
#include "IVideoDriver.h"
#include "IGUIFont.h"
+#include "irrlicht_changes/static_text.h"
#include "porting.h"
+#include "StyleSpec.h"
using namespace irr;
using namespace gui;
+// Multiply with a color to get the default corresponding hovered color
+#define COLOR_HOVERED_MOD 1.25f
+
+// Multiply with a color to get the default corresponding pressed color
+#define COLOR_PRESSED_MOD 0.85f
+
//! constructor
GUIButton::GUIButton(IGUIEnvironment* environment, IGUIElement* parent,
s32 id, core::rect<s32> rectangle, bool noclip)
@@ -34,7 +44,17 @@ GUIButton::GUIButton(IGUIEnvironment* environment, IGUIElement* parent,
// PATCH
for (size_t i = 0; i < 4; i++) {
Colors[i] = Environment->getSkin()->getColor((EGUI_DEFAULT_COLOR)i);
+ HoveredColors[i] = irr::video::SColor(Colors[i].getAlpha(),
+ core::clamp<u32>(Colors[i].getRed() * COLOR_HOVERED_MOD, 0, 255),
+ core::clamp<u32>(Colors[i].getGreen() * COLOR_HOVERED_MOD, 0, 255),
+ core::clamp<u32>(Colors[i].getBlue() * COLOR_HOVERED_MOD, 0, 255));
+ PressedColors[i] = irr::video::SColor(Colors[i].getAlpha(),
+ core::clamp<u32>(Colors[i].getRed() * COLOR_PRESSED_MOD, 0, 255),
+ core::clamp<u32>(Colors[i].getGreen() * COLOR_PRESSED_MOD, 0, 255),
+ core::clamp<u32>(Colors[i].getBlue() * COLOR_PRESSED_MOD, 0, 255));
}
+ StaticText = gui::StaticText::add(Environment, Text.c_str(), core::rect<s32>(0,0,rectangle.getWidth(),rectangle.getHeight()), false, false, this, id);
+ StaticText->setTextAlignment(EGUIA_CENTER, EGUIA_CENTER);
// END PATCH
}
@@ -182,8 +202,12 @@ bool GUIButton::OnEvent(const SEvent& event)
case EET_MOUSE_INPUT_EVENT:
if (event.MouseInput.Event == EMIE_LMOUSE_PRESSED_DOWN)
{
- if (!IsPushButton)
+ // Sometimes formspec elements can receive mouse events when the
+ // mouse is outside of the formspec. Thus, we test the position here.
+ if ( !IsPushButton && AbsoluteClippingRect.isPointInside(
+ core::position2d<s32>(event.MouseInput.X, event.MouseInput.Y ))) {
setPressed(true);
+ }
return true;
}
@@ -247,13 +271,15 @@ void GUIButton::draw()
if (!Pressed)
{
// PATCH
- skin->drawColored3DButtonPaneStandard(this, AbsoluteRect, &AbsoluteClippingRect, Colors);
+ skin->drawColored3DButtonPaneStandard(this, AbsoluteRect, &AbsoluteClippingRect,
+ isHovered() ? HoveredColors : Colors);
// END PATCH
}
else
{
// PATCH
- skin->drawColored3DButtonPanePressed(this, AbsoluteRect, &AbsoluteClippingRect, Colors);
+ skin->drawColored3DButtonPanePressed(this,
+ AbsoluteRect, &AbsoluteClippingRect, PressedColors);
// END PATCH
}
}
@@ -281,10 +307,25 @@ void GUIButton::draw()
}
}
- driver->draw2DImage(ButtonImages[(u32)imageState].Texture,
- ScaleImage? AbsoluteRect : core::rect<s32>(pos, sourceRect.getSize()),
- sourceRect, &AbsoluteClippingRect,
- 0, UseAlphaChannel);
+ // PATCH
+ video::ITexture* texture = ButtonImages[(u32)imageState].Texture;
+ if (BgMiddle.getArea() == 0) {
+ driver->draw2DImage(texture,
+ ScaleImage? AbsoluteRect : core::rect<s32>(pos, sourceRect.getSize()),
+ sourceRect, &AbsoluteClippingRect,
+ 0, UseAlphaChannel);
+ } else {
+ core::rect<s32> middle = BgMiddle;
+ // `-x` is interpreted as `w - x`
+ if (middle.LowerRightCorner.X < 0)
+ middle.LowerRightCorner.X += texture->getOriginalSize().Width;
+ if (middle.LowerRightCorner.Y < 0)
+ middle.LowerRightCorner.Y += texture->getOriginalSize().Height;
+ draw2DImage9Slice(driver, texture,
+ ScaleImage ? AbsoluteRect : core::rect<s32>(pos, sourceRect.getSize()),
+ middle, &AbsoluteClippingRect);
+ }
+ // END PATCH
}
if (SpriteBank)
@@ -302,7 +343,7 @@ void GUIButton::draw()
drawSprite(state, FocusTime, pos);
// mouse over / off animation
- state = Environment->getHovered() == this ? EGBS_BUTTON_MOUSE_OVER : EGBS_BUTTON_MOUSE_OFF;
+ state = isHovered() ? EGBS_BUTTON_MOUSE_OVER : EGBS_BUTTON_MOUSE_OFF;
drawSprite(state, HoverTime, pos);
}
else
@@ -312,23 +353,6 @@ void GUIButton::draw()
}
}
- if (Text.size())
- {
- IGUIFont* font = getActiveFont();
-
- core::rect<s32> rect = AbsoluteRect;
- if (Pressed)
- {
- rect.UpperLeftCorner.X += skin->getSize(EGDS_BUTTON_PRESSED_TEXT_OFFSET_X);
- rect.UpperLeftCorner.Y += skin->getSize(EGDS_BUTTON_PRESSED_TEXT_OFFSET_Y);
- }
-
- if (font)
- font->draw(Text.c_str(), rect,
- OverrideColorEnabled ? OverrideColor : skin->getColor(isEnabled() ? EGDC_BUTTON_TEXT : EGDC_GRAY_TEXT),
- true, true, &AbsoluteClippingRect);
- }
-
IGUIElement::draw();
}
@@ -356,10 +380,17 @@ void GUIButton::drawSprite(EGUI_BUTTON_STATE state, u32 startTime, const core::p
EGUI_BUTTON_IMAGE_STATE GUIButton::getImageState(bool pressed) const
{
+ // PATCH
+ return getImageState(pressed, ButtonImages);
+ // END PATCH
+}
+
+EGUI_BUTTON_IMAGE_STATE GUIButton::getImageState(bool pressed, const ButtonImage* images) const
+{
// figure state we should have
EGUI_BUTTON_IMAGE_STATE state = EGBIS_IMAGE_DISABLED;
bool focused = Environment->hasFocus((IGUIElement*)this);
- bool mouseOver = static_cast<const IGUIElement*>(Environment->getHovered()) == this; // (static cast for Borland)
+ bool mouseOver = isHovered();
if (isEnabled())
{
if ( pressed )
@@ -387,12 +418,13 @@ EGUI_BUTTON_IMAGE_STATE GUIButton::getImageState(bool pressed) const
}
// find a compatible state that has images
- while ( state != EGBIS_IMAGE_UP && !ButtonImages[(u32)state].Texture )
+ while ( state != EGBIS_IMAGE_UP && !images[(u32)state].Texture )
{
+ // PATCH
switch ( state )
{
case EGBIS_IMAGE_UP_FOCUSED:
- state = EGBIS_IMAGE_UP_MOUSEOVER;
+ state = EGBIS_IMAGE_UP;
break;
case EGBIS_IMAGE_UP_FOCUSED_MOUSEOVER:
state = EGBIS_IMAGE_UP_FOCUSED;
@@ -401,7 +433,7 @@ EGUI_BUTTON_IMAGE_STATE GUIButton::getImageState(bool pressed) const
state = EGBIS_IMAGE_DOWN;
break;
case EGBIS_IMAGE_DOWN_FOCUSED:
- state = EGBIS_IMAGE_DOWN_MOUSEOVER;
+ state = EGBIS_IMAGE_DOWN;
break;
case EGBIS_IMAGE_DOWN_FOCUSED_MOUSEOVER:
state = EGBIS_IMAGE_DOWN_FOCUSED;
@@ -415,6 +447,7 @@ EGUI_BUTTON_IMAGE_STATE GUIButton::getImageState(bool pressed) const
default:
state = EGBIS_IMAGE_UP;
}
+ // END PATCH
}
return state;
@@ -433,6 +466,8 @@ void GUIButton::setOverrideFont(IGUIFont* font)
if (OverrideFont)
OverrideFont->grab();
+
+ StaticText->setOverrideFont(font);
}
//! Gets the override font (if any)
@@ -457,6 +492,8 @@ void GUIButton::setOverrideColor(video::SColor color)
{
OverrideColor = color;
OverrideColorEnabled = true;
+
+ StaticText->setOverrideColor(color);
}
video::SColor GUIButton::getOverrideColor() const
@@ -490,6 +527,48 @@ void GUIButton::setImage(EGUI_BUTTON_IMAGE_STATE state, video::ITexture* image,
ButtonImages[stateIdx].SourceRect = sourceRect;
}
+// PATCH
+void GUIButton::setImage(video::ITexture* image)
+{
+ setImage(gui::EGBIS_IMAGE_UP, image);
+}
+
+void GUIButton::setImage(video::ITexture* image, const core::rect<s32>& pos)
+{
+ setImage(gui::EGBIS_IMAGE_UP, image, pos);
+}
+
+void GUIButton::setPressedImage(video::ITexture* image)
+{
+ setImage(gui::EGBIS_IMAGE_DOWN, image);
+}
+
+void GUIButton::setPressedImage(video::ITexture* image, const core::rect<s32>& pos)
+{
+ setImage(gui::EGBIS_IMAGE_DOWN, image, pos);
+}
+
+void GUIButton::setHoveredImage(video::ITexture* image)
+{
+ setImage(gui::EGBIS_IMAGE_UP_MOUSEOVER, image);
+ setImage(gui::EGBIS_IMAGE_UP_FOCUSED_MOUSEOVER, image);
+}
+
+void GUIButton::setHoveredImage(video::ITexture* image, const core::rect<s32>& pos)
+{
+ setImage(gui::EGBIS_IMAGE_UP_MOUSEOVER, image, pos);
+ setImage(gui::EGBIS_IMAGE_UP_FOCUSED_MOUSEOVER, image, pos);
+}
+
+//! Sets the text displayed by the button
+void GUIButton::setText(const wchar_t* text)
+{
+ StaticText->setText(text);
+
+ IGUIButton::setText(text);
+}
+// END PATCH
+
//! Sets if the button should behave like a push button. Which means it
//! can be in two states: Normal or Pressed. With a click on the button,
//! the user can change the state of the button.
@@ -505,6 +584,14 @@ bool GUIButton::isPressed() const
return Pressed;
}
+// PATCH
+//! Returns if this element (or one of its direct children) is hovered
+bool GUIButton::isHovered() const
+{
+ IGUIElement *hovered = Environment->getHovered();
+ return hovered == this || (hovered != nullptr && hovered->getParent() == this);
+}
+// END PATCH
//! Sets the pressed state of the button if this is a pushbutton
void GUIButton::setPressed(bool pressed)
@@ -513,6 +600,24 @@ void GUIButton::setPressed(bool pressed)
{
ClickTime = porting::getTimeMs();
Pressed = pressed;
+
+ GUISkin* skin = dynamic_cast<GUISkin*>(Environment->getSkin());
+
+ for(IGUIElement *child : getChildren())
+ {
+ core::rect<s32> originalRect = child->getRelativePosition();
+ if (Pressed) {
+ child->setRelativePosition(originalRect +
+ core::dimension2d<s32>(
+ skin->getSize(irr::gui::EGDS_BUTTON_PRESSED_IMAGE_OFFSET_X),
+ skin->getSize(irr::gui::EGDS_BUTTON_PRESSED_IMAGE_OFFSET_Y)));
+ } else {
+ child->setRelativePosition(originalRect -
+ core::dimension2d<s32>(
+ skin->getSize(irr::gui::EGDS_BUTTON_PRESSED_IMAGE_OFFSET_X),
+ skin->getSize(irr::gui::EGDS_BUTTON_PRESSED_IMAGE_OFFSET_Y)));
+ }
+ }
}
}
@@ -644,6 +749,76 @@ void GUIButton::setColor(video::SColor color)
for (size_t i = 0; i < 4; i++) {
video::SColor base = Environment->getSkin()->getColor((gui::EGUI_DEFAULT_COLOR)i);
Colors[i] = base.getInterpolated(color, d);
+ HoveredColors[i] = irr::video::SColor(Colors[i].getAlpha(),
+ core::clamp<u32>(Colors[i].getRed() * COLOR_HOVERED_MOD, 0, 255),
+ core::clamp<u32>(Colors[i].getGreen() * COLOR_HOVERED_MOD, 0, 255),
+ core::clamp<u32>(Colors[i].getBlue() * COLOR_HOVERED_MOD, 0, 255));
+ PressedColors[i] = irr::video::SColor(Colors[i].getAlpha(),
+ core::clamp<u32>(Colors[i].getRed() * COLOR_PRESSED_MOD, 0, 255),
+ core::clamp<u32>(Colors[i].getGreen() * COLOR_PRESSED_MOD, 0, 255),
+ core::clamp<u32>(Colors[i].getBlue() * COLOR_PRESSED_MOD, 0, 255));
+ }
+}
+void GUIButton::setHoveredColor(video::SColor color)
+{
+ float d = 0.65f;
+ for (size_t i = 0; i < 4; i++) {
+ video::SColor base = Environment->getSkin()->getColor((gui::EGUI_DEFAULT_COLOR)i);
+ HoveredColors[i] = base.getInterpolated(color, d);
+ }
+}
+void GUIButton::setPressedColor(video::SColor color)
+{
+ float d = 0.65f;
+ for (size_t i = 0; i < 4; i++) {
+ video::SColor base = Environment->getSkin()->getColor((gui::EGUI_DEFAULT_COLOR)i);
+ PressedColors[i] = base.getInterpolated(color, d);
+ }
+}
+
+//! Set element properties from a StyleSpec
+void GUIButton::setFromStyle(const StyleSpec& style, ISimpleTextureSource *tsrc)
+{
+ if (style.isNotDefault(StyleSpec::BGCOLOR)) {
+ setColor(style.getColor(StyleSpec::BGCOLOR));
+ }
+ if (style.isNotDefault(StyleSpec::BGCOLOR_HOVERED)) {
+ setHoveredColor(style.getColor(StyleSpec::BGCOLOR_HOVERED));
+ }
+ if (style.isNotDefault(StyleSpec::BGCOLOR_PRESSED)) {
+ setPressedColor(style.getColor(StyleSpec::BGCOLOR_PRESSED));
+ }
+
+ if (style.isNotDefault(StyleSpec::TEXTCOLOR)) {
+ setOverrideColor(style.getColor(StyleSpec::TEXTCOLOR));
+ }
+ setNotClipped(style.getBool(StyleSpec::NOCLIP, isNotClipped()));
+ setDrawBorder(style.getBool(StyleSpec::BORDER, DrawBorder));
+ setUseAlphaChannel(style.getBool(StyleSpec::ALPHA, true));
+
+ const core::position2di buttonCenter(AbsoluteRect.getCenter());
+ core::position2d<s32> geom(buttonCenter);
+ if (style.isNotDefault(StyleSpec::BGIMG)) {
+ video::ITexture *texture = style.getTexture(StyleSpec::BGIMG, tsrc);
+
+ setImage(guiScalingImageButton(
+ Environment->getVideoDriver(), texture, geom.X, geom.Y));
+ setScaleImage(true);
+ }
+ if (style.isNotDefault(StyleSpec::BGIMG_HOVERED)) {
+ video::ITexture *hovered_texture = style.getTexture(StyleSpec::BGIMG_HOVERED, tsrc);
+
+ setHoveredImage(guiScalingImageButton(
+ Environment->getVideoDriver(), hovered_texture, geom.X, geom.Y));
+ setScaleImage(true);
+ }
+ if (style.isNotDefault(StyleSpec::BGIMG_PRESSED)) {
+ video::ITexture *pressed_texture = style.getTexture(StyleSpec::BGIMG_PRESSED, tsrc);
+
+ setPressedImage(guiScalingImageButton(
+ Environment->getVideoDriver(), pressed_texture, geom.X, geom.Y));
+ setScaleImage(true);
}
+ BgMiddle = style.getRect(StyleSpec::BGIMG_MIDDLE, BgMiddle);
}
// END PATCH
diff --git a/src/gui/guiButton.h b/src/gui/guiButton.h
index 63e29ccfc..3d1f98c32 100644
--- a/src/gui/guiButton.h
+++ b/src/gui/guiButton.h
@@ -6,6 +6,8 @@
#include "IrrCompileConfig.h"
+#include <IGUIStaticText.h>
+#include "irrlicht_changes/static_text.h"
#include "IGUIButton.h"
#include "IGUISpriteBank.h"
#include "ITexture.h"
@@ -64,6 +66,9 @@ using namespace irr;
#endif
+class ISimpleTextureSource;
+class StyleSpec;
+
class GUIButton : public gui::IGUIButton
{
public:
@@ -102,34 +107,33 @@ public:
//! Checks if an override color is enabled
virtual bool isOverrideColorEnabled(void) const;
+ // PATCH
//! Sets an image which should be displayed on the button when it is in the given state.
virtual void setImage(gui::EGUI_BUTTON_IMAGE_STATE state,
- video::ITexture* image=0,
+ video::ITexture* image=nullptr,
const core::rect<s32>& sourceRect=core::rect<s32>(0,0,0,0));
//! Sets an image which should be displayed on the button when it is in normal state.
- virtual void setImage(video::ITexture* image=0) override
- {
- setImage(gui::EGBIS_IMAGE_UP, image);
- }
+ virtual void setImage(video::ITexture* image=nullptr) override;
//! Sets an image which should be displayed on the button when it is in normal state.
- virtual void setImage(video::ITexture* image, const core::rect<s32>& pos) override
- {
- setImage(gui::EGBIS_IMAGE_UP, image, pos);
- }
+ virtual void setImage(video::ITexture* image, const core::rect<s32>& pos) override;
//! Sets an image which should be displayed on the button when it is in pressed state.
- virtual void setPressedImage(video::ITexture* image=0) override
- {
- setImage(gui::EGBIS_IMAGE_DOWN, image);
- }
+ virtual void setPressedImage(video::ITexture* image=nullptr) override;
//! Sets an image which should be displayed on the button when it is in pressed state.
- virtual void setPressedImage(video::ITexture* image, const core::rect<s32>& pos) override
- {
- setImage(gui::EGBIS_IMAGE_DOWN, image, pos);
- }
+ virtual void setPressedImage(video::ITexture* image, const core::rect<s32>& pos) override;
+
+ //! Sets an image which should be displayed on the button when it is in hovered state.
+ virtual void setHoveredImage(video::ITexture* image=nullptr);
+
+ //! Sets the text displayed by the button
+ virtual void setText(const wchar_t* text) override;
+ // END PATCH
+
+ //! Sets an image which should be displayed on the button when it is in hovered state.
+ virtual void setHoveredImage(video::ITexture* image, const core::rect<s32>& pos);
//! Sets the sprite bank used by the button
virtual void setSpriteBank(gui::IGUISpriteBank* bank=0) override;
@@ -176,6 +180,11 @@ public:
//! Returns if the button is currently pressed
virtual bool isPressed() const override;
+ // PATCH
+ //! Returns if this element (or one of its direct children) is hovered
+ bool isHovered() const;
+ // END PATCH
+
//! Sets if the button should use the skin to draw its border
virtual void setDrawBorder(bool border=true) override;
@@ -215,6 +224,13 @@ public:
void setColor(video::SColor color);
+ // PATCH
+ void setHoveredColor(video::SColor color);
+ void setPressedColor(video::SColor color);
+
+ //! Set element properties from a StyleSpec
+ virtual void setFromStyle(const StyleSpec& style, ISimpleTextureSource *tsrc);
+ // END PATCH
//! Do not drop returned handle
@@ -225,28 +241,6 @@ protected:
void drawSprite(gui::EGUI_BUTTON_STATE state, u32 startTime, const core::position2di& center);
gui::EGUI_BUTTON_IMAGE_STATE getImageState(bool pressed) const;
-private:
-
- struct ButtonSprite
- {
- ButtonSprite() : Index(-1), Loop(false), Scale(false)
- {
- }
-
- bool operator==(const ButtonSprite& other) const
- {
- return Index == other.Index && Color == other.Color && Loop == other.Loop && Scale == other.Scale;
- }
-
- s32 Index;
- video::SColor Color;
- bool Loop;
- bool Scale;
- };
-
- ButtonSprite ButtonSprites[gui::EGBS_COUNT];
- gui::IGUISpriteBank* SpriteBank;
-
struct ButtonImage
{
ButtonImage() : Texture(0), SourceRect(core::rect<s32>(0,0,0,0))
@@ -288,6 +282,30 @@ private:
core::rect<s32> SourceRect;
};
+ gui::EGUI_BUTTON_IMAGE_STATE getImageState(bool pressed, const ButtonImage* images) const;
+
+private:
+
+ struct ButtonSprite
+ {
+ ButtonSprite() : Index(-1), Loop(false), Scale(false)
+ {
+ }
+
+ bool operator==(const ButtonSprite& other) const
+ {
+ return Index == other.Index && Color == other.Color && Loop == other.Loop && Scale == other.Scale;
+ }
+
+ s32 Index;
+ video::SColor Color;
+ bool Loop;
+ bool Scale;
+ };
+
+ ButtonSprite ButtonSprites[gui::EGBS_COUNT];
+ gui::IGUISpriteBank* SpriteBank;
+
ButtonImage ButtonImages[gui::EGBIS_COUNT];
gui::IGUIFont* OverrideFont;
@@ -307,4 +325,12 @@ private:
bool ScaleImage;
video::SColor Colors[4];
+ // PATCH
+ video::SColor HoveredColors[4];
+ video::SColor PressedColors[4];
+
+ gui::IGUIStaticText *StaticText;
+
+ core::rect<s32> BgMiddle;
+ // END PATCH
};
diff --git a/src/gui/guiButtonImage.cpp b/src/gui/guiButtonImage.cpp
new file mode 100644
index 000000000..02d920277
--- /dev/null
+++ b/src/gui/guiButtonImage.cpp
@@ -0,0 +1,160 @@
+/*
+Minetest
+Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "guiButtonImage.h"
+
+#include "client/guiscalingfilter.h"
+#include "debug.h"
+#include "IGUIEnvironment.h"
+#include "IGUIImage.h"
+#include "IVideoDriver.h"
+#include "StyleSpec.h"
+
+using namespace irr;
+using namespace gui;
+
+GUIButtonImage::GUIButtonImage(gui::IGUIEnvironment *environment,
+ gui::IGUIElement *parent, s32 id, core::rect<s32> rectangle, bool noclip)
+ : GUIButton (environment, parent, id, rectangle, noclip)
+{
+ m_image = Environment->addImage(
+ core::rect<s32>(0,0,rectangle.getWidth(),rectangle.getHeight()), this);
+ m_image->setScaleImage(isScalingImage());
+ sendToBack(m_image);
+}
+
+bool GUIButtonImage::OnEvent(const SEvent& event)
+{
+ bool result = GUIButton::OnEvent(event);
+
+ EGUI_BUTTON_IMAGE_STATE imageState = getImageState(isPressed(), m_foreground_images);
+ video::ITexture *texture = m_foreground_images[(u32)imageState].Texture;
+ if (texture != nullptr)
+ {
+ m_image->setImage(texture);
+ }
+
+ m_image->setVisible(texture != nullptr);
+
+ return result;
+}
+
+void GUIButtonImage::setForegroundImage(EGUI_BUTTON_IMAGE_STATE state,
+ video::ITexture *image, const core::rect<s32> &sourceRect)
+{
+ if (state >= EGBIS_COUNT)
+ return;
+
+ if (image)
+ image->grab();
+
+ u32 stateIdx = (u32)state;
+ if (m_foreground_images[stateIdx].Texture)
+ m_foreground_images[stateIdx].Texture->drop();
+
+ m_foreground_images[stateIdx].Texture = image;
+ m_foreground_images[stateIdx].SourceRect = sourceRect;
+
+ EGUI_BUTTON_IMAGE_STATE imageState = getImageState(isPressed(), m_foreground_images);
+ if (imageState == stateIdx)
+ m_image->setImage(image);
+}
+
+void GUIButtonImage::setForegroundImage(video::ITexture *image)
+{
+ setForegroundImage(gui::EGBIS_IMAGE_UP, image);
+}
+
+void GUIButtonImage::setForegroundImage(video::ITexture *image, const core::rect<s32> &pos)
+{
+ setForegroundImage(gui::EGBIS_IMAGE_UP, image, pos);
+}
+
+void GUIButtonImage::setPressedForegroundImage(video::ITexture *image)
+{
+ setForegroundImage(gui::EGBIS_IMAGE_DOWN, image);
+}
+
+void GUIButtonImage::setPressedForegroundImage(video::ITexture *image, const core::rect<s32> &pos)
+{
+ setForegroundImage(gui::EGBIS_IMAGE_DOWN, image, pos);
+}
+
+void GUIButtonImage::setHoveredForegroundImage(video::ITexture *image)
+{
+ setForegroundImage(gui::EGBIS_IMAGE_UP_MOUSEOVER, image);
+ setForegroundImage(gui::EGBIS_IMAGE_UP_FOCUSED_MOUSEOVER, image);
+}
+
+void GUIButtonImage::setHoveredForegroundImage(video::ITexture *image, const core::rect<s32> &pos)
+{
+ setForegroundImage(gui::EGBIS_IMAGE_UP_MOUSEOVER, image, pos);
+ setForegroundImage(gui::EGBIS_IMAGE_UP_FOCUSED_MOUSEOVER, image, pos);
+}
+
+void GUIButtonImage::setFromStyle(const StyleSpec &style, ISimpleTextureSource *tsrc)
+{
+ GUIButton::setFromStyle(style, tsrc);
+
+ video::IVideoDriver *driver = Environment->getVideoDriver();
+
+ const core::position2di buttonCenter(AbsoluteRect.getCenter());
+ core::position2d<s32> geom(buttonCenter);
+ if (style.isNotDefault(StyleSpec::FGIMG)) {
+ video::ITexture *texture = style.getTexture(StyleSpec::FGIMG, tsrc);
+
+ setForegroundImage(guiScalingImageButton(driver, texture, geom.X, geom.Y));
+ setScaleImage(true);
+ }
+ if (style.isNotDefault(StyleSpec::FGIMG_HOVERED)) {
+ video::ITexture *hovered_texture = style.getTexture(StyleSpec::FGIMG_HOVERED, tsrc);
+
+ setHoveredForegroundImage(guiScalingImageButton(driver, hovered_texture, geom.X, geom.Y));
+ setScaleImage(true);
+ }
+ if (style.isNotDefault(StyleSpec::FGIMG_PRESSED)) {
+ video::ITexture *pressed_texture = style.getTexture(StyleSpec::FGIMG_PRESSED, tsrc);
+
+ setPressedForegroundImage(guiScalingImageButton(driver, pressed_texture, geom.X, geom.Y));
+ setScaleImage(true);
+ }
+}
+
+void GUIButtonImage::setScaleImage(bool scaleImage)
+{
+ GUIButton::setScaleImage(scaleImage);
+ m_image->setScaleImage(scaleImage);
+}
+
+GUIButtonImage *GUIButtonImage::addButton(IGUIEnvironment *environment,
+ const core::rect<s32> &rectangle, IGUIElement *parent, s32 id,
+ const wchar_t *text, const wchar_t *tooltiptext)
+{
+ GUIButtonImage *button = new GUIButtonImage(environment,
+ parent ? parent : environment->getRootGUIElement(), id, rectangle);
+
+ if (text)
+ button->setText(text);
+
+ if (tooltiptext)
+ button->setToolTipText(tooltiptext);
+
+ button->drop();
+ return button;
+}
diff --git a/src/gui/guiButtonImage.h b/src/gui/guiButtonImage.h
new file mode 100644
index 000000000..15901ee5d
--- /dev/null
+++ b/src/gui/guiButtonImage.h
@@ -0,0 +1,59 @@
+/*
+Minetest
+Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "guiButton.h"
+#include "IGUIButton.h"
+
+using namespace irr;
+
+class GUIButtonImage : public GUIButton
+{
+public:
+ //! constructor
+ GUIButtonImage(gui::IGUIEnvironment *environment, gui::IGUIElement *parent,
+ s32 id, core::rect<s32> rectangle, bool noclip = false);
+
+ virtual bool OnEvent(const SEvent& event) override;
+
+ void setForegroundImage(gui::EGUI_BUTTON_IMAGE_STATE state,
+ video::ITexture *image = nullptr,
+ const core::rect<s32> &sourceRect = core::rect<s32>(0, 0, 0, 0));
+
+ void setForegroundImage(video::ITexture *image = nullptr);
+ void setForegroundImage(video::ITexture *image, const core::rect<s32> &pos);
+
+ void setPressedForegroundImage(video::ITexture *image = nullptr);
+ void setPressedForegroundImage(video::ITexture *image, const core::rect<s32> &pos);
+
+ void setHoveredForegroundImage(video::ITexture *image = nullptr);
+ void setHoveredForegroundImage(video::ITexture *image, const core::rect<s32> &pos);
+
+ virtual void setFromStyle(const StyleSpec &style, ISimpleTextureSource *tsrc) override;
+
+ virtual void setScaleImage(bool scaleImage=true) override;
+
+ //! Do not drop returned handle
+ static GUIButtonImage *addButton(gui::IGUIEnvironment *environment,
+ const core::rect<s32> &rectangle, IGUIElement *parent, s32 id,
+ const wchar_t *text, const wchar_t *tooltiptext = L"");
+
+private:
+ ButtonImage m_foreground_images[gui::EGBIS_COUNT];
+ gui::IGUIImage *m_image;
+};
diff --git a/src/gui/guiButtonItemImage.cpp b/src/gui/guiButtonItemImage.cpp
new file mode 100644
index 000000000..5c48b2acd
--- /dev/null
+++ b/src/gui/guiButtonItemImage.cpp
@@ -0,0 +1,57 @@
+/*
+Minetest
+Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "guiButtonItemImage.h"
+
+#include "client/client.h"
+#include "client/hud.h" // drawItemStack
+#include "guiItemImage.h"
+#include "IGUIEnvironment.h"
+#include "itemdef.h"
+
+using namespace irr;
+using namespace gui;
+
+GUIButtonItemImage::GUIButtonItemImage(gui::IGUIEnvironment *environment, gui::IGUIElement *parent,
+ s32 id, core::rect<s32> rectangle, std::string item, Client *client, bool noclip)
+ : GUIButton (environment, parent, id, rectangle, noclip)
+{
+ m_image = new GUIItemImage(environment, this, id,
+ core::rect<s32>(0,0,rectangle.getWidth(),rectangle.getHeight()),
+ item, getActiveFont(), client);
+ sendToBack(m_image);
+
+ m_item_name = item;
+ m_client = client;
+}
+
+GUIButtonItemImage *GUIButtonItemImage::addButton(IGUIEnvironment *environment,
+ const core::rect<s32> &rectangle, IGUIElement *parent, s32 id,
+ const wchar_t *text, std::string item, Client *client)
+{
+ GUIButtonItemImage *button = new GUIButtonItemImage(environment,
+ parent ? parent : environment->getRootGUIElement(),
+ id, rectangle, item, client);
+
+ if (text)
+ button->setText(text);
+
+ button->drop();
+ return button;
+}
diff --git a/src/gui/guiButtonItemImage.h b/src/gui/guiButtonItemImage.h
new file mode 100644
index 000000000..0a61874da
--- /dev/null
+++ b/src/gui/guiButtonItemImage.h
@@ -0,0 +1,45 @@
+/*
+Minetest
+Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "guiButton.h"
+#include "IGUIButton.h"
+
+using namespace irr;
+
+class Client;
+class GUIItemImage;
+
+class GUIButtonItemImage : public GUIButton
+{
+public:
+ //! constructor
+ GUIButtonItemImage(gui::IGUIEnvironment *environment, gui::IGUIElement *parent,
+ s32 id, core::rect<s32> rectangle, std::string item,
+ Client *client, bool noclip = false);
+
+ //! Do not drop returned handle
+ static GUIButtonItemImage *addButton(gui::IGUIEnvironment *environment,
+ const core::rect<s32> &rectangle, IGUIElement *parent, s32 id,
+ const wchar_t *text, std::string item, Client *client);
+
+private:
+ std::string m_item_name;
+ Client *m_client;
+ GUIItemImage *m_image;
+};
diff --git a/src/gui/guiConfirmRegistration.cpp b/src/gui/guiConfirmRegistration.cpp
index 6fe2a4fc4..0d8bdf54e 100644
--- a/src/gui/guiConfirmRegistration.cpp
+++ b/src/gui/guiConfirmRegistration.cpp
@@ -20,6 +20,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "guiConfirmRegistration.h"
#include "client/client.h"
+#include "guiButton.h"
#include <IGUICheckBox.h>
#include <IGUIButton.h>
#include <IGUIStaticText.h>
@@ -32,8 +33,9 @@ with this program; if not, write to the Free Software Foundation, Inc.,
// Continuing from guiPasswordChange.cpp
const int ID_confirmPassword = 262;
const int ID_confirm = 263;
-const int ID_message = 264;
+const int ID_intotext = 264;
const int ID_cancel = 265;
+const int ID_message = 266;
GUIConfirmRegistration::GUIConfirmRegistration(gui::IGUIEnvironment *env,
gui::IGUIElement *parent, s32 id, IMenuManager *menumgr, Client *client,
@@ -105,7 +107,7 @@ void GUIConfirmRegistration::regenerateGui(v2u32 screensize)
wchar_t *info_text_buf_wide = utf8_to_wide_c(info_text_buf);
gui::IGUIEditBox *e = new gui::intlGUIEditBox(info_text_buf_wide, true,
- Environment, this, ID_message, rect2, false, true);
+ Environment, this, ID_intotext, rect2, false, true);
delete[] info_text_buf_wide;
e->drop();
e->setMultiLine(true);
@@ -113,7 +115,7 @@ void GUIConfirmRegistration::regenerateGui(v2u32 screensize)
e->setTextAlignment(gui::EGUIA_UPPERLEFT, gui::EGUIA_CENTER);
}
- ypos += 210 * s;
+ ypos += 200 * s;
{
core::rect<s32> rect2(0, 0, 540 * s, 30 * s);
rect2 += topleft_client + v2s32(30 * s, ypos);
@@ -123,24 +125,24 @@ void GUIConfirmRegistration::regenerateGui(v2u32 screensize)
Environment->setFocus(e);
}
- ypos += 60 * s;
+ ypos += 50 * s;
{
core::rect<s32> rect2(0, 0, 230 * s, 35 * s);
rect2 = rect2 + v2s32(size.X / 2 - 220 * s, ypos);
text = wgettext("Register and Join");
- Environment->addButton(rect2, this, ID_confirm, text);
+ GUIButton::addButton(Environment, rect2, this, ID_confirm, text);
delete[] text;
}
{
core::rect<s32> rect2(0, 0, 120 * s, 35 * s);
rect2 = rect2 + v2s32(size.X / 2 + 70 * s, ypos);
text = wgettext("Cancel");
- Environment->addButton(rect2, this, ID_cancel, text);
+ GUIButton::addButton(Environment, rect2, this, ID_cancel, text);
delete[] text;
}
{
- core::rect<s32> rect2(0, 0, 200 * s, 20 * s);
- rect2 += topleft_client + v2s32(30 * s, ypos - 40 * s);
+ core::rect<s32> rect2(0, 0, 500 * s, 40 * s);
+ rect2 += topleft_client + v2s32(30 * s, ypos + 40 * s);
text = wgettext("Passwords do not match!");
IGUIElement *e = Environment->addStaticText(
text, rect2, false, true, this, ID_message);
diff --git a/src/gui/guiEditBoxWithScrollbar.cpp b/src/gui/guiEditBoxWithScrollbar.cpp
index 2f909f54f..442406688 100644
--- a/src/gui/guiEditBoxWithScrollbar.cpp
+++ b/src/gui/guiEditBoxWithScrollbar.cpp
@@ -1109,10 +1109,13 @@ void GUIEditBoxWithScrollBar::breakText()
m_broken_text_positions.push_back(last_line_start);
}
-// TODO: that function does interpret VAlign according to line-index (indexed line is placed on top-center-bottom)
-// but HAlign according to line-width (pixels) and not by row.
-// Intuitively I suppose HAlign handling is better as VScrollPos should handle the line-scrolling.
-// But please no one change this without also rewriting (and this time fucking testing!!!) autoscrolling (I noticed this when fixing the old autoscrolling).
+// TODO: that function does interpret VAlign according to line-index (indexed
+// line is placed on top-center-bottom) but HAlign according to line-width
+// (pixels) and not by row.
+// Intuitively I suppose HAlign handling is better as VScrollPos should handle
+// the line-scrolling.
+// But please no one change this without also rewriting (and this time
+// testing!!!) autoscrolling (I noticed this when fixing the old autoscrolling).
void GUIEditBoxWithScrollBar::setTextRect(s32 line)
{
if (line < 0)
diff --git a/src/gui/guiFormSpecMenu.cpp b/src/gui/guiFormSpecMenu.cpp
index 8d740237c..59cd130ef 100644
--- a/src/gui/guiFormSpecMenu.cpp
+++ b/src/gui/guiFormSpecMenu.cpp
@@ -21,22 +21,22 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include <cstdlib>
#include <algorithm>
#include <iterator>
-#include <sstream>
#include <limits>
-#include "guiButton.h"
+#include <sstream>
#include "guiFormSpecMenu.h"
+#include "guiScrollBar.h"
#include "guiTable.h"
#include "constants.h"
#include "gamedef.h"
#include "client/keycode.h"
#include "util/strfnd.h"
+#include <IGUIButton.h>
#include <IGUICheckBox.h>
+#include <IGUIComboBox.h>
#include <IGUIEditBox.h>
-#include <IGUIButton.h>
#include <IGUIStaticText.h>
#include <IGUIFont.h>
#include <IGUITabControl.h>
-#include <IGUIComboBox.h>
#include "client/renderingengine.h"
#include "log.h"
#include "client/tile.h" // ITextureSource
@@ -55,8 +55,19 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "util/string.h" // for parseColorString()
#include "irrlicht_changes/static_text.h"
#include "client/guiscalingfilter.h"
+#include "guiAnimatedImage.h"
+#include "guiBackgroundImage.h"
+#include "guiBox.h"
+#include "guiButton.h"
+#include "guiButtonImage.h"
+#include "guiButtonItemImage.h"
#include "guiEditBoxWithScrollbar.h"
+#include "guiInventoryList.h"
+#include "guiItemImage.h"
+#include "guiScrollBar.h"
+#include "guiTable.h"
#include "intlGUIEditBox.h"
+#include "guiHyperText.h"
#define MY_CHECKPOS(a,b) \
if (v_pos.size() != 2) { \
@@ -118,9 +129,20 @@ GUIFormSpecMenu::~GUIFormSpecMenu()
{
removeChildren();
- for (auto &table_it : m_tables) {
+ for (auto &table_it : m_tables)
table_it.second->drop();
- }
+ for (auto &inventorylist_it : m_inventorylists)
+ inventorylist_it->drop();
+ for (auto &checkbox_it : m_checkboxes)
+ checkbox_it.second->drop();
+ for (auto &scrollbar_it : m_scrollbars)
+ scrollbar_it.second->drop();
+ for (auto &background_it : m_backgrounds)
+ background_it->drop();
+ for (auto &tooltip_rect_it : m_tooltip_rects)
+ tooltip_rect_it.first->drop();
+ for (auto &clickthrough_it : m_clickthrough_elements)
+ clickthrough_it->drop();
delete m_selected_item;
delete m_form_src;
@@ -155,16 +177,15 @@ void GUIFormSpecMenu::removeChildren()
{
const core::list<gui::IGUIElement*> &children = getChildren();
- while(!children.empty()) {
+ while (!children.empty()) {
(*children.getLast())->remove();
}
- if(m_tooltip_element) {
+ if (m_tooltip_element) {
m_tooltip_element->remove();
m_tooltip_element->drop();
- m_tooltip_element = NULL;
+ m_tooltip_element = nullptr;
}
-
}
void GUIFormSpecMenu::setInitialFocus()
@@ -256,14 +277,9 @@ std::vector<std::string>* GUIFormSpecMenu::getDropDownValues(const std::string &
return NULL;
}
-v2s32 GUIFormSpecMenu::getElementBasePos(bool absolute,
- const std::vector<std::string> *v_pos)
+v2s32 GUIFormSpecMenu::getElementBasePos(const std::vector<std::string> *v_pos)
{
- v2s32 pos = padding;
- if (absolute)
- pos += AbsoluteRect.UpperLeftCorner;
-
- v2f32 pos_f = v2f32(pos.X, pos.Y) + pos_offset * spacing;
+ v2f32 pos_f = v2f32(padding.X, padding.Y) + pos_offset * spacing;
if (v_pos) {
pos_f.X += stof((*v_pos)[0]) * spacing.X;
pos_f.Y += stof((*v_pos)[1]) * spacing.Y;
@@ -271,18 +287,10 @@ v2s32 GUIFormSpecMenu::getElementBasePos(bool absolute,
return v2s32(pos_f.X, pos_f.Y);
}
-v2s32 GUIFormSpecMenu::getRealCoordinateBasePos(bool absolute,
- const std::vector<std::string> &v_pos)
+v2s32 GUIFormSpecMenu::getRealCoordinateBasePos(const std::vector<std::string> &v_pos)
{
- v2f32 pos_f = v2f32(0.0f, 0.0f);
-
- pos_f.X += stof(v_pos[0]) + pos_offset.X;
- pos_f.Y += stof(v_pos[1]) + pos_offset.Y;
-
- if (absolute)
- return v2s32(pos_f.X * imgsize.X + AbsoluteRect.UpperLeftCorner.X,
- pos_f.Y * imgsize.Y + AbsoluteRect.UpperLeftCorner.Y);
- return v2s32(pos_f.X * imgsize.X, pos_f.Y * imgsize.Y);
+ return v2s32((stof(v_pos[0]) + pos_offset.X) * imgsize.X,
+ (stof(v_pos[1]) + pos_offset.Y) * imgsize.Y);
}
v2s32 GUIFormSpecMenu::getRealCoordinateGeometry(const std::vector<std::string> &v_geom)
@@ -343,7 +351,7 @@ void GUIFormSpecMenu::parseContainerEnd(parserData* data)
}
}
-void GUIFormSpecMenu::parseList(parserData* data, const std::string &element)
+void GUIFormSpecMenu::parseList(parserData *data, const std::string &element)
{
if (m_client == 0) {
warningstream<<"invalid use of 'list' with m_client==0"<<std::endl;
@@ -373,14 +381,7 @@ void GUIFormSpecMenu::parseList(parserData* data, const std::string &element)
else
loc.deSerialize(location);
- v2s32 pos;
v2s32 geom;
-
- if (data->real_coordinates)
- pos = getRealCoordinateBasePos(true, v_pos);
- else
- pos = getElementBasePos(true, &v_pos);
-
geom.X = stoi(v_geom[0]);
geom.Y = stoi(v_geom[1]);
@@ -393,15 +394,67 @@ void GUIFormSpecMenu::parseList(parserData* data, const std::string &element)
return;
}
- if(!data->explicit_size)
- warningstream<<"invalid use of list without a size[] element"<<std::endl;
- m_inventorylists.emplace_back(loc, listname, pos, geom, start_i, data->real_coordinates);
+ // check for the existence of inventory and list
+ Inventory *inv = m_invmgr->getInventory(loc);
+ if (!inv) {
+ warningstream << "GUIFormSpecMenu::parseList(): "
+ << "The inventory location "
+ << "\"" << loc.dump() << "\" doesn't exist"
+ << std::endl;
+ return;
+ }
+ InventoryList *ilist = inv->getList(listname);
+ if (!ilist) {
+ warningstream << "GUIFormSpecMenu::parseList(): "
+ << "The inventory list \"" << listname << "\" "
+ << "@ \"" << loc.dump() << "\" doesn't exist"
+ << std::endl;
+ return;
+ }
+
+ // trim geom if it is larger than the actual inventory size
+ s32 list_size = (s32)ilist->getSize();
+ if (list_size < geom.X * geom.Y + start_i) {
+ list_size -= MYMAX(start_i, 0);
+ geom.Y = list_size / geom.X;
+ geom.Y += list_size % geom.X > 0 ? 1 : 0;
+ if (geom.Y <= 1)
+ geom.X = list_size;
+ }
+
+ if (!data->explicit_size)
+ warningstream << "invalid use of list without a size[] element" << std::endl;
+
+ FieldSpec spec(
+ "",
+ L"",
+ L"",
+ 258 + m_fields.size(),
+ 3
+ );
+
+ v2f32 slot_spacing = data->real_coordinates ?
+ v2f32(imgsize.X * 1.25f, imgsize.Y * 1.25f) : spacing;
+
+ v2s32 pos = data->real_coordinates ? getRealCoordinateBasePos(v_pos)
+ : getElementBasePos(&v_pos);
+
+ core::rect<s32> rect = core::rect<s32>(pos.X, pos.Y,
+ pos.X + (geom.X - 1) * slot_spacing.X + imgsize.X,
+ pos.Y + (geom.Y - 1) * slot_spacing.Y + imgsize.Y);
+
+ GUIInventoryList *e = new GUIInventoryList(Environment, this, spec.fid,
+ rect, m_invmgr, loc, listname, geom, start_i, imgsize, slot_spacing,
+ this, data->inventorylist_options, m_font);
+
+ m_inventorylists.push_back(e);
+ m_fields.push_back(spec);
return;
}
errorstream<< "Invalid list element(" << parts.size() << "): '" << element << "'" << std::endl;
}
-void GUIFormSpecMenu::parseListRing(parserData* data, const std::string &element)
+void GUIFormSpecMenu::parseListRing(parserData *data, const std::string &element)
{
if (m_client == 0) {
errorstream << "WARNING: invalid use of 'listring' with m_client==0" << std::endl;
@@ -428,10 +481,10 @@ void GUIFormSpecMenu::parseListRing(parserData* data, const std::string &element
if (element.empty() && m_inventorylists.size() > 1) {
size_t siz = m_inventorylists.size();
// insert the last two inv list elements into the list ring
- const ListDrawSpec &spa = m_inventorylists[siz - 2];
- const ListDrawSpec &spb = m_inventorylists[siz - 1];
- m_inventory_rings.emplace_back(spa.inventoryloc, spa.listname);
- m_inventory_rings.emplace_back(spb.inventoryloc, spb.listname);
+ const GUIInventoryList *spa = m_inventorylists[siz - 2];
+ const GUIInventoryList *spb = m_inventorylists[siz - 1];
+ m_inventory_rings.emplace_back(spa->getInventoryloc(), spa->getListname());
+ m_inventory_rings.emplace_back(spb->getInventoryloc(), spb->getListname());
return;
}
@@ -470,7 +523,7 @@ void GUIFormSpecMenu::parseCheckbox(parserData* data, const std::string &element
core::rect<s32> rect;
if (data->real_coordinates) {
- pos = getRealCoordinateBasePos(false, v_pos);
+ pos = getRealCoordinateBasePos(v_pos);
rect = core::rect<s32>(
pos.X,
@@ -479,7 +532,7 @@ void GUIFormSpecMenu::parseCheckbox(parserData* data, const std::string &element
pos.Y + y_center
);
} else {
- pos = getElementBasePos(false, &v_pos);
+ pos = getElementBasePos(&v_pos);
rect = core::rect<s32>(
pos.X,
pos.Y + imgsize.Y / 2 - y_center,
@@ -497,7 +550,7 @@ void GUIFormSpecMenu::parseCheckbox(parserData* data, const std::string &element
spec.ftype = f_CheckBox;
- gui::IGUICheckBox* e = Environment->addCheckBox(fselected, rect, this,
+ gui::IGUICheckBox *e = Environment->addCheckBox(fselected, rect, this,
spec.fid, spec.flabel.c_str());
auto style = getStyleForElement("checkbox", name);
@@ -507,7 +560,8 @@ void GUIFormSpecMenu::parseCheckbox(parserData* data, const std::string &element
Environment->setFocus(e);
}
- m_checkboxes.emplace_back(spec,e);
+ e->grab();
+ m_checkboxes.emplace_back(spec, e);
m_fields.push_back(spec);
return;
}
@@ -531,10 +585,10 @@ void GUIFormSpecMenu::parseScrollBar(parserData* data, const std::string &elemen
v2s32 dim;
if (data->real_coordinates) {
- pos = getRealCoordinateBasePos(false, v_pos);
+ pos = getRealCoordinateBasePos(v_pos);
dim = getRealCoordinateGeometry(v_geom);
} else {
- pos = getElementBasePos(false, &v_pos);
+ pos = getElementBasePos(&v_pos);
dim.X = stof(v_geom[0]) * spacing.X;
dim.Y = stof(v_geom[1]) * spacing.Y;
}
@@ -556,23 +610,87 @@ void GUIFormSpecMenu::parseScrollBar(parserData* data, const std::string &elemen
spec.ftype = f_ScrollBar;
spec.send = true;
- gui::IGUIScrollBar* e =
- Environment->addScrollBar(is_horizontal,rect,this,spec.fid);
+ GUIScrollBar *e = new GUIScrollBar(Environment, this, spec.fid, rect,
+ is_horizontal, true);
auto style = getStyleForElement("scrollbar", name);
e->setNotClipped(style.getBool(StyleSpec::NOCLIP, false));
+ e->setArrowsVisible(data->scrollbar_options.arrow_visiblity);
+
+ s32 max = data->scrollbar_options.max;
+ s32 min = data->scrollbar_options.min;
+
+ e->setMax(max);
+ e->setMin(min);
- e->setMax(1000);
- e->setMin(0);
e->setPos(stoi(parts[4]));
- e->setSmallStep(10);
- e->setLargeStep(100);
+
+ e->setSmallStep(data->scrollbar_options.small_step);
+ e->setLargeStep(data->scrollbar_options.large_step);
+
+ s32 scrollbar_size = is_horizontal ? dim.X : dim.Y;
+
+ e->setPageSize(scrollbar_size * (max - min + 1) / data->scrollbar_options.thumb_size);
m_scrollbars.emplace_back(spec,e);
m_fields.push_back(spec);
return;
}
- errorstream<< "Invalid scrollbar element(" << parts.size() << "): '" << element << "'" << std::endl;
+ errorstream << "Invalid scrollbar element(" << parts.size() << "): '" << element
+ << "'" << std::endl;
+}
+
+void GUIFormSpecMenu::parseScrollBarOptions(parserData* data, const std::string &element)
+{
+ std::vector<std::string> parts = split(element, ';');
+
+ if (parts.size() == 0) {
+ warningstream << "Invalid scrollbaroptions element(" << parts.size() << "): '" <<
+ element << "'" << std::endl;
+ return;
+ }
+
+ for (const std::string &i : parts) {
+ std::vector<std::string> options = split(i, '=');
+
+ if (options.size() != 2) {
+ warningstream << "Invalid scrollbaroptions option syntax: '" <<
+ element << "'" << std::endl;
+ continue; // Go to next option
+ }
+
+ if (options[0] == "max") {
+ data->scrollbar_options.max = stoi(options[1]);
+ continue;
+ } else if (options[0] == "min") {
+ data->scrollbar_options.min = stoi(options[1]);
+ continue;
+ } else if (options[0] == "smallstep") {
+ int value = stoi(options[1]);
+ data->scrollbar_options.small_step = value < 0 ? 10 : value;
+ continue;
+ } else if (options[0] == "largestep") {
+ int value = stoi(options[1]);
+ data->scrollbar_options.large_step = value < 0 ? 100 : value;
+ continue;
+ } else if (options[0] == "thumbsize") {
+ int value = stoi(options[1]);
+ data->scrollbar_options.thumb_size = value <= 0 ? 1 : value;
+ continue;
+ } else if (options[0] == "arrows") {
+ std::string value = trim(options[1]);
+ if (value == "hide")
+ data->scrollbar_options.arrow_visiblity = GUIScrollBar::HIDE;
+ else if (value == "show")
+ data->scrollbar_options.arrow_visiblity = GUIScrollBar::SHOW;
+ else // Auto hide/show
+ data->scrollbar_options.arrow_visiblity = GUIScrollBar::DEFAULT;
+ continue;
+ }
+
+ warningstream << "Invalid scrollbaroptions option(" << options[0] <<
+ "): '" << element << "'" << std::endl;
+ }
}
void GUIFormSpecMenu::parseImage(parserData* data, const std::string &element)
@@ -593,17 +711,42 @@ void GUIFormSpecMenu::parseImage(parserData* data, const std::string &element)
v2s32 geom;
if (data->real_coordinates) {
- pos = getRealCoordinateBasePos(true, v_pos);
+ pos = getRealCoordinateBasePos(v_pos);
geom = getRealCoordinateGeometry(v_geom);
} else {
- pos = getElementBasePos(true, &v_pos);
+ pos = getElementBasePos(&v_pos);
geom.X = stof(v_geom[0]) * (float)imgsize.X;
geom.Y = stof(v_geom[1]) * (float)imgsize.Y;
}
if (!data->explicit_size)
warningstream<<"invalid use of image without a size[] element"<<std::endl;
- m_images.emplace_back(name, pos, geom);
+
+ video::ITexture *texture = m_tsrc->getTexture(name);
+ if (!texture) {
+ errorstream << "GUIFormSpecMenu::parseImage() Unable to load texture:"
+ << std::endl << "\t" << name << std::endl;
+ return;
+ }
+
+ FieldSpec spec(
+ name,
+ L"",
+ L"",
+ 258 + m_fields.size(),
+ 1
+ );
+ core::rect<s32> rect(pos, pos + geom);
+ gui::IGUIImage *e = Environment->addImage(rect, this, spec.fid, 0, true);
+ e->setImage(texture);
+ e->setScaleImage(true);
+ auto style = getStyleForElement("image", spec.fname);
+ e->setNotClipped(style.getBool(StyleSpec::NOCLIP, m_formspec_version < 3));
+ m_fields.push_back(spec);
+
+ // images should let events through
+ e->grab();
+ m_clickthrough_elements.push_back(e);
return;
}
@@ -613,16 +756,98 @@ void GUIFormSpecMenu::parseImage(parserData* data, const std::string &element)
MY_CHECKPOS("image", 0);
- v2s32 pos = getElementBasePos(true, &v_pos);
+ v2s32 pos = getElementBasePos(&v_pos);
if (!data->explicit_size)
warningstream<<"invalid use of image without a size[] element"<<std::endl;
- m_images.emplace_back(name, pos);
+
+ video::ITexture *texture = m_tsrc->getTexture(name);
+ if (!texture) {
+ errorstream << "GUIFormSpecMenu::parseImage() Unable to load texture:"
+ << std::endl << "\t" << name << std::endl;
+ return;
+ }
+
+ FieldSpec spec(
+ name,
+ L"",
+ L"",
+ 258 + m_fields.size()
+ );
+ gui::IGUIImage *e = Environment->addImage(texture, pos, true, this,
+ spec.fid, 0);
+ auto style = getStyleForElement("image", spec.fname);
+ e->setNotClipped(style.getBool(StyleSpec::NOCLIP, m_formspec_version < 3));
+ m_fields.push_back(spec);
+
+ // images should let events through
+ e->grab();
+ m_clickthrough_elements.push_back(e);
return;
}
errorstream<< "Invalid image element(" << parts.size() << "): '" << element << "'" << std::endl;
}
+void GUIFormSpecMenu::parseAnimatedImage(parserData *data, const std::string &element)
+{
+ std::vector<std::string> parts = split(element, ';');
+
+ if (parts.size() != 6 && parts.size() != 7 &&
+ !(parts.size() > 7 && m_formspec_version > FORMSPEC_API_VERSION)) {
+ errorstream << "Invalid animated_image element(" << parts.size()
+ << "): '" << element << "'" << std::endl;
+ return;
+ }
+
+ std::vector<std::string> v_pos = split(parts[0], ',');
+ std::vector<std::string> v_geom = split(parts[1], ',');
+ std::string name = parts[2];
+ std::string texture_name = unescape_string(parts[3]);
+ s32 frame_count = stoi(parts[4]);
+ s32 frame_duration = stoi(parts[5]);
+
+ MY_CHECKPOS("animated_image", 0);
+ MY_CHECKGEOM("animated_image", 1);
+
+ v2s32 pos;
+ v2s32 geom;
+
+ if (data->real_coordinates) {
+ pos = getRealCoordinateBasePos(v_pos);
+ geom = getRealCoordinateGeometry(v_geom);
+ } else {
+ pos = getElementBasePos(&v_pos);
+ geom.X = stof(v_geom[0]) * (float)imgsize.X;
+ geom.Y = stof(v_geom[1]) * (float)imgsize.Y;
+ }
+
+ if (!data->explicit_size)
+ warningstream << "Invalid use of animated_image without a size[] element" << std::endl;
+
+ FieldSpec spec(
+ name,
+ L"",
+ L"",
+ 258 + m_fields.size()
+ );
+ spec.ftype = f_AnimatedImage;
+ spec.send = true;
+
+ core::rect<s32> rect = core::rect<s32>(pos, pos + geom);
+
+ GUIAnimatedImage *e = new GUIAnimatedImage(Environment, this, spec.fid,
+ rect, texture_name, frame_count, frame_duration, m_tsrc);
+
+ if (parts.size() >= 7)
+ e->setFrameIndex(stoi(parts[6]) - 1);
+
+ auto style = getStyleForElement("animated_image", spec.fname, "image");
+ e->setNotClipped(style.getBool(StyleSpec::NOCLIP, false));
+ e->drop();
+
+ m_fields.push_back(spec);
+}
+
void GUIFormSpecMenu::parseItemImage(parserData* data, const std::string &element)
{
std::vector<std::string> parts = split(element,';');
@@ -641,17 +866,35 @@ void GUIFormSpecMenu::parseItemImage(parserData* data, const std::string &elemen
v2s32 geom;
if (data->real_coordinates) {
- pos = getRealCoordinateBasePos(true, v_pos);
+ pos = getRealCoordinateBasePos(v_pos);
geom = getRealCoordinateGeometry(v_geom);
} else {
- pos = getElementBasePos(true, &v_pos);
+ pos = getElementBasePos(&v_pos);
geom.X = stof(v_geom[0]) * (float)imgsize.X;
geom.Y = stof(v_geom[1]) * (float)imgsize.Y;
}
if(!data->explicit_size)
warningstream<<"invalid use of item_image without a size[] element"<<std::endl;
- m_itemimages.emplace_back("", name, pos, geom);
+
+ FieldSpec spec(
+ "",
+ L"",
+ L"",
+ 258 + m_fields.size(),
+ 2
+ );
+ spec.ftype = f_ItemImage;
+
+ GUIItemImage *e = new GUIItemImage(Environment, this, spec.fid,
+ core::rect<s32>(pos, pos + geom), name, m_font, m_client);
+ auto style = getStyleForElement("item_image", spec.fname);
+ e->setNotClipped(style.getBool(StyleSpec::NOCLIP, false));
+
+ // item images should let events through
+ m_clickthrough_elements.push_back(e);
+
+ m_fields.push_back(spec);
return;
}
errorstream<< "Invalid ItemImage element(" << parts.size() << "): '" << element << "'" << std::endl;
@@ -678,12 +921,12 @@ void GUIFormSpecMenu::parseButton(parserData* data, const std::string &element,
core::rect<s32> rect;
if (data->real_coordinates) {
- pos = getRealCoordinateBasePos(false, v_pos);
+ pos = getRealCoordinateBasePos(v_pos);
geom = getRealCoordinateGeometry(v_geom);
rect = core::rect<s32>(pos.X, pos.Y, pos.X+geom.X,
pos.Y+geom.Y);
} else {
- pos = getElementBasePos(false, &v_pos);
+ pos = getElementBasePos(&v_pos);
geom.X = (stof(v_geom[0]) * spacing.X) - (spacing.X - imgsize.X);
pos.Y += (stof(v_geom[1]) * (float)imgsize.Y)/2;
@@ -700,7 +943,7 @@ void GUIFormSpecMenu::parseButton(parserData* data, const std::string &element,
name,
wlabel,
L"",
- 258+m_fields.size()
+ 258 + m_fields.size()
);
spec.ftype = f_Button;
if(type == "button_exit")
@@ -709,34 +952,7 @@ void GUIFormSpecMenu::parseButton(parserData* data, const std::string &element,
GUIButton *e = GUIButton::addButton(Environment, rect, this, spec.fid, spec.flabel.c_str());
auto style = getStyleForElement(type, name, (type != "button") ? "button" : "");
- if (style.isNotDefault(StyleSpec::BGCOLOR)) {
- e->setColor(style.getColor(StyleSpec::BGCOLOR));
- }
- if (style.isNotDefault(StyleSpec::TEXTCOLOR)) {
- e->setOverrideColor(style.getColor(StyleSpec::TEXTCOLOR));
- }
- e->setNotClipped(style.getBool(StyleSpec::NOCLIP, false));
- e->setDrawBorder(style.getBool(StyleSpec::BORDER, true));
-
- if (style.isNotDefault(StyleSpec::BGIMG)) {
- std::string image_name = style.get(StyleSpec::BGIMG, "");
- std::string pressed_image_name = style.get(StyleSpec::BGIMG_PRESSED, "");
-
- video::ITexture *texture = 0;
- video::ITexture *pressed_texture = 0;
- texture = m_tsrc->getTexture(image_name);
- if (!pressed_image_name.empty())
- pressed_texture = m_tsrc->getTexture(pressed_image_name);
- else
- pressed_texture = texture;
-
- e->setUseAlphaChannel(style.getBool(StyleSpec::ALPHA, true));
- e->setImage(guiScalingImageButton(
- Environment->getVideoDriver(), texture, geom.X, geom.Y));
- e->setPressedImage(guiScalingImageButton(
- Environment->getVideoDriver(), pressed_texture, geom.X, geom.Y));
- e->setScaleImage(true);
- }
+ e->setFromStyle(style, m_tsrc);
if (spec.fname == data->focused_fieldname) {
Environment->setFocus(e);
@@ -765,10 +981,10 @@ void GUIFormSpecMenu::parseBackground(parserData* data, const std::string &eleme
v2s32 geom;
if (data->real_coordinates) {
- pos = getRealCoordinateBasePos(true, v_pos);
+ pos = getRealCoordinateBasePos(v_pos);
geom = getRealCoordinateGeometry(v_geom);
} else {
- pos = getElementBasePos(true, &v_pos);
+ pos = getElementBasePos(&v_pos);
pos.X -= (spacing.X - (float)imgsize.X) / 2;
pos.Y -= (spacing.Y - (float)imgsize.Y) / 2;
@@ -779,7 +995,7 @@ void GUIFormSpecMenu::parseBackground(parserData* data, const std::string &eleme
bool clip = false;
if (parts.size() >= 4 && is_yes(parts[3])) {
if (data->real_coordinates) {
- pos = getRealCoordinateBasePos(false, v_pos) * -1;
+ pos = getRealCoordinateBasePos(v_pos) * -1;
geom = v2s32(0, 0);
} else {
pos.X = stoi(v_pos[0]); //acts as offset
@@ -812,8 +1028,33 @@ void GUIFormSpecMenu::parseBackground(parserData* data, const std::string &eleme
if (!data->explicit_size && !clip)
warningstream << "invalid use of unclipped background without a size[] element" << std::endl;
- m_backgrounds.emplace_back(name, pos, geom, middle, clip);
+ FieldSpec spec(
+ name,
+ L"",
+ L"",
+ 258 + m_fields.size()
+ );
+
+ core::rect<s32> rect;
+ if (!clip) {
+ // no auto_clip => position like normal image
+ rect = core::rect<s32>(pos, pos + geom);
+ } else {
+ // it will be auto-clipped when drawing
+ rect = core::rect<s32>(-pos, pos);
+ }
+
+ GUIBackgroundImage *e = new GUIBackgroundImage(Environment, this, spec.fid,
+ rect, name, middle, m_tsrc, clip);
+
+ FATAL_ERROR_IF(!e, "Failed to create background formspec element");
+ e->setNotClipped(true);
+
+ e->setVisible(false); // the element is drawn manually before all others
+
+ m_backgrounds.push_back(e);
+ m_fields.push_back(spec);
return;
}
errorstream<< "Invalid background element(" << parts.size() << "): '" << element << "'" << std::endl;
@@ -875,10 +1116,10 @@ void GUIFormSpecMenu::parseTable(parserData* data, const std::string &element)
v2s32 geom;
if (data->real_coordinates) {
- pos = getRealCoordinateBasePos(false, v_pos);
+ pos = getRealCoordinateBasePos(v_pos);
geom = getRealCoordinateGeometry(v_geom);
} else {
- pos = getElementBasePos(false, &v_pos);
+ pos = getElementBasePos(&v_pos);
geom.X = stof(v_geom[0]) * spacing.X;
geom.Y = stof(v_geom[1]) * spacing.Y;
}
@@ -889,7 +1130,7 @@ void GUIFormSpecMenu::parseTable(parserData* data, const std::string &element)
name,
L"",
L"",
- 258+m_fields.size()
+ 258 + m_fields.size()
);
spec.ftype = f_Table;
@@ -899,8 +1140,7 @@ void GUIFormSpecMenu::parseTable(parserData* data, const std::string &element)
}
//now really show table
- GUITable *e = new GUITable(Environment, this, spec.fid, rect,
- m_tsrc);
+ GUITable *e = new GUITable(Environment, this, spec.fid, rect, m_tsrc);
if (spec.fname == data->focused_fieldname) {
Environment->setFocus(e);
@@ -952,10 +1192,10 @@ void GUIFormSpecMenu::parseTextList(parserData* data, const std::string &element
v2s32 geom;
if (data->real_coordinates) {
- pos = getRealCoordinateBasePos(false, v_pos);
+ pos = getRealCoordinateBasePos(v_pos);
geom = getRealCoordinateGeometry(v_geom);
} else {
- pos = getElementBasePos(false, &v_pos);
+ pos = getElementBasePos(&v_pos);
geom.X = stof(v_geom[0]) * spacing.X;
geom.Y = stof(v_geom[1]) * spacing.Y;
}
@@ -966,7 +1206,7 @@ void GUIFormSpecMenu::parseTextList(parserData* data, const std::string &element
name,
L"",
L"",
- 258+m_fields.size()
+ 258 + m_fields.size()
);
spec.ftype = f_Table;
@@ -976,8 +1216,7 @@ void GUIFormSpecMenu::parseTextList(parserData* data, const std::string &element
}
//now really show list
- GUITable *e = new GUITable(Environment, this, spec.fid, rect,
- m_tsrc);
+ GUITable *e = new GUITable(Environment, this, spec.fid, rect, m_tsrc);
if (spec.fname == data->focused_fieldname) {
Environment->setFocus(e);
@@ -1030,11 +1269,11 @@ void GUIFormSpecMenu::parseDropDown(parserData* data, const std::string &element
MY_CHECKGEOM("dropdown",1);
- pos = getRealCoordinateBasePos(false, v_pos);
+ pos = getRealCoordinateBasePos(v_pos);
geom = getRealCoordinateGeometry(v_geom);
rect = core::rect<s32>(pos.X, pos.Y, pos.X+geom.X, pos.Y+geom.Y);
} else {
- pos = getElementBasePos(false, &v_pos);
+ pos = getElementBasePos(&v_pos);
s32 width = stof(parts[1]) * spacing.Y;
@@ -1046,14 +1285,14 @@ void GUIFormSpecMenu::parseDropDown(parserData* data, const std::string &element
name,
L"",
L"",
- 258+m_fields.size()
+ 258 + m_fields.size()
);
spec.ftype = f_DropDown;
spec.send = true;
//now really show list
- gui::IGUIComboBox *e = Environment->addComboBox(rect, this,spec.fid);
+ gui::IGUIComboBox *e = Environment->addComboBox(rect, this, spec.fid);
if (spec.fname == data->focused_fieldname) {
Environment->setFocus(e);
@@ -1097,8 +1336,8 @@ void GUIFormSpecMenu::parsePwdField(parserData* data, const std::string &element
{
std::vector<std::string> parts = split(element,';');
- if ((parts.size() == 4) || (parts.size() == 5) ||
- ((parts.size() > 5) && (m_formspec_version > FORMSPEC_API_VERSION)))
+ if ((parts.size() == 4) ||
+ ((parts.size() > 4) && (m_formspec_version > FORMSPEC_API_VERSION)))
{
std::vector<std::string> v_pos = split(parts[0],',');
std::vector<std::string> v_geom = split(parts[1],',');
@@ -1112,10 +1351,10 @@ void GUIFormSpecMenu::parsePwdField(parserData* data, const std::string &element
v2s32 geom;
if (data->real_coordinates) {
- pos = getRealCoordinateBasePos(false, v_pos);
+ pos = getRealCoordinateBasePos(v_pos);
geom = getRealCoordinateGeometry(v_geom);
} else {
- pos = getElementBasePos(false, &v_pos);
+ pos = getElementBasePos(&v_pos);
pos -= padding;
geom.X = (stof(v_geom[0]) * spacing.X) - (spacing.X - imgsize.X);
@@ -1133,7 +1372,9 @@ void GUIFormSpecMenu::parsePwdField(parserData* data, const std::string &element
name,
wlabel,
L"",
- 258+m_fields.size()
+ 258 + m_fields.size(),
+ 0,
+ ECI_IBEAM
);
spec.send = true;
@@ -1167,12 +1408,8 @@ void GUIFormSpecMenu::parsePwdField(parserData* data, const std::string &element
evt.KeyInput.PressedDown = true;
e->OnEvent(evt);
- if (parts.size() >= 5) {
- // TODO: remove after 2016-11-03
- warningstream << "pwdfield: use field_close_on_enter[name, enabled]" <<
- " instead of the 5th param" << std::endl;
- field_close_on_enter[name] = is_yes(parts[4]);
- }
+ // Note: Before 5.2.0 "parts.size() >= 5" resulted in a
+ // warning referring to field_close_on_enter[]!
m_fields.push_back(spec);
return;
@@ -1187,7 +1424,7 @@ void GUIFormSpecMenu::createTextField(parserData *data, FieldSpec &spec,
if (!is_editable && !is_multiline) {
// spec field id to 0, this stops submit searching for a value that isn't there
gui::StaticText::add(Environment, spec.flabel.c_str(), rect, false, true,
- this, spec.fid);
+ this, spec.fid);
return;
}
@@ -1204,20 +1441,21 @@ void GUIFormSpecMenu::createTextField(parserData *data, FieldSpec &spec,
IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR < 9;
if (use_intl_edit_box && g_settings->getBool("freetype")) {
- e = new gui::intlGUIEditBox(spec.fdefault.c_str(),
- true, Environment, this, spec.fid, rect, is_editable, is_multiline);
- e->drop();
+ e = new gui::intlGUIEditBox(spec.fdefault.c_str(), true, Environment,
+ this, spec.fid, rect, is_editable, is_multiline);
} else {
if (is_multiline) {
e = new GUIEditBoxWithScrollBar(spec.fdefault.c_str(), true,
- Environment, this, spec.fid, rect, is_editable, true);
- e->drop();
+ Environment, this, spec.fid, rect, is_editable, true);
} else if (is_editable) {
- e = Environment->addEditBox(spec.fdefault.c_str(), rect, true,
- this, spec.fid);
+ e = Environment->addEditBox(spec.fdefault.c_str(), rect, true, this,
+ spec.fid);
+ e->grab();
}
}
+ auto style = getStyleForElement(is_multiline ? "textarea" : "field", spec.fname);
+
if (e) {
if (is_editable && spec.fname == data->focused_fieldname)
Environment->setFocus(e);
@@ -1237,26 +1475,30 @@ void GUIFormSpecMenu::createTextField(parserData *data, FieldSpec &spec,
e->OnEvent(evt);
}
- auto style = getStyleForElement(is_multiline ? "textarea" : "field", spec.fname);
e->setNotClipped(style.getBool(StyleSpec::NOCLIP, false));
e->setDrawBorder(style.getBool(StyleSpec::BORDER, true));
e->setOverrideColor(style.getColor(StyleSpec::TEXTCOLOR, video::SColor(0xFFFFFFFF)));
if (style.get(StyleSpec::BGCOLOR, "") == "transparent") {
e->setDrawBackground(false);
}
+
+ e->drop();
}
if (!spec.flabel.empty()) {
int font_height = g_fontengine->getTextHeight();
rect.UpperLeftCorner.Y -= font_height;
rect.LowerRightCorner.Y = rect.UpperLeftCorner.Y + font_height;
- gui::StaticText::add(Environment, spec.flabel.c_str(), rect, false, true,
- this, 0);
+ IGUIElement *t = gui::StaticText::add(Environment, spec.flabel.c_str(),
+ rect, false, true, this, 0);
+
+ if (t)
+ t->setNotClipped(style.getBool(StyleSpec::NOCLIP, false));
}
}
-void GUIFormSpecMenu::parseSimpleField(parserData* data,
- std::vector<std::string> &parts)
+void GUIFormSpecMenu::parseSimpleField(parserData *data,
+ std::vector<std::string> &parts)
{
std::string name = parts[0];
std::string label = parts[1];
@@ -1264,18 +1506,20 @@ void GUIFormSpecMenu::parseSimpleField(parserData* data,
core::rect<s32> rect;
- if(data->explicit_size)
- warningstream<<"invalid use of unpositioned \"field\" in inventory"<<std::endl;
+ if (data->explicit_size)
+ warningstream << "invalid use of unpositioned \"field\" in inventory" << std::endl;
- v2s32 pos = getElementBasePos(false, nullptr);
- pos.Y = ((m_fields.size()+2)*60);
+ v2s32 pos = getElementBasePos(nullptr);
+ pos.Y = (data->simple_field_count + 2) * 60;
v2s32 size = DesiredRect.getSize();
- rect = core::rect<s32>(size.X / 2 - 150, pos.Y,
- (size.X / 2 - 150) + 300, pos.Y + (m_btn_height*2));
+ rect = core::rect<s32>(
+ size.X / 2 - 150, pos.Y,
+ size.X / 2 - 150 + 300, pos.Y + m_btn_height * 2
+ );
- if(m_form_src)
+ if (m_form_src)
default_val = m_form_src->resolveText(default_val);
@@ -1285,25 +1529,21 @@ void GUIFormSpecMenu::parseSimpleField(parserData* data,
name,
wlabel,
utf8_to_wide(unescape_string(default_val)),
- 258+m_fields.size()
+ 258 + m_fields.size(),
+ 0,
+ ECI_IBEAM
);
createTextField(data, spec, rect, false);
- if (parts.size() >= 4) {
- // TODO: remove after 2016-11-03
- warningstream << "field/simple: use field_close_on_enter[name, enabled]" <<
- " instead of the 4th param" << std::endl;
- field_close_on_enter[name] = is_yes(parts[3]);
- }
-
m_fields.push_back(spec);
+
+ data->simple_field_count++;
}
void GUIFormSpecMenu::parseTextArea(parserData* data, std::vector<std::string>& parts,
const std::string &type)
{
-
std::vector<std::string> v_pos = split(parts[0],',');
std::vector<std::string> v_geom = split(parts[1],',');
std::string name = parts[2];
@@ -1317,10 +1557,10 @@ void GUIFormSpecMenu::parseTextArea(parserData* data, std::vector<std::string>&
v2s32 geom;
if (data->real_coordinates) {
- pos = getRealCoordinateBasePos(false, v_pos);
+ pos = getRealCoordinateBasePos(v_pos);
geom = getRealCoordinateGeometry(v_geom);
} else {
- pos = getElementBasePos(false, &v_pos);
+ pos = getElementBasePos(&v_pos);
pos -= padding;
geom.X = (stof(v_geom[0]) * spacing.X) - (spacing.X - imgsize.X);
@@ -1353,17 +1593,15 @@ void GUIFormSpecMenu::parseTextArea(parserData* data, std::vector<std::string>&
name,
wlabel,
utf8_to_wide(unescape_string(default_val)),
- 258+m_fields.size()
+ 258 + m_fields.size(),
+ 0,
+ ECI_IBEAM
);
createTextField(data, spec, rect, type == "textarea");
- if (parts.size() >= 6) {
- // TODO: remove after 2016-11-03
- warningstream << "field/textarea: use field_close_on_enter[name, enabled]" <<
- " instead of the 6th param" << std::endl;
- field_close_on_enter[name] = is_yes(parts[5]);
- }
+ // Note: Before 5.2.0 "parts.size() >= 6" resulted in a
+ // warning referring to field_close_on_enter[]!
m_fields.push_back(spec);
}
@@ -1378,8 +1616,8 @@ void GUIFormSpecMenu::parseField(parserData* data, const std::string &element,
return;
}
- if ((parts.size() == 5) || (parts.size() == 6) ||
- ((parts.size() > 6) && (m_formspec_version > FORMSPEC_API_VERSION)))
+ if ((parts.size() == 5) ||
+ ((parts.size() > 5) && (m_formspec_version > FORMSPEC_API_VERSION)))
{
parseTextArea(data,parts,type);
return;
@@ -1387,6 +1625,58 @@ void GUIFormSpecMenu::parseField(parserData* data, const std::string &element,
errorstream<< "Invalid field element(" << parts.size() << "): '" << element << "'" << std::endl;
}
+void GUIFormSpecMenu::parseHyperText(parserData *data, const std::string &element)
+{
+ std::vector<std::string> parts = split(element, ';');
+
+ if (parts.size() != 4 && m_formspec_version < FORMSPEC_API_VERSION) {
+ errorstream << "Invalid text element(" << parts.size() << "): '" << element << "'" << std::endl;
+ return;
+ }
+
+ std::vector<std::string> v_pos = split(parts[0], ',');
+ std::vector<std::string> v_geom = split(parts[1], ',');
+ std::string name = parts[2];
+ std::string text = parts[3];
+
+ MY_CHECKPOS("hypertext", 0);
+ MY_CHECKGEOM("hypertext", 1);
+
+ v2s32 pos;
+ v2s32 geom;
+
+ if (data->real_coordinates) {
+ pos = getRealCoordinateBasePos(v_pos);
+ geom = getRealCoordinateGeometry(v_geom);
+ } else {
+ pos = getElementBasePos(&v_pos);
+ pos -= padding;
+
+ geom.X = (stof(v_geom[0]) * spacing.X) - (spacing.X - imgsize.X);
+ geom.Y = (stof(v_geom[1]) * (float)imgsize.Y) - (spacing.Y - imgsize.Y);
+ pos.Y += m_btn_height;
+ }
+
+ core::rect<s32> rect = core::rect<s32>(pos.X, pos.Y, pos.X + geom.X, pos.Y + geom.Y);
+
+ if(m_form_src)
+ text = m_form_src->resolveText(text);
+
+ FieldSpec spec(
+ name,
+ utf8_to_wide(unescape_string(text)),
+ L"",
+ 258 + m_fields.size()
+ );
+
+ spec.ftype = f_HyperText;
+ GUIHyperText *e = new GUIHyperText(spec.flabel.c_str(), Environment, this,
+ spec.fid, rect, m_client, m_tsrc);
+ e->drop();
+
+ m_fields.push_back(spec);
+}
+
void GUIFormSpecMenu::parseLabel(parserData* data, const std::string &element)
{
std::vector<std::string> parts = split(element,';');
@@ -1418,7 +1708,7 @@ void GUIFormSpecMenu::parseLabel(parserData* data, const std::string &element)
// easily without sacrificing good line distance. If
// it was one whole imgsize, it would have too much
// spacing.
- v2s32 pos = getRealCoordinateBasePos(false, v_pos);
+ v2s32 pos = getRealCoordinateBasePos(v_pos);
// Labels are positioned by their center, not their top.
pos.Y += (((float) imgsize.Y) / -2) + (((float) imgsize.Y) * i / 2);
@@ -1439,7 +1729,7 @@ void GUIFormSpecMenu::parseLabel(parserData* data, const std::string &element)
// in the integer cases: 0.4 is not exactly
// representable in binary floating point.
- v2s32 pos = getElementBasePos(false, nullptr);
+ v2s32 pos = getElementBasePos(nullptr);
pos.X += stof(v_pos[0]) * spacing.X;
pos.Y += (stof(v_pos[1]) + 7.0f / 30.0f) * spacing.Y;
@@ -1455,10 +1745,11 @@ void GUIFormSpecMenu::parseLabel(parserData* data, const std::string &element)
"",
wlabel_colors,
L"",
- 258+m_fields.size()
+ 258 + m_fields.size(),
+ 4
);
gui::IGUIStaticText *e = gui::StaticText::add(Environment,
- spec.flabel.c_str(), rect, false, false, this, spec.fid);
+ spec.flabel.c_str(), rect, false, false, this, spec.fid);
e->setTextAlignment(gui::EGUIA_UPPERLEFT, gui::EGUIA_CENTER);
auto style = getStyleForElement("label", spec.fname);
@@ -1466,6 +1757,10 @@ void GUIFormSpecMenu::parseLabel(parserData* data, const std::string &element)
e->setOverrideColor(style.getColor(StyleSpec::TEXTCOLOR, video::SColor(0xFFFFFFFF)));
m_fields.push_back(spec);
+
+ // labels should let events through
+ e->grab();
+ m_clickthrough_elements.push_back(e);
}
return;
@@ -1491,7 +1786,7 @@ void GUIFormSpecMenu::parseVertLabel(parserData* data, const std::string &elemen
core::rect<s32> rect;
if (data->real_coordinates) {
- pos = getRealCoordinateBasePos(false, v_pos);
+ pos = getRealCoordinateBasePos(v_pos);
// Vertlabels are positioned by center, not left.
pos.X -= imgsize.X / 2;
@@ -1504,7 +1799,7 @@ void GUIFormSpecMenu::parseVertLabel(parserData* data, const std::string &elemen
(text.length() + 1));
} else {
- pos = getElementBasePos(false, &v_pos);
+ pos = getElementBasePos(&v_pos);
// As above, the length must be one longer. The width of
// the rect (15 pixels) seems rather arbitrary, but
@@ -1531,10 +1826,10 @@ void GUIFormSpecMenu::parseVertLabel(parserData* data, const std::string &elemen
"",
label,
L"",
- 258+m_fields.size()
+ 258 + m_fields.size()
);
gui::IGUIStaticText *e = gui::StaticText::add(Environment, spec.flabel.c_str(),
- rect, false, false, this, spec.fid);
+ rect, false, false, this, spec.fid);
e->setTextAlignment(gui::EGUIA_CENTER, gui::EGUIA_CENTER);
auto style = getStyleForElement("vertlabel", spec.fname, "label");
@@ -1542,6 +1837,10 @@ void GUIFormSpecMenu::parseVertLabel(parserData* data, const std::string &elemen
e->setOverrideColor(style.getColor(StyleSpec::TEXTCOLOR, video::SColor(0xFFFFFFFF)));
m_fields.push_back(spec);
+
+ // vertlabels should let events through
+ e->grab();
+ m_clickthrough_elements.push_back(e);
return;
}
errorstream<< "Invalid vertlabel element(" << parts.size() << "): '" << element << "'" << std::endl;
@@ -1583,10 +1882,10 @@ void GUIFormSpecMenu::parseImageButton(parserData* data, const std::string &elem
v2s32 geom;
if (data->real_coordinates) {
- pos = getRealCoordinateBasePos(false, v_pos);
+ pos = getRealCoordinateBasePos(v_pos);
geom = getRealCoordinateGeometry(v_geom);
} else {
- pos = getElementBasePos(false, &v_pos);
+ pos = getElementBasePos(&v_pos);
geom.X = (stof(v_geom[0]) * spacing.X) - (spacing.X - imgsize.X);
geom.Y = (stof(v_geom[1]) * spacing.Y) - (spacing.Y - imgsize.Y);
}
@@ -1606,40 +1905,39 @@ void GUIFormSpecMenu::parseImageButton(parserData* data, const std::string &elem
name,
wlabel,
utf8_to_wide(image_name),
- 258+m_fields.size()
+ 258 + m_fields.size()
);
spec.ftype = f_Button;
if (type == "image_button_exit")
spec.is_exit = true;
- video::ITexture *texture = 0;
- video::ITexture *pressed_texture = 0;
- texture = m_tsrc->getTexture(image_name);
- if (!pressed_image_name.empty())
- pressed_texture = m_tsrc->getTexture(pressed_image_name);
- else
- pressed_texture = texture;
-
- gui::IGUIButton *e = Environment->addButton(rect, this, spec.fid, spec.flabel.c_str());
+ GUIButtonImage *e = GUIButtonImage::addButton(Environment, rect, this, spec.fid, spec.flabel.c_str());
if (spec.fname == data->focused_fieldname) {
Environment->setFocus(e);
}
auto style = getStyleForElement("image_button", spec.fname);
+ e->setFromStyle(style, m_tsrc);
- e->setUseAlphaChannel(style.getBool(StyleSpec::ALPHA, true));
- e->setImage(guiScalingImageButton(
- Environment->getVideoDriver(), texture, geom.X, geom.Y));
- e->setPressedImage(guiScalingImageButton(
- Environment->getVideoDriver(), pressed_texture, geom.X, geom.Y));
+ // We explicitly handle these arguments *after* the style properties in
+ // order to override them if they are provided
+ if (!image_name.empty())
+ {
+ video::ITexture *texture = m_tsrc->getTexture(image_name);
+ e->setForegroundImage(guiScalingImageButton(
+ Environment->getVideoDriver(), texture, geom.X, geom.Y));
+ }
+ if (!pressed_image_name.empty()) {
+ video::ITexture *pressed_texture = m_tsrc->getTexture(pressed_image_name);
+ e->setPressedForegroundImage(guiScalingImageButton(
+ Environment->getVideoDriver(), pressed_texture, geom.X, geom.Y));
+ }
e->setScaleImage(true);
+
if (parts.size() >= 7) {
e->setNotClipped(noclip);
e->setDrawBorder(drawborder);
- } else {
- e->setNotClipped(style.getBool(StyleSpec::NOCLIP, false));
- e->setDrawBorder(style.getBool(StyleSpec::BORDER, true));
}
m_fields.push_back(spec);
@@ -1695,7 +1993,7 @@ void GUIFormSpecMenu::parseTabHeader(parserData* data, const std::string &elemen
name,
L"",
L"",
- 258+m_fields.size()
+ 258 + m_fields.size()
);
spec.ftype = f_TabHeader;
@@ -1704,7 +2002,7 @@ void GUIFormSpecMenu::parseTabHeader(parserData* data, const std::string &elemen
v2s32 geom;
if (data->real_coordinates) {
- pos = getRealCoordinateBasePos(false, v_pos);
+ pos = getRealCoordinateBasePos(v_pos);
geom = getRealCoordinateGeometry(v_geom);
pos.Y -= geom.Y; // TabHeader base pos is the bottom, not the top.
@@ -1789,10 +2087,10 @@ void GUIFormSpecMenu::parseItemImageButton(parserData* data, const std::string &
v2s32 geom;
if (data->real_coordinates) {
- pos = getRealCoordinateBasePos(false, v_pos);
+ pos = getRealCoordinateBasePos(v_pos);
geom = getRealCoordinateGeometry(v_geom);
} else {
- pos = getElementBasePos(false, &v_pos);
+ pos = getElementBasePos(&v_pos);
geom.X = (stof(v_geom[0]) * spacing.X) - (spacing.X - imgsize.X);
geom.Y = (stof(v_geom[1]) * spacing.Y) - (spacing.Y - imgsize.Y);
}
@@ -1811,35 +2109,28 @@ void GUIFormSpecMenu::parseItemImageButton(parserData* data, const std::string &
m_default_tooltip_bgcolor,
m_default_tooltip_color);
- FieldSpec spec(
+ // the spec for the button
+ FieldSpec spec_btn(
name,
utf8_to_wide(label),
utf8_to_wide(item_name),
- 258 + m_fields.size()
+ 258 + m_fields.size(),
+ 2
);
- gui::IGUIButton *e = Environment->addButton(rect, this, spec.fid, L"");
+ GUIButtonItemImage *e_btn = GUIButtonItemImage::addButton(Environment, rect, this, spec_btn.fid, spec_btn.flabel.c_str(), item_name, m_client);
- auto style = getStyleForElement("item_image_button", spec.fname, "image_button");
- e->setNotClipped(style.getBool(StyleSpec::NOCLIP, false));
- e->setDrawBorder(style.getBool(StyleSpec::BORDER, true));
+ auto style = getStyleForElement("item_image_button", spec_btn.fname, "image_button");
+ e_btn->setFromStyle(style, m_tsrc);
- if (spec.fname == data->focused_fieldname) {
- Environment->setFocus(e);
+ if (spec_btn.fname == data->focused_fieldname) {
+ Environment->setFocus(e_btn);
}
- spec.ftype = f_Button;
- rect+=data->basepos-padding;
- spec.rect=rect;
- m_fields.push_back(spec);
-
- if (data->real_coordinates)
- pos = getRealCoordinateBasePos(true, v_pos);
- else
- pos = getElementBasePos(true, &v_pos);
-
- m_itemimages.emplace_back("", item_name, e, pos, geom);
- m_static_texts.emplace_back(utf8_to_wide(label), rect, e);
+ spec_btn.ftype = f_Button;
+ rect += data->basepos-padding;
+ spec_btn.rect = rect;
+ m_fields.push_back(spec_btn);
return;
}
errorstream<< "Invalid ItemImagebutton element(" << parts.size() << "): '" << element << "'" << std::endl;
@@ -1862,10 +2153,10 @@ void GUIFormSpecMenu::parseBox(parserData* data, const std::string &element)
v2s32 geom;
if (data->real_coordinates) {
- pos = getRealCoordinateBasePos(true, v_pos);
+ pos = getRealCoordinateBasePos(v_pos);
geom = getRealCoordinateGeometry(v_geom);
} else {
- pos = getElementBasePos(true, &v_pos);
+ pos = getElementBasePos(&v_pos);
geom.X = stof(v_geom[0]) * spacing.X;
geom.Y = stof(v_geom[1]) * spacing.Y;
}
@@ -1873,11 +2164,27 @@ void GUIFormSpecMenu::parseBox(parserData* data, const std::string &element)
video::SColor tmp_color;
if (parseColorString(parts[2], tmp_color, false, 0x8C)) {
- BoxDrawSpec spec(pos, geom, tmp_color);
+ FieldSpec spec(
+ "",
+ L"",
+ L"",
+ 258 + m_fields.size(),
+ -2
+ );
+ spec.ftype = f_Box;
- m_boxes.push_back(spec);
- }
- else {
+ core::rect<s32> rect(pos, pos + geom);
+
+ GUIBox *e = new GUIBox(Environment, this, spec.fid, rect, tmp_color);
+
+ auto style = getStyleForElement("box", spec.fname);
+ e->setNotClipped(style.getBool(StyleSpec::NOCLIP, m_formspec_version < 3));
+
+ e->drop();
+
+ m_fields.push_back(spec);
+
+ } else {
errorstream<< "Invalid Box element(" << parts.size() << "): '" << element << "' INVALID COLOR" << std::endl;
}
return;
@@ -1888,21 +2195,36 @@ void GUIFormSpecMenu::parseBox(parserData* data, const std::string &element)
void GUIFormSpecMenu::parseBackgroundColor(parserData* data, const std::string &element)
{
std::vector<std::string> parts = split(element,';');
+ const u32 parameter_count = parts.size();
+
+ if ((parameter_count > 2 && m_formspec_version < 3) ||
+ (parameter_count > 3 && m_formspec_version <= FORMSPEC_API_VERSION)) {
+ errorstream << "Invalid bgcolor element(" << parameter_count << "): '"
+ << element << "'" << std::endl;
+ return;
+ }
- if (((parts.size() == 1) || (parts.size() == 2)) ||
- ((parts.size() > 2) && (m_formspec_version > FORMSPEC_API_VERSION))) {
+ // bgcolor
+ if (parameter_count >= 1 && parts[0] != "")
parseColorString(parts[0], m_bgcolor, false);
- if (parts.size() == 2) {
- std::string fullscreen = parts[1];
- m_bgfullscreen = is_yes(fullscreen);
+ // fullscreen
+ if (parameter_count >= 2) {
+ if (parts[1] == "both") {
+ m_bgnonfullscreen = true;
+ m_bgfullscreen = true;
+ } else if (parts[1] == "neither") {
+ m_bgnonfullscreen = false;
+ m_bgfullscreen = false;
+ } else if (parts[1] != "" || m_formspec_version < 3) {
+ m_bgfullscreen = is_yes(parts[1]);
+ m_bgnonfullscreen = !m_bgfullscreen;
}
-
- return;
}
- errorstream << "Invalid bgcolor element(" << parts.size() << "): '" << element << "'"
- << std::endl;
+ // fbgcolor
+ if (parameter_count >= 3 && parts[2] != "")
+ parseColorString(parts[2], m_fullscreen_bgcolor, false);
}
void GUIFormSpecMenu::parseListColors(parserData* data, const std::string &element)
@@ -1912,12 +2234,13 @@ void GUIFormSpecMenu::parseListColors(parserData* data, const std::string &eleme
if (((parts.size() == 2) || (parts.size() == 3) || (parts.size() == 5)) ||
((parts.size() > 5) && (m_formspec_version > FORMSPEC_API_VERSION)))
{
- parseColorString(parts[0], m_slotbg_n, false);
- parseColorString(parts[1], m_slotbg_h, false);
+ parseColorString(parts[0], data->inventorylist_options.slotbg_n, false);
+ parseColorString(parts[1], data->inventorylist_options.slotbg_h, false);
if (parts.size() >= 3) {
- if (parseColorString(parts[2], m_slotbordercolor, false)) {
- m_slotborder = true;
+ if (parseColorString(parts[2], data->inventorylist_options.slotbordercolor,
+ false)) {
+ data->inventorylist_options.slotborder = true;
}
}
if (parts.size() == 5) {
@@ -1928,6 +2251,14 @@ void GUIFormSpecMenu::parseListColors(parserData* data, const std::string &eleme
if (parseColorString(parts[4], tmp_color, false))
m_default_tooltip_color = tmp_color;
}
+
+ // update all already parsed inventorylists
+ for (GUIInventoryList *e : m_inventorylists) {
+ e->setSlotBGColors(data->inventorylist_options.slotbg_n,
+ data->inventorylist_options.slotbg_h);
+ e->setSlotBorders(data->inventorylist_options.slotborder,
+ data->inventorylist_options.slotbordercolor);
+ }
return;
}
errorstream<< "Invalid listcolors element(" << parts.size() << "): '" << element << "'" << std::endl;
@@ -1978,16 +2309,32 @@ void GUIFormSpecMenu::parseTooltip(parserData* data, const std::string &element)
v2s32 geom;
if (data->real_coordinates) {
- pos = getRealCoordinateBasePos(true, v_pos);
+ pos = getRealCoordinateBasePos(v_pos);
geom = getRealCoordinateGeometry(v_geom);
} else {
- pos = getElementBasePos(true, &v_pos);
+ pos = getElementBasePos(&v_pos);
geom.X = stof(v_geom[0]) * spacing.X;
geom.Y = stof(v_geom[1]) * spacing.Y;
}
- irr::core::rect<s32> rect(pos, pos + geom);
- m_tooltip_rects.emplace_back(rect, spec);
+ FieldSpec fieldspec(
+ "",
+ L"",
+ L"",
+ 258 + m_fields.size()
+ );
+
+ core::rect<s32> rect(pos, pos + geom);
+
+ gui::IGUIElement *e = new gui::IGUIElement(EGUIET_ELEMENT, Environment,
+ this, fieldspec.fid, rect);
+
+ // the element the rect tooltip is bound to should not block mouse-clicks
+ e->setVisible(false);
+
+ m_fields.push_back(fieldspec);
+ m_tooltip_rects.emplace_back(e, spec);
+
} else {
m_tooltips[parts[0]] = spec;
}
@@ -2034,7 +2381,7 @@ bool GUIFormSpecMenu::parseSizeDirect(parserData* data, const std::string &eleme
return false;
if (type == "invsize")
- log_deprecated("Deprecated formspec element \"invsize\" is used");
+ warningstream << "Deprecated formspec element \"invsize\" is used" << std::endl;
parseSize(data, description);
@@ -2120,13 +2467,6 @@ bool GUIFormSpecMenu::parseStyle(parserData *data, const std::string &element, b
return false;
}
- std::string selector = trim(parts[0]);
- if (selector.empty()) {
- errorstream << "Invalid style element (Selector required): '" << element
- << "'" << std::endl;
- return false;
- }
-
StyleSpec spec;
for (size_t i = 1; i < parts.size(); i++) {
@@ -2156,10 +2496,21 @@ bool GUIFormSpecMenu::parseStyle(parserData *data, const std::string &element, b
spec.set(prop, value);
}
- if (style_type) {
- theme_by_type[selector] |= spec;
- } else {
- theme_by_name[selector] |= spec;
+ std::vector<std::string> selectors = split(parts[0], ',');
+ for (size_t sel = 0; sel < selectors.size(); sel++) {
+ std::string selector = trim(selectors[sel]);
+
+ if (selector.empty()) {
+ errorstream << "Invalid style element (Empty selector): '" << element
+ << "'" << std::endl;
+ continue;
+ }
+
+ if (style_type) {
+ theme_by_type[selector] |= spec;
+ } else {
+ theme_by_name[selector] |= spec;
+ }
}
return true;
@@ -2223,6 +2574,11 @@ void GUIFormSpecMenu::parseElement(parserData* data, const std::string &element)
return;
}
+ if (type == "animated_image") {
+ parseAnimatedImage(data, description);
+ return;
+ }
+
if (type == "item_image") {
parseItemImage(data, description);
return;
@@ -2278,6 +2634,11 @@ void GUIFormSpecMenu::parseElement(parserData* data, const std::string &element)
return;
}
+ if (type == "hypertext") {
+ parseHyperText(data,description);
+ return;
+ }
+
if (type == "label") {
parseLabel(data,description);
return;
@@ -2343,6 +2704,11 @@ void GUIFormSpecMenu::parseElement(parserData* data, const std::string &element)
return;
}
+ if (type == "scrollbaroptions") {
+ parseScrollBarOptions(data, description);
+ return;
+ }
+
// Ignore others
infostream << "Unknown DrawSpec: type=" << type << ", data=\"" << description << "\""
<< std::endl;
@@ -2385,37 +2751,45 @@ void GUIFormSpecMenu::regenerateGui(v2u32 screensize)
// Remove children
removeChildren();
- for (auto &table_it : m_tables) {
+ for (auto &table_it : m_tables)
table_it.second->drop();
- }
+ for (auto &inventorylist_it : m_inventorylists)
+ inventorylist_it->drop();
+ for (auto &checkbox_it : m_checkboxes)
+ checkbox_it.second->drop();
+ for (auto &scrollbar_it : m_scrollbars)
+ scrollbar_it.second->drop();
+ for (auto &background_it : m_backgrounds)
+ background_it->drop();
+ for (auto &tooltip_rect_it : m_tooltip_rects)
+ tooltip_rect_it.first->drop();
+ for (auto &clickthrough_it : m_clickthrough_elements)
+ clickthrough_it->drop();
mydata.size= v2s32(100,100);
mydata.screensize = screensize;
mydata.offset = v2f32(0.5f, 0.5f);
mydata.anchor = v2f32(0.5f, 0.5f);
+ mydata.simple_field_count = 0;
// Base position of contents of form
mydata.basepos = getBasePos();
- /* Convert m_init_draw_spec to m_inventorylists */
-
m_inventorylists.clear();
- m_images.clear();
m_backgrounds.clear();
- m_itemimages.clear();
m_tables.clear();
m_checkboxes.clear();
m_scrollbars.clear();
m_fields.clear();
- m_boxes.clear();
m_tooltips.clear();
m_tooltip_rects.clear();
m_inventory_rings.clear();
- m_static_texts.clear();
m_dropdowns.clear();
theme_by_name.clear();
theme_by_type.clear();
+ m_clickthrough_elements.clear();
+ m_bgnonfullscreen = true;
m_bgfullscreen = false;
m_formspec_version = 1;
@@ -2440,15 +2814,9 @@ void GUIFormSpecMenu::regenerateGui(v2u32 screensize)
);
}
- m_slotbg_n = video::SColor(255,128,128,128);
- m_slotbg_h = video::SColor(255,192,192,192);
-
m_default_tooltip_bgcolor = video::SColor(255,110,130,60);
m_default_tooltip_color = video::SColor(255,255,255,255);
- m_slotbordercolor = video::SColor(200,0,0,0);
- m_slotborder = false;
-
// Add tooltip
{
assert(!m_tooltip_element);
@@ -2654,6 +3022,9 @@ void GUIFormSpecMenu::regenerateGui(v2u32 screensize)
pos_offset = v2f32();
+ // used for formspec versions < 3
+ core::list<IGUIElement *>::Iterator legacy_sort_start = Children.getLast();
+
if (enable_prepends) {
// Backup the coordinates so that prepends can use the coordinates of choice.
bool rc_backup = mydata.real_coordinates;
@@ -2664,6 +3035,14 @@ void GUIFormSpecMenu::regenerateGui(v2u32 screensize)
for (const auto &element : prepend_elements)
parseElement(&mydata, element);
+ // legacy sorting for formspec versions < 3
+ if (m_formspec_version >= 3)
+ // prepends do not need to be reordered
+ legacy_sort_start = Children.getLast();
+ else if (version_backup >= 3)
+ // only prepends elements have to be reordered
+ legacySortElements(legacy_sort_start);
+
m_formspec_version = version_backup;
mydata.real_coordinates = rc_backup; // Restore coordinates
}
@@ -2679,30 +3058,31 @@ void GUIFormSpecMenu::regenerateGui(v2u32 screensize)
// If there are fields without explicit size[], add a "Proceed"
// button and adjust size to fit all the fields.
- if (!m_fields.empty() && !mydata.explicit_size) {
+ if (mydata.simple_field_count > 0 && !mydata.explicit_size) {
mydata.rect = core::rect<s32>(
- mydata.screensize.X/2 - 580/2,
- mydata.screensize.Y/2 - 300/2,
- mydata.screensize.X/2 + 580/2,
- mydata.screensize.Y/2 + 240/2+(m_fields.size()*60)
+ mydata.screensize.X / 2 - 580 / 2,
+ mydata.screensize.Y / 2 - 300 / 2,
+ mydata.screensize.X / 2 + 580 / 2,
+ mydata.screensize.Y / 2 + 240 / 2 + mydata.simple_field_count * 60
);
+
DesiredRect = mydata.rect;
recalculateAbsolutePosition(false);
mydata.basepos = getBasePos();
{
v2s32 pos = mydata.basepos;
- pos.Y = ((m_fields.size()+2)*60);
+ pos.Y = (mydata.simple_field_count + 2) * 60;
v2s32 size = DesiredRect.getSize();
- mydata.rect =
- core::rect<s32>(size.X/2-70, pos.Y,
- (size.X/2-70)+140, pos.Y + (m_btn_height*2));
+ mydata.rect = core::rect<s32>(
+ size.X / 2 - 70, pos.Y,
+ size.X / 2 - 70 + 140, pos.Y + m_btn_height * 2
+ );
const wchar_t *text = wgettext("Proceed");
- Environment->addButton(mydata.rect, this, 257, text);
+ GUIButton::addButton(Environment, mydata.rect, this, 257, text);
delete[] text;
}
-
}
//set initial focus if parser didn't set it
@@ -2713,6 +3093,51 @@ void GUIFormSpecMenu::regenerateGui(v2u32 screensize)
setInitialFocus();
skin->setFont(old_font);
+
+ // legacy sorting
+ if (m_formspec_version < 3)
+ legacySortElements(legacy_sort_start);
+}
+
+void GUIFormSpecMenu::legacySortElements(core::list<IGUIElement *>::Iterator from)
+{
+ /*
+ Draw order for formspec_version <= 2:
+ -3 bgcolor
+ -2 background
+ -1 box
+ 0 All other elements
+ 1 image
+ 2 item_image, item_image_button
+ 3 list
+ 4 label
+ */
+
+ if (from == Children.end())
+ from = Children.begin();
+ else
+ from++;
+
+ core::list<IGUIElement *>::Iterator to = Children.end();
+ // 1: Copy into a sortable container
+ std::vector<IGUIElement *> elements;
+ for (auto it = from; it != to; ++it)
+ elements.emplace_back(*it);
+
+ // 2: Sort the container
+ std::stable_sort(elements.begin(), elements.end(),
+ [this] (const IGUIElement *a, const IGUIElement *b) -> bool {
+ const FieldSpec *spec_a = getSpecByID(a->getID());
+ const FieldSpec *spec_b = getSpecByID(b->getID());
+ return spec_a && spec_b &&
+ spec_a->priority < spec_b->priority;
+ });
+
+ // 3: Re-assign the pointers
+ for (auto e : elements) {
+ *from = e;
+ from++;
+ }
}
#ifdef __ANDROID__
@@ -2724,13 +3149,13 @@ bool GUIFormSpecMenu::getAndroidUIInput()
std::string fieldname = m_jni_field_name;
m_jni_field_name.clear();
- for(std::vector<FieldSpec>::iterator iter = m_fields.begin();
+ for (std::vector<FieldSpec>::iterator iter = m_fields.begin();
iter != m_fields.end(); ++iter) {
if (iter->fname != fieldname) {
continue;
}
- IGUIElement* tochange = getElementFromId(iter->fid);
+ IGUIElement *tochange = getElementFromId(iter->fid, true);
if (tochange == 0) {
return false;
@@ -2748,135 +3173,18 @@ bool GUIFormSpecMenu::getAndroidUIInput()
}
#endif
-GUIFormSpecMenu::ItemSpec GUIFormSpecMenu::getItemAtPos(v2s32 p) const
+GUIInventoryList::ItemSpec GUIFormSpecMenu::getItemAtPos(v2s32 p) const
{
- core::rect<s32> imgrect(0,0,imgsize.X,imgsize.Y);
-
- for (const GUIFormSpecMenu::ListDrawSpec &s : m_inventorylists) {
- for(s32 i=0; i<s.geom.X*s.geom.Y; i++) {
- s32 item_i = i + s.start_item_i;
+ core::rect<s32> imgrect(0, 0, imgsize.X, imgsize.Y);
- s32 x;
- s32 y;
- if (s.real_coordinates) {
- x = (i%s.geom.X) * (imgsize.X * 1.25);
- y = (i/s.geom.X) * (imgsize.Y * 1.25);
- } else {
- x = (i%s.geom.X) * spacing.X;
- y = (i/s.geom.X) * spacing.Y;
- }
- v2s32 p0(x,y);
- core::rect<s32> rect = imgrect + s.pos + p0;
- if(rect.isPointInside(p))
- {
- return ItemSpec(s.inventoryloc, s.listname, item_i);
- }
- }
+ for (const GUIInventoryList *e : m_inventorylists) {
+ s32 item_index = e->getItemIndexAtPos(p);
+ if (item_index != -1)
+ return GUIInventoryList::ItemSpec(e->getInventoryloc(), e->getListname(),
+ item_index);
}
- return ItemSpec(InventoryLocation(), "", -1);
-}
-
-void GUIFormSpecMenu::drawList(const ListDrawSpec &s, int layer,
- bool &item_hovered)
-{
- video::IVideoDriver* driver = Environment->getVideoDriver();
-
- Inventory *inv = m_invmgr->getInventory(s.inventoryloc);
- if(!inv){
- warningstream<<"GUIFormSpecMenu::drawList(): "
- <<"The inventory location "
- <<"\""<<s.inventoryloc.dump()<<"\" doesn't exist"
- <<std::endl;
- return;
- }
- InventoryList *ilist = inv->getList(s.listname);
- if(!ilist){
- warningstream<<"GUIFormSpecMenu::drawList(): "
- <<"The inventory list \""<<s.listname<<"\" @ \""
- <<s.inventoryloc.dump()<<"\" doesn't exist"
- <<std::endl;
- return;
- }
-
- core::rect<s32> imgrect(0,0,imgsize.X,imgsize.Y);
-
- for (s32 i = 0; i < s.geom.X * s.geom.Y; i++) {
- s32 item_i = i + s.start_item_i;
- if (item_i >= (s32)ilist->getSize())
- break;
-
- s32 x;
- s32 y;
- if (s.real_coordinates) {
- x = (i%s.geom.X) * (imgsize.X * 1.25);
- y = (i/s.geom.X) * (imgsize.Y * 1.25);
- } else {
- x = (i%s.geom.X) * spacing.X;
- y = (i/s.geom.X) * spacing.Y;
- }
- v2s32 p(x,y);
- core::rect<s32> rect = imgrect + s.pos + p;
- ItemStack item = ilist->getItem(item_i);
-
- bool selected = m_selected_item
- && m_invmgr->getInventory(m_selected_item->inventoryloc) == inv
- && m_selected_item->listname == s.listname
- && m_selected_item->i == item_i;
- bool hovering = rect.isPointInside(m_pointer);
- ItemRotationKind rotation_kind = selected ? IT_ROT_SELECTED :
- (hovering ? IT_ROT_HOVERED : IT_ROT_NONE);
-
- if (layer == 0) {
- if (hovering) {
- item_hovered = true;
- driver->draw2DRectangle(m_slotbg_h, rect, &AbsoluteClippingRect);
- } else {
- driver->draw2DRectangle(m_slotbg_n, rect, &AbsoluteClippingRect);
- }
- }
-
- //Draw inv slot borders
- if (m_slotborder) {
- s32 x1 = rect.UpperLeftCorner.X;
- s32 y1 = rect.UpperLeftCorner.Y;
- s32 x2 = rect.LowerRightCorner.X;
- s32 y2 = rect.LowerRightCorner.Y;
- s32 border = 1;
- driver->draw2DRectangle(m_slotbordercolor,
- core::rect<s32>(v2s32(x1 - border, y1 - border),
- v2s32(x2 + border, y1)), NULL);
- driver->draw2DRectangle(m_slotbordercolor,
- core::rect<s32>(v2s32(x1 - border, y2),
- v2s32(x2 + border, y2 + border)), NULL);
- driver->draw2DRectangle(m_slotbordercolor,
- core::rect<s32>(v2s32(x1 - border, y1),
- v2s32(x1, y2)), NULL);
- driver->draw2DRectangle(m_slotbordercolor,
- core::rect<s32>(v2s32(x2, y1),
- v2s32(x2 + border, y2)), NULL);
- }
-
- if (layer == 1) {
- if (selected)
- item.takeItem(m_selected_amount);
-
- if (!item.empty()) {
- // Draw item stack
- drawItemStack(driver, m_font, item,
- rect, &AbsoluteClippingRect, m_client,
- rotation_kind);
- // Draw tooltip
- if (hovering && !m_selected_item) {
- std::string tooltip = item.getDescription(m_client->idef());
- if (m_tooltip_append_itemname)
- tooltip += "\n[" + item.name + "]";
- showTooltip(utf8_to_wide(tooltip), m_default_tooltip_color,
- m_default_tooltip_bgcolor);
- }
- }
- }
- }
+ return GUIInventoryList::ItemSpec(InventoryLocation(), "", -1);
}
void GUIFormSpecMenu::drawSelectedItem()
@@ -2884,9 +3192,10 @@ void GUIFormSpecMenu::drawSelectedItem()
video::IVideoDriver* driver = Environment->getVideoDriver();
if (!m_selected_item) {
+ // reset rotation time
drawItemStack(driver, m_font, ItemStack(),
- core::rect<s32>(v2s32(0, 0), v2s32(0, 0)),
- NULL, m_client, IT_ROT_DRAGGED);
+ core::rect<s32>(v2s32(0, 0), v2s32(0, 0)), NULL,
+ m_client, IT_ROT_DRAGGED);
return;
}
@@ -2918,22 +3227,31 @@ void GUIFormSpecMenu::drawMenu()
gui::IGUIFont *old_font = skin->getFont();
skin->setFont(m_font);
+ m_hovered_item_tooltips.clear();
+
updateSelectedItem();
video::IVideoDriver* driver = Environment->getVideoDriver();
+ /*
+ Draw background color
+ */
v2u32 screenSize = driver->getScreenSize();
core::rect<s32> allbg(0, 0, screenSize.X, screenSize.Y);
if (m_bgfullscreen)
driver->draw2DRectangle(m_fullscreen_bgcolor, allbg, &allbg);
- else
+ if (m_bgnonfullscreen)
driver->draw2DRectangle(m_bgcolor, AbsoluteRect, &AbsoluteClippingRect);
+ /*
+ Draw rect_mode tooltip
+ */
m_tooltip_element->setVisible(false);
for (const auto &pair : m_tooltip_rects) {
- if (pair.first.isPointInside(m_pointer)) {
+ const core::rect<s32> &rect = pair.first->getAbsoluteClippingRect();
+ if (rect.getArea() > 0 && rect.isPointInside(m_pointer)) {
const std::wstring &text = pair.second.tooltip;
if (!text.empty()) {
showTooltip(text, pair.second.color, pair.second.bgcolor);
@@ -2945,135 +3263,33 @@ void GUIFormSpecMenu::drawMenu()
/*
Draw backgrounds
*/
- for (const GUIFormSpecMenu::ImageDrawSpec &spec : m_backgrounds) {
- video::ITexture *texture = m_tsrc->getTexture(spec.name);
-
- if (texture != 0) {
- // Image size on screen
- core::rect<s32> imgrect(0, 0, spec.geom.X, spec.geom.Y);
- // Image rectangle on screen
- core::rect<s32> rect = imgrect + spec.pos;
- // Middle rect for 9-slicing
- core::rect<s32> middle = spec.middle;
-
- if (spec.clip) {
- core::dimension2d<s32> absrec_size = AbsoluteRect.getSize();
- rect = core::rect<s32>(AbsoluteRect.UpperLeftCorner.X - spec.pos.X,
- AbsoluteRect.UpperLeftCorner.Y - spec.pos.Y,
- AbsoluteRect.UpperLeftCorner.X + absrec_size.Width + spec.pos.X,
- AbsoluteRect.UpperLeftCorner.Y + absrec_size.Height + spec.pos.Y);
- }
-
- if (middle.getArea() == 0) {
- const video::SColor color(255, 255, 255, 255);
- const video::SColor colors[] = {color, color, color, color};
- draw2DImageFilterScaled(driver, texture, rect,
- core::rect<s32>(core::position2d<s32>(0, 0),
- core::dimension2di(texture->getOriginalSize())),
- NULL/*&AbsoluteClippingRect*/, colors, true);
- } else {
- // `-x` is interpreted as `w - x`
- if (middle.LowerRightCorner.X < 0) {
- middle.LowerRightCorner.X += texture->getOriginalSize().Width;
- }
- if (middle.LowerRightCorner.Y < 0) {
- middle.LowerRightCorner.Y += texture->getOriginalSize().Height;
- }
- draw2DImage9Slice(driver, texture, rect, middle);
- }
- } else {
- errorstream << "GUIFormSpecMenu::drawMenu() Draw backgrounds unable to load texture:" << std::endl;
- errorstream << "\t" << spec.name << std::endl;
- }
+ for (gui::IGUIElement *e : m_backgrounds) {
+ e->setVisible(true);
+ e->draw();
+ e->setVisible(false);
}
- /*
- Draw Boxes
- */
- for (const GUIFormSpecMenu::BoxDrawSpec &spec : m_boxes) {
- irr::video::SColor todraw = spec.color;
-
- core::rect<s32> rect(spec.pos.X,spec.pos.Y,
- spec.pos.X + spec.geom.X,spec.pos.Y + spec.geom.Y);
-
- driver->draw2DRectangle(todraw, rect, 0);
- }
+ // Some elements are only visible while being drawn
+ for (gui::IGUIElement *e : m_clickthrough_elements)
+ e->setVisible(true);
/*
Call base class
+ (This is where all the drawing happens.)
*/
gui::IGUIElement::draw();
- /*
- Draw images
- */
- for (const GUIFormSpecMenu::ImageDrawSpec &spec : m_images) {
- video::ITexture *texture = m_tsrc->getTexture(spec.name);
+ for (gui::IGUIElement *e : m_clickthrough_elements)
+ e->setVisible(false);
- if (texture != 0) {
- const core::dimension2d<u32>& img_origsize = texture->getOriginalSize();
- // Image size on screen
- core::rect<s32> imgrect;
-
- if (spec.scale)
- imgrect = core::rect<s32>(0,0,spec.geom.X, spec.geom.Y);
- else {
-
- imgrect = core::rect<s32>(0,0,img_origsize.Width,img_origsize.Height);
- }
- // Image rectangle on screen
- core::rect<s32> rect = imgrect + spec.pos;
- const video::SColor color(255,255,255,255);
- const video::SColor colors[] = {color,color,color,color};
- draw2DImageFilterScaled(driver, texture, rect,
- core::rect<s32>(core::position2d<s32>(0,0),img_origsize),
- NULL/*&AbsoluteClippingRect*/, colors, true);
- }
- else {
- errorstream << "GUIFormSpecMenu::drawMenu() Draw images unable to load texture:" << std::endl;
- errorstream << "\t" << spec.name << std::endl;
- }
+ // Draw hovered item tooltips
+ for (const std::string &tooltip : m_hovered_item_tooltips) {
+ showTooltip(utf8_to_wide(tooltip), m_default_tooltip_color,
+ m_default_tooltip_bgcolor);
}
- /*
- Draw item images
- */
- for (const GUIFormSpecMenu::ImageDrawSpec &spec : m_itemimages) {
- if (m_client == 0)
- break;
-
- IItemDefManager *idef = m_client->idef();
- ItemStack item;
- item.deSerialize(spec.item_name, idef);
- core::rect<s32> imgrect(0, 0, spec.geom.X, spec.geom.Y);
- // Viewport rectangle on screen
- core::rect<s32> rect = imgrect + spec.pos;
- if (spec.parent_button && spec.parent_button->isPressed()) {
-#if (IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR < 8)
- rect += core::dimension2d<s32>(
- 0.05 * (float)rect.getWidth(), 0.05 * (float)rect.getHeight());
-#else
- rect += core::dimension2d<s32>(
- skin->getSize(irr::gui::EGDS_BUTTON_PRESSED_IMAGE_OFFSET_X),
- skin->getSize(irr::gui::EGDS_BUTTON_PRESSED_IMAGE_OFFSET_Y));
-#endif
- }
- drawItemStack(driver, m_font, item, rect, &AbsoluteClippingRect,
- m_client, IT_ROT_NONE);
- }
-
- /*
- Draw items
- Layer 0: Item slot rectangles
- Layer 1: Item images; prepare tooltip
- */
- bool item_hovered = false;
- for (int layer = 0; layer < 2; layer++) {
- for (const GUIFormSpecMenu::ListDrawSpec &spec : m_inventorylists) {
- drawList(spec, layer, item_hovered);
- }
- }
- if (!item_hovered) {
+ if (m_hovered_item_tooltips.empty()) {
+ // reset rotation time
drawItemStack(driver, m_font, ItemStack(),
core::rect<s32>(v2s32(0, 0), v2s32(0, 0)),
NULL, m_client, IT_ROT_HOVERED);
@@ -3085,32 +3301,18 @@ void GUIFormSpecMenu::drawMenu()
#endif
/*
- Draw static text elements
- */
- for (const GUIFormSpecMenu::StaticTextSpec &spec : m_static_texts) {
- core::rect<s32> rect = spec.rect;
- if (spec.parent_button && spec.parent_button->isPressed()) {
-#if (IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR < 8)
- rect += core::dimension2d<s32>(
- 0.05 * (float)rect.getWidth(), 0.05 * (float)rect.getHeight());
-#else
- // Use image offset instead of text's because its a bit smaller
- // and fits better, also TEXT_OFFSET_X is always 0
- rect += core::dimension2d<s32>(
- skin->getSize(irr::gui::EGDS_BUTTON_PRESSED_IMAGE_OFFSET_X),
- skin->getSize(irr::gui::EGDS_BUTTON_PRESSED_IMAGE_OFFSET_Y));
-#endif
- }
- video::SColor color(255, 255, 255, 255);
- m_font->draw(spec.text.c_str(), rect, color, true, true, &rect);
- }
-
- /*
- Draw fields/buttons tooltips
+ Draw fields/buttons tooltips and update the mouse cursor
*/
gui::IGUIElement *hovered =
Environment->getRootGUIElement()->getElementFromPoint(m_pointer);
+#ifndef HAVE_TOUCHSCREENGUI
+ gui::ICursorControl *cursor_control = RenderingEngine::get_raw_device()->
+ getCursorControl();
+ gui::ECURSOR_ICON current_cursor_icon = cursor_control->getActiveIcon();
+#endif
+ bool hovered_element_found = false;
+
if (hovered != NULL) {
s32 id = hovered->getID();
@@ -3126,23 +3328,41 @@ void GUIFormSpecMenu::drawMenu()
}
}
- // Find and update the current tooltip
- if (id != -1 && delta >= m_tooltip_show_delay) {
+ // Find and update the current tooltip and cursor icon
+ if (id != -1) {
for (const FieldSpec &field : m_fields) {
if (field.fid != id)
continue;
- const std::wstring &text = m_tooltips[field.fname].tooltip;
- if (!text.empty())
- showTooltip(text, m_tooltips[field.fname].color,
- m_tooltips[field.fname].bgcolor);
+ if (delta >= m_tooltip_show_delay) {
+ const std::wstring &text = m_tooltips[field.fname].tooltip;
+ if (!text.empty())
+ showTooltip(text, m_tooltips[field.fname].color,
+ m_tooltips[field.fname].bgcolor);
+ }
+
+#ifndef HAVE_TOUCHSCREENGUI
+ if (field.ftype != f_HyperText && // Handled directly in guiHyperText
+ current_cursor_icon != field.fcursor_icon)
+ cursor_control->setActiveIcon(field.fcursor_icon);
+#endif
+
+ hovered_element_found = true;
break;
}
}
}
+ if (!hovered_element_found) {
+ // no element is hovered
+#ifndef HAVE_TOUCHSCREENGUI
+ if (current_cursor_icon != ECI_NORMAL)
+ cursor_control->setActiveIcon(ECI_NORMAL);
+#endif
+ }
+
m_tooltip_element->draw();
/*
@@ -3157,19 +3377,16 @@ void GUIFormSpecMenu::drawMenu()
void GUIFormSpecMenu::showTooltip(const std::wstring &text,
const irr::video::SColor &color, const irr::video::SColor &bgcolor)
{
- const std::wstring ntext = translate_string(text);
- m_tooltip_element->setOverrideColor(color);
- m_tooltip_element->setBackgroundColor(bgcolor);
- setStaticText(m_tooltip_element, ntext.c_str());
+ EnrichedString ntext(text);
+ ntext.setDefaultColor(color);
+ ntext.setBackground(bgcolor);
+
+ setStaticText(m_tooltip_element, ntext);
// Tooltip size and offset
s32 tooltip_width = m_tooltip_element->getTextWidth() + m_btn_height;
-#if (IRRLICHT_VERSION_MAJOR <= 1 && IRRLICHT_VERSION_MINOR <= 8 && IRRLICHT_VERSION_REVISION < 2) || USE_FREETYPE == 1
- std::vector<std::wstring> text_rows = str_split(ntext, L'\n');
- s32 tooltip_height = m_tooltip_element->getTextHeight() * text_rows.size() + 5;
-#else
s32 tooltip_height = m_tooltip_element->getTextHeight() + 5;
-#endif
+
v2u32 screenSize = Environment->getVideoDriver()->getScreenSize();
int tooltip_offset_x = m_btn_height;
int tooltip_offset_y = m_btn_height;
@@ -3206,11 +3423,11 @@ void GUIFormSpecMenu::updateSelectedItem()
// If craftresult is nonempty and nothing else is selected, select it now.
if (!m_selected_item) {
- for (const GUIFormSpecMenu::ListDrawSpec &s : m_inventorylists) {
- if (s.listname != "craftpreview")
+ for (const GUIInventoryList *e : m_inventorylists) {
+ if (e->getListname() != "craftpreview")
continue;
- Inventory *inv = m_invmgr->getInventory(s.inventoryloc);
+ Inventory *inv = m_invmgr->getInventory(e->getInventoryloc());
if (!inv)
continue;
@@ -3224,8 +3441,8 @@ void GUIFormSpecMenu::updateSelectedItem()
continue;
// Grab selected item from the crafting result list
- m_selected_item = new ItemSpec;
- m_selected_item->inventoryloc = s.inventoryloc;
+ m_selected_item = new GUIInventoryList::ItemSpec;
+ m_selected_item->inventoryloc = e->getInventoryloc();
m_selected_item->listname = "craftresult";
m_selected_item->i = 0;
m_selected_amount = item.count;
@@ -3246,16 +3463,12 @@ ItemStack GUIFormSpecMenu::verifySelectedItem()
// If the selected stack has become smaller, adjust m_selected_amount.
// Return the selected stack.
- if(m_selected_item)
- {
- if(m_selected_item->isValid())
- {
+ if (m_selected_item) {
+ if (m_selected_item->isValid()) {
Inventory *inv = m_invmgr->getInventory(m_selected_item->inventoryloc);
- if(inv)
- {
+ if (inv) {
InventoryList *list = inv->getList(m_selected_item->listname);
- if(list && (u32) m_selected_item->i < list->getSize())
- {
+ if (list && (u32) m_selected_item->i < list->getSize()) {
ItemStack stack = list->getItem(m_selected_item->i);
if (!m_selected_swap.empty()) {
if (m_selected_swap.name == stack.name &&
@@ -3273,7 +3486,7 @@ ItemStack GUIFormSpecMenu::verifySelectedItem()
// selection was not valid
delete m_selected_item;
- m_selected_item = NULL;
+ m_selected_item = nullptr;
m_selected_amount = 0;
m_selected_dragging = false;
}
@@ -3322,7 +3535,7 @@ void GUIFormSpecMenu::acceptInput(FormspecQuitMode quitmode=quit_mode_no)
}
for (const GUIFormSpecMenu::FieldSpec &s : m_fields) {
- if(s.send) {
+ if (s.send) {
std::string name = s.fname;
if (s.ftype == f_Button) {
fields[name] = wide_to_utf8(s.flabel);
@@ -3331,14 +3544,17 @@ void GUIFormSpecMenu::acceptInput(FormspecQuitMode quitmode=quit_mode_no)
if (table) {
fields[name] = table->checkEvent();
}
- }
- else if(s.ftype == f_DropDown) {
- // no dynamic cast possible due to some distributions shipped
- // without rtti support in irrlicht
- IGUIElement * element = getElementFromId(s.fid);
+ } else if (s.ftype == f_DropDown) {
+ // No dynamic cast possible due to some distributions shipped
+ // without rtti support in Irrlicht
+ IGUIElement *element = getElementFromId(s.fid, true);
gui::IGUIComboBox *e = NULL;
if ((element) && (element->getType() == gui::EGUIET_COMBO_BOX)) {
- e = static_cast<gui::IGUIComboBox*>(element);
+ e = static_cast<gui::IGUIComboBox *>(element);
+ } else {
+ warningstream << "GUIFormSpecMenu::acceptInput: dropdown "
+ << "field without dropdown element" << std::endl;
+ continue;
}
s32 selected = e->getSelected();
if (selected >= 0) {
@@ -3348,12 +3564,11 @@ void GUIFormSpecMenu::acceptInput(FormspecQuitMode quitmode=quit_mode_no)
fields[name] = (*dropdown_values)[selected];
}
}
- }
- else if (s.ftype == f_TabHeader) {
- // no dynamic cast possible due to some distributions shipped
- // without rttzi support in irrlicht
- IGUIElement * element = getElementFromId(s.fid);
- gui::IGUITabControl *e = NULL;
+ } else if (s.ftype == f_TabHeader) {
+ // No dynamic cast possible due to some distributions shipped
+ // without rtti support in Irrlicht
+ IGUIElement *element = getElementFromId(s.fid, true);
+ gui::IGUITabControl *e = nullptr;
if ((element) && (element->getType() == gui::EGUIET_TAB_CONTROL)) {
e = static_cast<gui::IGUITabControl *>(element);
}
@@ -3363,12 +3578,11 @@ void GUIFormSpecMenu::acceptInput(FormspecQuitMode quitmode=quit_mode_no)
ss << (e->getActiveTab() +1);
fields[name] = ss.str();
}
- }
- else if (s.ftype == f_CheckBox) {
- // no dynamic cast possible due to some distributions shipped
- // without rtti support in irrlicht
- IGUIElement * element = getElementFromId(s.fid);
- gui::IGUICheckBox *e = NULL;
+ } else if (s.ftype == f_CheckBox) {
+ // No dynamic cast possible due to some distributions shipped
+ // without rtti support in Irrlicht
+ IGUIElement *element = getElementFromId(s.fid, true);
+ gui::IGUICheckBox *e = nullptr;
if ((element) && (element->getType() == gui::EGUIET_CHECK_BOX)) {
e = static_cast<gui::IGUICheckBox*>(element);
}
@@ -3379,17 +3593,15 @@ void GUIFormSpecMenu::acceptInput(FormspecQuitMode quitmode=quit_mode_no)
else
fields[name] = "false";
}
- }
- else if (s.ftype == f_ScrollBar) {
- // no dynamic cast possible due to some distributions shipped
- // without rtti support in irrlicht
- IGUIElement * element = getElementFromId(s.fid);
- gui::IGUIScrollBar *e = NULL;
- if ((element) && (element->getType() == gui::EGUIET_SCROLL_BAR)) {
- e = static_cast<gui::IGUIScrollBar*>(element);
- }
-
- if (e != 0) {
+ } else if (s.ftype == f_ScrollBar) {
+ // No dynamic cast possible due to some distributions shipped
+ // without rtti support in Irrlicht
+ IGUIElement *element = getElementFromId(s.fid, true);
+ GUIScrollBar *e = nullptr;
+ if (element && element->getType() == gui::EGUIET_ELEMENT)
+ e = static_cast<GUIScrollBar *>(element);
+
+ if (e) {
std::stringstream os;
os << e->getPos();
if (s.fdefault == L"Changed")
@@ -3397,13 +3609,20 @@ void GUIFormSpecMenu::acceptInput(FormspecQuitMode quitmode=quit_mode_no)
else
fields[name] = "VAL:" + os.str();
}
- }
- else
- {
- IGUIElement* e = getElementFromId(s.fid);
- if(e != NULL) {
+ } else if (s.ftype == f_AnimatedImage) {
+ // No dynamic cast possible due to some distributions shipped
+ // without rtti support in Irrlicht
+ IGUIElement *element = getElementFromId(s.fid, true);
+ GUIAnimatedImage *e = nullptr;
+ if (element && element->getType() == gui::EGUIET_ELEMENT)
+ e = static_cast<GUIAnimatedImage *>(element);
+
+ if (e)
+ fields[name] = std::to_string(e->getFrameIndex() + 1);
+ } else {
+ IGUIElement *e = getElementFromId(s.fid, true);
+ if (e)
fields[name] = wide_to_utf8(e->getText());
- }
}
}
}
@@ -3412,9 +3631,9 @@ void GUIFormSpecMenu::acceptInput(FormspecQuitMode quitmode=quit_mode_no)
}
}
-static bool isChild(gui::IGUIElement * tocheck, gui::IGUIElement * parent)
+static bool isChild(gui::IGUIElement *tocheck, gui::IGUIElement *parent)
{
- while(tocheck != NULL) {
+ while (tocheck) {
if (tocheck == parent) {
return true;
}
@@ -3451,8 +3670,8 @@ bool GUIFormSpecMenu::preprocessEvent(const SEvent& event)
}
// Fix Esc/Return key being eaten by checkboxen and tables
- if(event.EventType==EET_KEY_INPUT_EVENT) {
- KeyPress kp(event.KeyInput);
+ if (event.EventType == EET_KEY_INPUT_EVENT) {
+ KeyPress kp(event.KeyInput);
if (kp == EscapeKey || kp == CancelKey
|| kp == getKeySetting("keymap_inventory")
|| event.KeyInput.Key==KEY_RETURN) {
@@ -3467,9 +3686,11 @@ bool GUIFormSpecMenu::preprocessEvent(const SEvent& event)
}
}
}
- // Mouse wheel events: send to hovered element instead of focused
- if(event.EventType==EET_MOUSE_INPUT_EVENT
- && event.MouseInput.Event == EMIE_MOUSE_WHEEL) {
+ // Mouse wheel and move events: send to hovered element instead of focused
+ if (event.EventType == EET_MOUSE_INPUT_EVENT &&
+ (event.MouseInput.Event == EMIE_MOUSE_WHEEL ||
+ (event.MouseInput.Event == EMIE_MOUSE_MOVED &&
+ event.MouseInput.ButtonStates == 0))) {
s32 x = event.MouseInput.X;
s32 y = event.MouseInput.Y;
gui::IGUIElement *hovered =
@@ -3477,7 +3698,7 @@ bool GUIFormSpecMenu::preprocessEvent(const SEvent& event)
core::position2d<s32>(x, y));
if (hovered && isMyChild(hovered)) {
hovered->OnEvent(event);
- return true;
+ return event.MouseInput.Event == EMIE_MOUSE_WHEEL;
}
}
@@ -3490,7 +3711,7 @@ bool GUIFormSpecMenu::preprocessEvent(const SEvent& event)
if (event.MouseInput.Event == EMIE_LMOUSE_PRESSED_DOWN) {
m_old_tooltip_id = -1;
}
- if (!isChild(hovered,this)) {
+ if (!isChild(hovered, this)) {
if (DoubleClickDetection(event)) {
return true;
}
@@ -3661,7 +3882,7 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event)
m_old_tooltip_id = -1;
updateSelectedItem();
- ItemSpec s = getItemAtPos(m_pointer);
+ GUIInventoryList::ItemSpec s = getItemAtPos(m_pointer);
Inventory *inv_selected = NULL;
Inventory *inv_s = NULL;
@@ -3763,8 +3984,9 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event)
case BET_DOWN:
// Some mouse button has been pressed
- //infostream<<"Mouse button "<<button<<" pressed at p=("
- // <<p.X<<","<<p.Y<<")"<<std::endl;
+ //infostream << "Mouse button " << button << " pressed at p=("
+ // << event.MouseInput.X << "," << event.MouseInput.Y << ")"
+ // << std::endl;
m_selected_dragging = false;
@@ -3774,7 +3996,7 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event)
} else if (!m_selected_item) {
if (s_count && button != BET_WHEEL_UP) {
// Non-empty stack has been clicked: select or shift-move it
- m_selected_item = new ItemSpec(s);
+ m_selected_item = new GUIInventoryList::ItemSpec(s);
u32 count;
if (button == BET_RIGHT)
@@ -4002,7 +4224,7 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event)
// if there are no items selected or the selected item
// belongs to craftresult list, proceed with crafting
- if (m_selected_item == NULL ||
+ if (!m_selected_item ||
!m_selected_item->isValid() || m_selected_item->listname == "craftresult") {
assert(inv_s);
@@ -4020,14 +4242,14 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event)
if (m_selected_amount == 0) {
m_selected_swap.clear();
delete m_selected_item;
- m_selected_item = NULL;
+ m_selected_item = nullptr;
m_selected_amount = 0;
m_selected_dragging = false;
}
m_old_pointer = m_pointer;
}
- if (event.EventType == EET_GUI_EVENT) {
+ if (event.EventType == EET_GUI_EVENT) {
if (event.GUIEvent.EventType == gui::EGET_TAB_CHANGED
&& isVisible()) {
// find the element that was clicked
@@ -4054,9 +4276,9 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event)
(event.GUIEvent.EventType == gui::EGET_CHECKBOX_CHANGED) ||
(event.GUIEvent.EventType == gui::EGET_COMBO_BOX_CHANGED) ||
(event.GUIEvent.EventType == gui::EGET_SCROLL_BAR_CHANGED)) {
- unsigned int btn_id = event.GUIEvent.Caller->getID();
+ s32 caller_id = event.GUIEvent.Caller->getID();
- if (btn_id == 257) {
+ if (caller_id == 257) {
if (m_allowclose) {
acceptInput(quit_mode_accept);
quitMenu();
@@ -4072,8 +4294,11 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event)
for (GUIFormSpecMenu::FieldSpec &s : m_fields) {
// if its a button, set the send field so
// lua knows which button was pressed
- if ((s.ftype == f_Button || s.ftype == f_CheckBox) &&
- s.fid == event.GUIEvent.Caller->getID()) {
+
+ if (caller_id != s.fid)
+ continue;
+
+ if (s.ftype == f_Button || s.ftype == f_CheckBox) {
s.send = true;
if (s.is_exit) {
if (m_allowclose) {
@@ -4089,8 +4314,7 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event)
s.send = false;
return true;
- } else if ((s.ftype == f_DropDown) &&
- (s.fid == event.GUIEvent.Caller->getID())) {
+ } else if (s.ftype == f_DropDown) {
// only send the changed dropdown
for (GUIFormSpecMenu::FieldSpec &s2 : m_fields) {
if (s2.ftype == f_DropDown) {
@@ -4108,11 +4332,14 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event)
}
}
return true;
- } else if ((s.ftype == f_ScrollBar) &&
- (s.fid == event.GUIEvent.Caller->getID())) {
+ } else if (s.ftype == f_ScrollBar) {
s.fdefault = L"Changed";
acceptInput(quit_mode_no);
s.fdefault = L"";
+ } else if (s.ftype == f_Unknown || s.ftype == f_HyperText) {
+ s.send = true;
+ acceptInput();
+ s.send = false;
}
}
}
@@ -4175,13 +4402,22 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event)
std::string GUIFormSpecMenu::getNameByID(s32 id)
{
for (FieldSpec &spec : m_fields) {
- if (spec.fid == id) {
+ if (spec.fid == id)
return spec.fname;
- }
}
return "";
}
+
+const GUIFormSpecMenu::FieldSpec *GUIFormSpecMenu::getSpecByID(s32 id)
+{
+ for (FieldSpec &spec : m_fields) {
+ if (spec.fid == id)
+ return &spec;
+ }
+ return nullptr;
+}
+
/**
* get label of element by id
* @param id of element
@@ -4190,9 +4426,8 @@ std::string GUIFormSpecMenu::getNameByID(s32 id)
std::wstring GUIFormSpecMenu::getLabelByID(s32 id)
{
for (FieldSpec &spec : m_fields) {
- if (spec.fid == id) {
+ if (spec.fid == id)
return spec.flabel;
- }
}
return L"";
}
diff --git a/src/gui/guiFormSpecMenu.h b/src/gui/guiFormSpecMenu.h
index 46df0930c..17bfef205 100644
--- a/src/gui/guiFormSpecMenu.h
+++ b/src/gui/guiFormSpecMenu.h
@@ -26,6 +26,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "irrlichttypes_extrabloated.h"
#include "inventorymanager.h"
#include "modalMenu.h"
+#include "guiInventoryList.h"
#include "guiTable.h"
#include "network/networkprotocol.h"
#include "client/joystick_controller.h"
@@ -36,6 +37,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
class InventoryManager;
class ISimpleTextureSource;
class Client;
+class GUIScrollBar;
+class TexturePool;
typedef enum {
f_Button,
@@ -44,6 +47,10 @@ typedef enum {
f_CheckBox,
f_DropDown,
f_ScrollBar,
+ f_Box,
+ f_ItemImage,
+ f_HyperText,
+ f_AnimatedImage,
f_Unknown
} FormspecFieldType;
@@ -75,51 +82,6 @@ public:
class GUIFormSpecMenu : public GUIModalMenu
{
- struct ItemSpec
- {
- ItemSpec() = default;
-
- ItemSpec(const InventoryLocation &a_inventoryloc,
- const std::string &a_listname,
- s32 a_i) :
- inventoryloc(a_inventoryloc),
- listname(a_listname),
- i(a_i)
- {
- }
-
- bool isValid() const { return i != -1; }
-
- InventoryLocation inventoryloc;
- std::string listname;
- s32 i = -1;
- };
-
- struct ListDrawSpec
- {
- ListDrawSpec() = default;
-
- ListDrawSpec(const InventoryLocation &a_inventoryloc,
- const std::string &a_listname,
- v2s32 a_pos, v2s32 a_geom, s32 a_start_item_i,
- bool a_real_coordinates):
- inventoryloc(a_inventoryloc),
- listname(a_listname),
- pos(a_pos),
- geom(a_geom),
- start_item_i(a_start_item_i),
- real_coordinates(a_real_coordinates)
- {
- }
-
- InventoryLocation inventoryloc;
- std::string listname;
- v2s32 pos;
- v2s32 geom;
- s32 start_item_i;
- bool real_coordinates;
- };
-
struct ListRingSpec
{
ListRingSpec() = default;
@@ -135,121 +97,36 @@ class GUIFormSpecMenu : public GUIModalMenu
std::string listname;
};
- struct ImageDrawSpec
- {
- ImageDrawSpec():
- parent_button(NULL),
- clip(false)
- {
- }
-
- ImageDrawSpec(const std::string &a_name,
- const std::string &a_item_name,
- gui::IGUIButton *a_parent_button,
- const v2s32 &a_pos, const v2s32 &a_geom):
- name(a_name),
- item_name(a_item_name),
- parent_button(a_parent_button),
- pos(a_pos),
- geom(a_geom),
- scale(true),
- clip(false)
- {
- }
-
- ImageDrawSpec(const std::string &a_name,
- const std::string &a_item_name,
- const v2s32 &a_pos, const v2s32 &a_geom):
- name(a_name),
- item_name(a_item_name),
- parent_button(NULL),
- pos(a_pos),
- geom(a_geom),
- scale(true),
- clip(false)
- {
- }
-
- ImageDrawSpec(const std::string &a_name,
- const v2s32 &a_pos, const v2s32 &a_geom, bool clip=false):
- name(a_name),
- parent_button(NULL),
- pos(a_pos),
- geom(a_geom),
- scale(true),
- clip(clip)
- {
- }
-
- ImageDrawSpec(const std::string &a_name,
- const v2s32 &a_pos, const v2s32 &a_geom, const core::rect<s32> &middle, bool clip=false):
- name(a_name),
- parent_button(NULL),
- pos(a_pos),
- geom(a_geom),
- middle(middle),
- scale(true),
- clip(clip)
- {
- }
-
- ImageDrawSpec(const std::string &a_name,
- const v2s32 &a_pos):
- name(a_name),
- parent_button(NULL),
- pos(a_pos),
- scale(false),
- clip(false)
- {
- }
-
- std::string name;
- std::string item_name;
- gui::IGUIButton *parent_button;
- v2s32 pos;
- v2s32 geom;
- core::rect<s32> middle;
- bool scale;
- bool clip;
- };
-
struct FieldSpec
{
FieldSpec() = default;
FieldSpec(const std::string &name, const std::wstring &label,
- const std::wstring &default_text, int id) :
+ const std::wstring &default_text, s32 id, int priority = 0,
+ gui::ECURSOR_ICON cursor_icon = ECI_NORMAL) :
fname(name),
flabel(label),
fdefault(unescape_enriched(translate_string(default_text))),
fid(id),
send(false),
ftype(f_Unknown),
- is_exit(false)
+ is_exit(false),
+ priority(priority),
+ fcursor_icon(cursor_icon)
{
}
std::string fname;
std::wstring flabel;
std::wstring fdefault;
- int fid;
+ s32 fid;
bool send;
FormspecFieldType ftype;
bool is_exit;
+ // Draw priority for formspec version < 3
+ int priority;
core::rect<s32> rect;
- };
-
- struct BoxDrawSpec
- {
- BoxDrawSpec(v2s32 a_pos, v2s32 a_geom, irr::video::SColor a_color):
- pos(a_pos),
- geom(a_geom),
- color(a_color)
- {
- }
- v2s32 pos;
- v2s32 geom;
- irr::video::SColor color;
+ gui::ECURSOR_ICON fcursor_icon;
};
struct TooltipSpec
@@ -268,35 +145,6 @@ class GUIFormSpecMenu : public GUIModalMenu
irr::video::SColor color;
};
- struct StaticTextSpec
- {
- StaticTextSpec():
- parent_button(NULL)
- {
- }
-
- StaticTextSpec(const std::wstring &a_text,
- const core::rect<s32> &a_rect):
- text(a_text),
- rect(a_rect),
- parent_button(NULL)
- {
- }
-
- StaticTextSpec(const std::wstring &a_text,
- const core::rect<s32> &a_rect,
- gui::IGUIButton *a_parent_button):
- text(a_text),
- rect(a_rect),
- parent_button(a_parent_button)
- {
- }
-
- std::wstring text;
- core::rect<s32> rect;
- gui::IGUIButton *parent_button;
- };
-
public:
GUIFormSpecMenu(JoystickController *joystick,
gui::IGUIElement* parent, s32 id,
@@ -365,13 +213,37 @@ public:
m_focused_element = elementname;
}
+ Client *getClient() const
+ {
+ return m_client;
+ }
+
+ const GUIInventoryList::ItemSpec *getSelectedItem() const
+ {
+ return m_selected_item;
+ }
+
+ const u16 getSelectedAmount() const
+ {
+ return m_selected_amount;
+ }
+
+ bool doTooltipAppendItemname() const
+ {
+ return m_tooltip_append_itemname;
+ }
+
+ void addHoveredItemTooltip(const std::string &name)
+ {
+ m_hovered_item_tooltips.emplace_back(name);
+ }
+
/*
Remove and re-add (or reposition) stuff
*/
void regenerateGui(v2u32 screensize);
- ItemSpec getItemAtPos(v2s32 p) const;
- void drawList(const ListDrawSpec &s, int layer, bool &item_hovered);
+ GUIInventoryList::ItemSpec getItemAtPos(v2s32 p) const;
void drawSelectedItem();
void drawMenu();
void updateSelectedItem();
@@ -397,10 +269,9 @@ protected:
}
std::wstring getLabelByID(s32 id);
std::string getNameByID(s32 id);
- v2s32 getElementBasePos(bool absolute,
- const std::vector<std::string> *v_pos);
- v2s32 getRealCoordinateBasePos(bool absolute,
- const std::vector<std::string> &v_pos);
+ const FieldSpec *getSpecByID(s32 id);
+ v2s32 getElementBasePos(const std::vector<std::string> *v_pos);
+ v2s32 getRealCoordinateBasePos(const std::vector<std::string> &v_pos);
v2s32 getRealCoordinateGeometry(const std::vector<std::string> &v_geom);
std::unordered_map<std::string, StyleSpec> theme_by_type;
@@ -425,23 +296,20 @@ protected:
std::string m_formspec_prepend;
InventoryLocation m_current_inventory_location;
- std::vector<ListDrawSpec> m_inventorylists;
+ std::vector<GUIInventoryList *> m_inventorylists;
std::vector<ListRingSpec> m_inventory_rings;
- std::vector<ImageDrawSpec> m_backgrounds;
- std::vector<ImageDrawSpec> m_images;
- std::vector<ImageDrawSpec> m_itemimages;
- std::vector<BoxDrawSpec> m_boxes;
+ std::vector<gui::IGUIElement *> m_backgrounds;
std::unordered_map<std::string, bool> field_close_on_enter;
std::vector<FieldSpec> m_fields;
- std::vector<StaticTextSpec> m_static_texts;
- std::vector<std::pair<FieldSpec,GUITable*> > m_tables;
- std::vector<std::pair<FieldSpec,gui::IGUICheckBox*> > m_checkboxes;
+ std::vector<std::pair<FieldSpec, GUITable *>> m_tables;
+ std::vector<std::pair<FieldSpec, gui::IGUICheckBox *>> m_checkboxes;
std::map<std::string, TooltipSpec> m_tooltips;
- std::vector<std::pair<irr::core::rect<s32>, TooltipSpec>> m_tooltip_rects;
- std::vector<std::pair<FieldSpec,gui::IGUIScrollBar*> > m_scrollbars;
- std::vector<std::pair<FieldSpec, std::vector<std::string> > > m_dropdowns;
+ std::vector<std::pair<gui::IGUIElement *, TooltipSpec>> m_tooltip_rects;
+ std::vector<std::pair<FieldSpec, GUIScrollBar *>> m_scrollbars;
+ std::vector<std::pair<FieldSpec, std::vector<std::string>>> m_dropdowns;
+ std::vector<gui::IGUIElement *> m_clickthrough_elements;
- ItemSpec *m_selected_item = nullptr;
+ GUIInventoryList::ItemSpec *m_selected_item = nullptr;
u16 m_selected_amount = 0;
bool m_selected_dragging = false;
ItemStack m_selected_swap;
@@ -459,16 +327,14 @@ protected:
bool m_lock = false;
v2u32 m_lockscreensize;
+ bool m_bgnonfullscreen;
bool m_bgfullscreen;
- bool m_slotborder;
video::SColor m_bgcolor;
video::SColor m_fullscreen_bgcolor;
- video::SColor m_slotbg_n;
- video::SColor m_slotbg_h;
- video::SColor m_slotbordercolor;
video::SColor m_default_tooltip_bgcolor;
video::SColor m_default_tooltip_color;
+
private:
IFormSource *m_form_src;
TextDest *m_text_dst;
@@ -479,6 +345,7 @@ private:
typedef struct {
bool explicit_size;
bool real_coordinates;
+ u8 simple_field_count;
v2f invsize;
v2s32 size;
v2f32 offset;
@@ -489,6 +356,18 @@ private:
std::string focused_fieldname;
GUITable::TableOptions table_options;
GUITable::TableColumns table_columns;
+
+ GUIInventoryList::Options inventorylist_options;
+
+ struct {
+ s32 max = 1000;
+ s32 min = 0;
+ s32 small_step = 10;
+ s32 large_step = 100;
+ s32 thumb_size = 1;
+ GUIScrollBar::ArrowVisibility arrow_visiblity = GUIScrollBar::DEFAULT;
+ } scrollbar_options;
+
// used to restore table selection/scroll/treeview state
std::unordered_map<std::string, GUITable::DynamicData> table_dyndata;
} parserData;
@@ -502,6 +381,7 @@ private:
fs_key_pendig current_keys_pending;
std::string current_field_enter_pending = "";
+ std::vector<std::string> m_hovered_item_tooltips;
void parseElement(parserData* data, const std::string &element);
@@ -512,6 +392,7 @@ private:
void parseListRing(parserData* data, const std::string &element);
void parseCheckbox(parserData* data, const std::string &element);
void parseImage(parserData* data, const std::string &element);
+ void parseAnimatedImage(parserData *data, const std::string &element);
void parseItemImage(parserData* data, const std::string &element);
void parseButton(parserData* data, const std::string &element,
const std::string &typ);
@@ -529,6 +410,7 @@ private:
void parseSimpleField(parserData* data,std::vector<std::string> &parts);
void parseTextArea(parserData* data,std::vector<std::string>& parts,
const std::string &type);
+ void parseHyperText(parserData *data, const std::string &element);
void parseLabel(parserData* data, const std::string &element);
void parseVertLabel(parserData* data, const std::string &element);
void parseImageButton(parserData* data, const std::string &element,
@@ -542,6 +424,7 @@ private:
bool parseVersionDirect(const std::string &data);
bool parseSizeDirect(parserData* data, const std::string &element);
void parseScrollBar(parserData* data, const std::string &element);
+ void parseScrollBarOptions(parserData *data, const std::string &element);
bool parsePositionDirect(parserData *data, const std::string &element);
void parsePosition(parserData *data, const std::string &element);
bool parseAnchorDirect(parserData *data, const std::string &element);
@@ -554,6 +437,13 @@ private:
const irr::video::SColor &bgcolor);
/**
+ * In formspec version < 2 the elements were not ordered properly. Some element
+ * types were drawn before others.
+ * This function sorts the elements in the old order for backwards compatibility.
+ */
+ void legacySortElements(core::list<IGUIElement *>::Iterator from);
+
+ /**
* check if event is part of a double click
* @param event event to evaluate
* @return true/false if a doubleclick was detected
diff --git a/src/gui/guiHyperText.cpp b/src/gui/guiHyperText.cpp
new file mode 100644
index 000000000..e107b5a3e
--- /dev/null
+++ b/src/gui/guiHyperText.cpp
@@ -0,0 +1,1158 @@
+/*
+Minetest
+Copyright (C) 2019 EvicenceBKidscode / Pierre-Yves Rollo <dev@pyrollo.com>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "IGUIEnvironment.h"
+#include "IGUIElement.h"
+#include "guiScrollBar.h"
+#include "IGUIFont.h"
+#include <vector>
+#include <list>
+#include <unordered_map>
+using namespace irr::gui;
+#include "client/fontengine.h"
+#include <SColor.h>
+#include "client/tile.h"
+#include "IVideoDriver.h"
+#include "client/client.h"
+#include "client/renderingengine.h"
+#include "hud.h"
+#include "guiHyperText.h"
+#include "util/string.h"
+
+bool check_color(const std::string &str)
+{
+ irr::video::SColor color;
+ return parseColorString(str, color, false);
+}
+
+bool check_integer(const std::string &str)
+{
+ if (str.empty())
+ return false;
+
+ char *endptr = nullptr;
+ strtol(str.c_str(), &endptr, 10);
+
+ return *endptr == '\0';
+}
+
+// -----------------------------------------------------------------------------
+// ParsedText - A text parser
+
+void ParsedText::Element::setStyle(StyleList &style)
+{
+ this->underline = is_yes(style["underline"]);
+
+ video::SColor color;
+
+ if (parseColorString(style["color"], color, false))
+ this->color = color;
+ if (parseColorString(style["hovercolor"], color, false))
+ this->hovercolor = color;
+
+ unsigned int font_size = std::atoi(style["fontsize"].c_str());
+ FontMode font_mode = FM_Standard;
+ if (style["fontstyle"] == "mono")
+ font_mode = FM_Mono;
+
+ FontSpec spec(font_size, font_mode,
+ is_yes(style["bold"]), is_yes(style["italic"]));
+
+ // TODO: find a way to check font validity
+ // Build a new fontengine ?
+ this->font = g_fontengine->getFont(spec);
+
+ if (!this->font)
+ printf("No font found ! Size=%d, mode=%d, bold=%s, italic=%s\n",
+ font_size, font_mode, style["bold"].c_str(),
+ style["italic"].c_str());
+}
+
+void ParsedText::Paragraph::setStyle(StyleList &style)
+{
+ if (style["halign"] == "center")
+ this->halign = HALIGN_CENTER;
+ else if (style["halign"] == "right")
+ this->halign = HALIGN_RIGHT;
+ else if (style["halign"] == "justify")
+ this->halign = HALIGN_JUSTIFY;
+ else
+ this->halign = HALIGN_LEFT;
+}
+
+ParsedText::ParsedText(const wchar_t *text)
+{
+ // Default style
+ m_root_tag.name = "root";
+ m_root_tag.style["fontsize"] = "16";
+ m_root_tag.style["fontstyle"] = "normal";
+ m_root_tag.style["bold"] = "false";
+ m_root_tag.style["italic"] = "false";
+ m_root_tag.style["underline"] = "false";
+ m_root_tag.style["halign"] = "left";
+ m_root_tag.style["color"] = "#EEEEEE";
+ m_root_tag.style["hovercolor"] = "#FF0000";
+
+ m_active_tags.push_front(&m_root_tag);
+ m_style = m_root_tag.style;
+
+ // Default simple tags definitions
+ StyleList style;
+
+ style["color"] = "#0000FF";
+ style["underline"] = "true";
+ m_elementtags["action"] = style;
+ style.clear();
+
+ style["bold"] = "true";
+ m_elementtags["b"] = style;
+ style.clear();
+
+ style["italic"] = "true";
+ m_elementtags["i"] = style;
+ style.clear();
+
+ style["underline"] = "true";
+ m_elementtags["u"] = style;
+ style.clear();
+
+ style["fontstyle"] = "mono";
+ m_elementtags["mono"] = style;
+ style.clear();
+
+ style["fontsize"] = m_root_tag.style["fontsize"];
+ m_elementtags["normal"] = style;
+ style.clear();
+
+ style["fontsize"] = "24";
+ m_elementtags["big"] = style;
+ style.clear();
+
+ style["fontsize"] = "36";
+ m_elementtags["bigger"] = style;
+ style.clear();
+
+ style["halign"] = "center";
+ m_paragraphtags["center"] = style;
+ style.clear();
+
+ style["halign"] = "justify";
+ m_paragraphtags["justify"] = style;
+ style.clear();
+
+ style["halign"] = "left";
+ m_paragraphtags["left"] = style;
+ style.clear();
+
+ style["halign"] = "right";
+ m_paragraphtags["right"] = style;
+ style.clear();
+
+ m_element = NULL;
+ m_paragraph = NULL;
+ m_end_paragraph_reason = ER_NONE;
+
+ parse(text);
+}
+
+ParsedText::~ParsedText()
+{
+ for (auto &tag : m_not_root_tags)
+ delete tag;
+}
+
+void ParsedText::parse(const wchar_t *text)
+{
+ wchar_t c;
+ u32 cursor = 0;
+ bool escape = false;
+
+ while ((c = text[cursor]) != L'\0') {
+ cursor++;
+
+ if (c == L'\r') { // Mac or Windows breaks
+ if (text[cursor] == L'\n')
+ cursor++;
+ // If text has begun, don't skip empty line
+ if (m_paragraph) {
+ endParagraph(ER_NEWLINE);
+ enterElement(ELEMENT_SEPARATOR);
+ }
+ escape = false;
+ continue;
+ }
+
+ if (c == L'\n') { // Unix breaks
+ // If text has begun, don't skip empty line
+ if (m_paragraph) {
+ endParagraph(ER_NEWLINE);
+ enterElement(ELEMENT_SEPARATOR);
+ }
+ escape = false;
+ continue;
+ }
+
+ if (escape) {
+ escape = false;
+ pushChar(c);
+ continue;
+ }
+
+ if (c == L'\\') {
+ escape = true;
+ continue;
+ }
+
+ // Tag check
+ if (c == L'<') {
+ u32 newcursor = parseTag(text, cursor);
+ if (newcursor > 0) {
+ cursor = newcursor;
+ continue;
+ }
+ }
+
+ // Default behavior
+ pushChar(c);
+ }
+
+ endParagraph(ER_NONE);
+}
+
+void ParsedText::endElement()
+{
+ m_element = NULL;
+}
+
+void ParsedText::endParagraph(EndReason reason)
+{
+ if (!m_paragraph)
+ return;
+
+ EndReason previous = m_end_paragraph_reason;
+ m_end_paragraph_reason = reason;
+ if (m_empty_paragraph && (reason == ER_TAG ||
+ (reason == ER_NEWLINE && previous == ER_TAG))) {
+ // Ignore last empty paragraph
+ m_paragraph = nullptr;
+ m_paragraphs.pop_back();
+ return;
+ }
+ endElement();
+ m_paragraph = NULL;
+}
+
+void ParsedText::enterParagraph()
+{
+ if (!m_paragraph) {
+ m_paragraphs.emplace_back();
+ m_paragraph = &m_paragraphs.back();
+ m_paragraph->setStyle(m_style);
+ m_empty_paragraph = true;
+ }
+}
+
+void ParsedText::enterElement(ElementType type)
+{
+ enterParagraph();
+
+ if (!m_element || m_element->type != type) {
+ m_paragraph->elements.emplace_back();
+ m_element = &m_paragraph->elements.back();
+ m_element->type = type;
+ m_element->tags = m_active_tags;
+ m_element->setStyle(m_style);
+ }
+}
+
+void ParsedText::pushChar(wchar_t c)
+{
+ // New word if needed
+ if (c == L' ' || c == L'\t') {
+ if (!m_empty_paragraph)
+ enterElement(ELEMENT_SEPARATOR);
+ else
+ return;
+ } else {
+ m_empty_paragraph = false;
+ enterElement(ELEMENT_TEXT);
+ }
+ m_element->text += c;
+}
+
+ParsedText::Tag *ParsedText::newTag(const std::string &name, const AttrsList &attrs)
+{
+ endElement();
+ Tag *newtag = new Tag();
+ newtag->name = name;
+ newtag->attrs = attrs;
+ m_not_root_tags.push_back(newtag);
+ return newtag;
+}
+
+ParsedText::Tag *ParsedText::openTag(const std::string &name, const AttrsList &attrs)
+{
+ Tag *newtag = newTag(name, attrs);
+ m_active_tags.push_front(newtag);
+ return newtag;
+}
+
+bool ParsedText::closeTag(const std::string &name)
+{
+ bool found = false;
+ for (auto id = m_active_tags.begin(); id != m_active_tags.end(); ++id)
+ if ((*id)->name == name) {
+ m_active_tags.erase(id);
+ found = true;
+ break;
+ }
+ return found;
+}
+
+void ParsedText::parseGenericStyleAttr(
+ const std::string &name, const std::string &value, StyleList &style)
+{
+ // Color styles
+ if (name == "color" || name == "hovercolor") {
+ if (check_color(value))
+ style[name] = value;
+
+ // Boolean styles
+ } else if (name == "bold" || name == "italic" || name == "underline") {
+ style[name] = is_yes(value);
+
+ } else if (name == "size") {
+ if (check_integer(value))
+ style["fontsize"] = value;
+
+ } else if (name == "font") {
+ if (value == "mono" || value == "normal")
+ style["fontstyle"] = value;
+ }
+}
+
+void ParsedText::parseStyles(const AttrsList &attrs, StyleList &style)
+{
+ for (auto const &attr : attrs)
+ parseGenericStyleAttr(attr.first, attr.second, style);
+}
+
+void ParsedText::globalTag(const AttrsList &attrs)
+{
+ for (const auto &attr : attrs) {
+ // Only page level style
+ if (attr.first == "margin") {
+ if (check_integer(attr.second))
+ margin = stoi(attr.second.c_str());
+
+ } else if (attr.first == "valign") {
+ if (attr.second == "top")
+ valign = ParsedText::VALIGN_TOP;
+ else if (attr.second == "bottom")
+ valign = ParsedText::VALIGN_BOTTOM;
+ else if (attr.second == "middle")
+ valign = ParsedText::VALIGN_MIDDLE;
+ } else if (attr.first == "background") {
+ irr::video::SColor color;
+ if (attr.second == "none") {
+ background_type = BACKGROUND_NONE;
+ } else if (parseColorString(attr.second, color, false)) {
+ background_type = BACKGROUND_COLOR;
+ background_color = color;
+ }
+
+ // Inheriting styles
+
+ } else if (attr.first == "halign") {
+ if (attr.second == "left" || attr.second == "center" ||
+ attr.second == "right" ||
+ attr.second == "justify")
+ m_root_tag.style["halign"] = attr.second;
+
+ // Generic default styles
+
+ } else {
+ parseGenericStyleAttr(attr.first, attr.second, m_root_tag.style);
+ }
+ }
+}
+
+u32 ParsedText::parseTag(const wchar_t *text, u32 cursor)
+{
+ // Tag name
+ bool end = false;
+ std::string name = "";
+ wchar_t c = text[cursor];
+
+ if (c == L'/') {
+ end = true;
+ c = text[++cursor];
+ if (c == L'\0')
+ return 0;
+ }
+
+ while (c != ' ' && c != '>') {
+ name += c;
+ c = text[++cursor];
+ if (c == L'\0')
+ return 0;
+ }
+
+ // Tag attributes
+ AttrsList attrs;
+ while (c != L'>') {
+ std::string attr_name = "";
+ core::stringw attr_val = L"";
+
+ while (c == ' ') {
+ c = text[++cursor];
+ if (c == L'\0' || c == L'=')
+ return 0;
+ }
+
+ while (c != L' ' && c != L'=') {
+ attr_name += (char)c;
+ c = text[++cursor];
+ if (c == L'\0' || c == L'>')
+ return 0;
+ }
+
+ while (c == L' ') {
+ c = text[++cursor];
+ if (c == L'\0' || c == L'>')
+ return 0;
+ }
+
+ if (c != L'=')
+ return 0;
+
+ c = text[++cursor];
+
+ if (c == L'\0')
+ return 0;
+
+ while (c != L'>' && c != L' ') {
+ attr_val += c;
+ c = text[++cursor];
+ if (c == L'\0')
+ return 0;
+ }
+
+ attrs[attr_name] = stringw_to_utf8(attr_val);
+ }
+
+ ++cursor; // Last ">"
+
+ // Tag specific processing
+ StyleList style;
+
+ if (name == "global") {
+ if (end)
+ return 0;
+ globalTag(attrs);
+
+ } else if (name == "style") {
+ if (end) {
+ closeTag(name);
+ } else {
+ parseStyles(attrs, style);
+ openTag(name, attrs)->style = style;
+ }
+ endElement();
+ } else if (name == "img" || name == "item") {
+ if (end)
+ return 0;
+
+ // Name is a required attribute
+ if (!attrs.count("name"))
+ return 0;
+
+ // Rotate attribute is only for <item>
+ if (attrs.count("rotate") && name != "item")
+ return 0;
+
+ // Angle attribute is only for <item>
+ if (attrs.count("angle") && name != "item")
+ return 0;
+
+ // Ok, element can be created
+ newTag(name, attrs);
+
+ if (name == "img")
+ enterElement(ELEMENT_IMAGE);
+ else
+ enterElement(ELEMENT_ITEM);
+
+ m_element->text = utf8_to_stringw(attrs["name"]);
+
+ if (attrs.count("float")) {
+ if (attrs["float"] == "left")
+ m_element->floating = FLOAT_LEFT;
+ if (attrs["float"] == "right")
+ m_element->floating = FLOAT_RIGHT;
+ }
+
+ if (attrs.count("width")) {
+ int width = stoi(attrs["width"]);
+ if (width > 0)
+ m_element->dim.Width = width;
+ }
+
+ if (attrs.count("height")) {
+ int height = stoi(attrs["height"]);
+ if (height > 0)
+ m_element->dim.Height = height;
+ }
+
+ if (attrs.count("angle")) {
+ std::string str = attrs["angle"];
+ std::vector<std::string> parts = split(str, ',');
+ if (parts.size() == 3) {
+ m_element->angle = v3s16(
+ rangelim(stoi(parts[0]), -180, 180),
+ rangelim(stoi(parts[1]), -180, 180),
+ rangelim(stoi(parts[2]), -180, 180));
+ m_element->rotation = v3s16(0, 0, 0);
+ }
+ }
+
+ if (attrs.count("rotate")) {
+ if (attrs["rotate"] == "yes") {
+ m_element->rotation = v3s16(0, 100, 0);
+ } else {
+ std::string str = attrs["rotate"];
+ std::vector<std::string> parts = split(str, ',');
+ if (parts.size() == 3) {
+ m_element->rotation = v3s16 (
+ rangelim(stoi(parts[0]), -1000, 1000),
+ rangelim(stoi(parts[1]), -1000, 1000),
+ rangelim(stoi(parts[2]), -1000, 1000));
+ }
+ }
+ }
+
+ endElement();
+
+ } else if (name == "tag") {
+ // Required attributes
+ if (!attrs.count("name"))
+ return 0;
+
+ StyleList tagstyle;
+ parseStyles(attrs, tagstyle);
+
+ if (is_yes(attrs["paragraph"]))
+ m_paragraphtags[attrs["name"]] = tagstyle;
+ else
+ m_elementtags[attrs["name"]] = tagstyle;
+
+ } else if (name == "action") {
+ if (end) {
+ closeTag(name);
+ } else {
+ if (!attrs.count("name"))
+ return 0;
+ openTag(name, attrs)->style = m_elementtags["action"];
+ }
+
+ } else if (m_elementtags.count(name)) {
+ if (end) {
+ closeTag(name);
+ } else {
+ openTag(name, attrs)->style = m_elementtags[name];
+ }
+ endElement();
+
+ } else if (m_paragraphtags.count(name)) {
+ if (end) {
+ closeTag(name);
+ } else {
+ openTag(name, attrs)->style = m_paragraphtags[name];
+ }
+ endParagraph(ER_TAG);
+
+ } else
+ return 0; // Unknown tag
+
+ // Update styles accordingly
+ m_style.clear();
+ for (auto tag = m_active_tags.crbegin(); tag != m_active_tags.crend(); ++tag)
+ for (const auto &prop : (*tag)->style)
+ m_style[prop.first] = prop.second;
+
+ return cursor;
+}
+
+// -----------------------------------------------------------------------------
+// Text Drawer
+
+TextDrawer::TextDrawer(const wchar_t *text, Client *client,
+ gui::IGUIEnvironment *environment, ISimpleTextureSource *tsrc) :
+ m_text(text),
+ m_client(client), m_environment(environment)
+{
+ // Size all elements
+ for (auto &p : m_text.m_paragraphs) {
+ for (auto &e : p.elements) {
+ switch (e.type) {
+ case ParsedText::ELEMENT_SEPARATOR:
+ case ParsedText::ELEMENT_TEXT:
+ if (e.font) {
+ e.dim.Width = e.font->getDimension(e.text.c_str()).Width;
+ e.dim.Height = e.font->getDimension(L"Yy").Height;
+#if USE_FREETYPE
+ if (e.font->getType() == irr::gui::EGFT_CUSTOM) {
+ e.baseline = e.dim.Height - 1 -
+ ((irr::gui::CGUITTFont *)e.font)->getAscender() / 64;
+ }
+#endif
+ } else {
+ e.dim = {0, 0};
+ }
+ break;
+
+ case ParsedText::ELEMENT_IMAGE:
+ case ParsedText::ELEMENT_ITEM:
+ // Resize only non sized items
+ if (e.dim.Height != 0 && e.dim.Width != 0)
+ break;
+
+ // Default image and item size
+ core::dimension2d<u32> dim(80, 80);
+
+ if (e.type == ParsedText::ELEMENT_IMAGE) {
+ video::ITexture *texture =
+ m_client->getTextureSource()->
+ getTexture(stringw_to_utf8(e.text));
+ if (texture)
+ dim = texture->getOriginalSize();
+ }
+
+ if (e.dim.Height == 0)
+ if (e.dim.Width == 0)
+ e.dim = dim;
+ else
+ e.dim.Height = dim.Height * e.dim.Width /
+ dim.Width;
+ else
+ e.dim.Width = dim.Width * e.dim.Height /
+ dim.Height;
+ break;
+ }
+ }
+ }
+}
+
+// Get element at given coordinates. Coordinates are inner coordinates (starting
+// at 0,0).
+ParsedText::Element *TextDrawer::getElementAt(core::position2d<s32> pos)
+{
+ pos.Y -= m_voffset;
+ for (auto &p : m_text.m_paragraphs) {
+ for (auto &el : p.elements) {
+ core::rect<s32> rect(el.pos, el.dim);
+ if (rect.isPointInside(pos))
+ return &el;
+ }
+ }
+ return 0;
+}
+
+/*
+ This function places all elements according to given width. Elements have
+ been previously sized by constructor and will be later drawed by draw.
+ It may be called each time width changes and resulting height can be
+ retrieved using getHeight. See GUIHyperText constructor, it uses it once to
+ test if text fits in window and eventually another time if width is reduced
+ m_floating because of scrollbar added.
+*/
+void TextDrawer::place(const core::rect<s32> &dest_rect)
+{
+ m_floating.clear();
+ s32 y = 0;
+ s32 ymargin = m_text.margin;
+
+ // Iterator used :
+ // p - Current paragraph, walked only once
+ // el - Current element, walked only once
+ // e and f - local element and floating operators
+
+ for (auto &p : m_text.m_paragraphs) {
+ // Find and place floating stuff in paragraph
+ for (auto e = p.elements.begin(); e != p.elements.end(); ++e) {
+ if (e->floating != ParsedText::FLOAT_NONE) {
+ if (y)
+ e->pos.Y = y + std::max(ymargin, e->margin);
+ else
+ e->pos.Y = ymargin;
+
+ if (e->floating == ParsedText::FLOAT_LEFT)
+ e->pos.X = m_text.margin;
+ if (e->floating == ParsedText::FLOAT_RIGHT)
+ e->pos.X = dest_rect.getWidth() - e->dim.Width -
+ m_text.margin;
+
+ RectWithMargin floating;
+ floating.rect = core::rect<s32>(e->pos, e->dim);
+ floating.margin = e->margin;
+
+ m_floating.push_back(floating);
+ }
+ }
+
+ if (y)
+ y = y + std::max(ymargin, p.margin);
+
+ ymargin = p.margin;
+
+ // Place non floating stuff
+ std::vector<ParsedText::Element>::iterator el = p.elements.begin();
+
+ while (el != p.elements.end()) {
+ // Determine line width and y pos
+ s32 left, right;
+ s32 nexty = y;
+ do {
+ y = nexty;
+ nexty = 0;
+
+ // Inner left & right
+ left = m_text.margin;
+ right = dest_rect.getWidth() - m_text.margin;
+
+ for (const auto &f : m_floating) {
+ // Does floating rect intersect paragraph y line?
+ if (f.rect.UpperLeftCorner.Y - f.margin <= y &&
+ f.rect.LowerRightCorner.Y + f.margin >= y) {
+
+ // Next Y to try if no room left
+ if (!nexty || f.rect.LowerRightCorner.Y +
+ std::max(f.margin, p.margin) < nexty) {
+ nexty = f.rect.LowerRightCorner.Y +
+ std::max(f.margin, p.margin) + 1;
+ }
+
+ if (f.rect.UpperLeftCorner.X - f.margin <= left &&
+ f.rect.LowerRightCorner.X + f.margin < right) {
+ // float on left
+ if (f.rect.LowerRightCorner.X +
+ std::max(f.margin, p.margin) > left) {
+ left = f.rect.LowerRightCorner.X +
+ std::max(f.margin, p.margin);
+ }
+ } else if (f.rect.LowerRightCorner.X + f.margin >= right &&
+ f.rect.UpperLeftCorner.X - f.margin > left) {
+ // float on right
+ if (f.rect.UpperLeftCorner.X -
+ std::max(f.margin, p.margin) < right)
+ right = f.rect.UpperLeftCorner.X -
+ std::max(f.margin, p.margin);
+
+ } else if (f.rect.UpperLeftCorner.X - f.margin <= left &&
+ f.rect.LowerRightCorner.X + f.margin >= right) {
+ // float taking all space
+ left = right;
+ }
+ else
+ { // float in the middle -- should not occure yet, see that later
+ }
+ }
+ }
+ } while (nexty && right <= left);
+
+ u32 linewidth = right - left;
+ float x = left;
+
+ u32 charsheight = 0;
+ u32 charswidth = 0;
+ u32 wordcount = 0;
+
+ // Skip begining of line separators but include them in height
+ // computation.
+ while (el != p.elements.end() &&
+ el->type == ParsedText::ELEMENT_SEPARATOR) {
+ if (el->floating == ParsedText::FLOAT_NONE) {
+ el->drawwidth = 0;
+ if (charsheight < el->dim.Height)
+ charsheight = el->dim.Height;
+ }
+ el++;
+ }
+
+ std::vector<ParsedText::Element>::iterator linestart = el;
+ std::vector<ParsedText::Element>::iterator lineend = p.elements.end();
+
+ // First pass, find elements fitting into line
+ // (or at least one element)
+ while (el != p.elements.end() && (charswidth == 0 ||
+ charswidth + el->dim.Width <= linewidth)) {
+ if (el->floating == ParsedText::FLOAT_NONE) {
+ if (el->type != ParsedText::ELEMENT_SEPARATOR) {
+ lineend = el;
+ wordcount++;
+ }
+ charswidth += el->dim.Width;
+ if (charsheight < el->dim.Height)
+ charsheight = el->dim.Height;
+ }
+ el++;
+ }
+
+ // Empty line, nothing to place only go down line height
+ if (lineend == p.elements.end()) {
+ y += charsheight;
+ continue;
+ }
+
+ // Point to the first position outside line (may be end())
+ lineend++;
+
+ // Second pass, compute printable line width and adjustments
+ charswidth = 0;
+ s32 top = 0;
+ s32 bottom = 0;
+ for (auto e = linestart; e != lineend; ++e) {
+ if (e->floating == ParsedText::FLOAT_NONE) {
+ charswidth += e->dim.Width;
+ if (top < (s32)e->dim.Height - e->baseline)
+ top = e->dim.Height - e->baseline;
+ if (bottom < e->baseline)
+ bottom = e->baseline;
+ }
+ }
+
+ float extraspace = 0.f;
+
+ switch (p.halign) {
+ case ParsedText::HALIGN_CENTER:
+ x += (linewidth - charswidth) / 2.f;
+ break;
+ case ParsedText::HALIGN_JUSTIFY:
+ if (wordcount > 1 && // Justification only if at least two words
+ !(lineend == p.elements.end())) // Don't justify last line
+ extraspace = ((float)(linewidth - charswidth)) / (wordcount - 1);
+ break;
+ case ParsedText::HALIGN_RIGHT:
+ x += linewidth - charswidth;
+ break;
+ case ParsedText::HALIGN_LEFT:
+ break;
+ }
+
+ // Third pass, actually place everything
+ for (auto e = linestart; e != lineend; ++e) {
+ if (e->floating != ParsedText::FLOAT_NONE)
+ continue;
+
+ e->pos.X = x;
+ e->pos.Y = y;
+
+ switch (e->type) {
+ case ParsedText::ELEMENT_TEXT:
+ case ParsedText::ELEMENT_SEPARATOR:
+ e->pos.X = x;
+
+ // Align char baselines
+ e->pos.Y = y + top + e->baseline - e->dim.Height;
+
+ x += e->dim.Width;
+ if (e->type == ParsedText::ELEMENT_SEPARATOR)
+ x += extraspace;
+ break;
+
+ case ParsedText::ELEMENT_IMAGE:
+ case ParsedText::ELEMENT_ITEM:
+ x += e->dim.Width;
+ break;
+ }
+
+ // Draw width for separator can be different than element
+ // width. This will be important for char effects like
+ // underline.
+ e->drawwidth = x - e->pos.X;
+ }
+ y += charsheight;
+ } // Elements (actually lines)
+ } // Paragraph
+
+ // Check if float goes under paragraph
+ for (const auto &f : m_floating) {
+ if (f.rect.LowerRightCorner.Y >= y)
+ y = f.rect.LowerRightCorner.Y;
+ }
+
+ m_height = y + m_text.margin;
+ // Compute vertical offset according to vertical alignment
+ if (m_height < dest_rect.getHeight())
+ switch (m_text.valign) {
+ case ParsedText::VALIGN_BOTTOM:
+ m_voffset = dest_rect.getHeight() - m_height;
+ break;
+ case ParsedText::VALIGN_MIDDLE:
+ m_voffset = (dest_rect.getHeight() - m_height) / 2;
+ break;
+ case ParsedText::VALIGN_TOP:
+ default:
+ m_voffset = 0;
+ }
+ else
+ m_voffset = 0;
+}
+
+// Draw text in a rectangle with a given offset. Items are actually placed in
+// relative (to upper left corner) coordinates.
+void TextDrawer::draw(const core::rect<s32> &dest_rect,
+ const core::position2d<s32> &dest_offset)
+{
+ irr::video::IVideoDriver *driver = m_environment->getVideoDriver();
+ core::position2d<s32> offset = dest_rect.UpperLeftCorner + dest_offset;
+ offset.Y += m_voffset;
+
+ if (m_text.background_type == ParsedText::BACKGROUND_COLOR)
+ driver->draw2DRectangle(m_text.background_color, dest_rect);
+
+ for (auto &p : m_text.m_paragraphs) {
+ for (auto &el : p.elements) {
+ core::rect<s32> rect(el.pos + offset, el.dim);
+ if (!rect.isRectCollided(dest_rect))
+ continue;
+
+ switch (el.type) {
+ case ParsedText::ELEMENT_SEPARATOR:
+ case ParsedText::ELEMENT_TEXT: {
+ irr::video::SColor color = el.color;
+
+ for (auto tag : el.tags)
+ if (&(*tag) == m_hovertag)
+ color = el.hovercolor;
+
+ if (!el.font)
+ break;
+
+ if (el.type == ParsedText::ELEMENT_TEXT)
+ el.font->draw(el.text, rect, color, false, true,
+ &dest_rect);
+
+ if (el.underline && el.drawwidth) {
+ s32 linepos = el.pos.Y + offset.Y +
+ el.dim.Height - (el.baseline >> 1);
+
+ core::rect<s32> linerect(el.pos.X + offset.X,
+ linepos - (el.baseline >> 3) - 1,
+ el.pos.X + offset.X + el.drawwidth,
+ linepos + (el.baseline >> 3));
+
+ driver->draw2DRectangle(color, linerect, &dest_rect);
+ }
+ } break;
+
+ case ParsedText::ELEMENT_IMAGE: {
+ video::ITexture *texture =
+ m_client->getTextureSource()->getTexture(
+ stringw_to_utf8(el.text));
+ if (texture != 0)
+ m_environment->getVideoDriver()->draw2DImage(
+ texture, rect,
+ irr::core::rect<s32>(
+ core::position2d<s32>(0, 0),
+ texture->getOriginalSize()),
+ &dest_rect, 0, true);
+ } break;
+
+ case ParsedText::ELEMENT_ITEM: {
+ IItemDefManager *idef = m_client->idef();
+ ItemStack item;
+ item.deSerialize(stringw_to_utf8(el.text), idef);
+
+ drawItemStack(
+ m_environment->getVideoDriver(),
+ g_fontengine->getFont(), item, rect, &dest_rect,
+ m_client, IT_ROT_OTHER, el.angle, el.rotation
+ );
+ } break;
+ }
+ }
+ }
+}
+
+// -----------------------------------------------------------------------------
+// GUIHyperText - The formated text area formspec item
+
+//! constructor
+GUIHyperText::GUIHyperText(const wchar_t *text, IGUIEnvironment *environment,
+ IGUIElement *parent, s32 id, const core::rect<s32> &rectangle,
+ Client *client, ISimpleTextureSource *tsrc) :
+ IGUIElement(EGUIET_ELEMENT, environment, parent, id, rectangle),
+ m_client(client), m_vscrollbar(nullptr),
+ m_drawer(text, client, environment, tsrc), m_text_scrollpos(0, 0)
+{
+
+#ifdef _DEBUG
+ setDebugName("GUIHyperText");
+#endif
+
+ IGUISkin *skin = 0;
+ if (Environment)
+ skin = Environment->getSkin();
+
+ m_scrollbar_width = skin ? skin->getSize(gui::EGDS_SCROLLBAR_SIZE) : 16;
+
+ core::rect<s32> rect = irr::core::rect<s32>(
+ RelativeRect.getWidth() - m_scrollbar_width, 0,
+ RelativeRect.getWidth(), RelativeRect.getHeight());
+
+ m_vscrollbar = new GUIScrollBar(Environment, this, -1, rect, false, true);
+ m_vscrollbar->setVisible(false);
+}
+
+//! destructor
+GUIHyperText::~GUIHyperText()
+{
+ m_vscrollbar->remove();
+ m_vscrollbar->drop();
+}
+
+ParsedText::Element *GUIHyperText::getElementAt(s32 X, s32 Y)
+{
+ core::position2d<s32> pos{X, Y};
+ pos -= m_display_text_rect.UpperLeftCorner;
+ pos -= m_text_scrollpos;
+ return m_drawer.getElementAt(pos);
+}
+
+void GUIHyperText::checkHover(s32 X, s32 Y)
+{
+ m_drawer.m_hovertag = nullptr;
+
+ if (AbsoluteRect.isPointInside(core::position2d<s32>(X, Y))) {
+ ParsedText::Element *element = getElementAt(X, Y);
+
+ if (element) {
+ for (auto &tag : element->tags) {
+ if (tag->name == "action") {
+ m_drawer.m_hovertag = tag;
+ break;
+ }
+ }
+ }
+ }
+
+#ifndef HAVE_TOUCHSCREENGUI
+ if (m_drawer.m_hovertag)
+ RenderingEngine::get_raw_device()->getCursorControl()->setActiveIcon(
+ gui::ECI_HAND);
+ else
+ RenderingEngine::get_raw_device()->getCursorControl()->setActiveIcon(
+ gui::ECI_NORMAL);
+#endif
+}
+
+bool GUIHyperText::OnEvent(const SEvent &event)
+{
+ // Scroll bar
+ if (event.EventType == EET_GUI_EVENT &&
+ event.GUIEvent.EventType == EGET_SCROLL_BAR_CHANGED &&
+ event.GUIEvent.Caller == m_vscrollbar) {
+ m_text_scrollpos.Y = -m_vscrollbar->getPos();
+ }
+
+ // Reset hover if element left
+ if (event.EventType == EET_GUI_EVENT &&
+ event.GUIEvent.EventType == EGET_ELEMENT_LEFT) {
+ m_drawer.m_hovertag = nullptr;
+#ifndef HAVE_TOUCHSCREENGUI
+ gui::ICursorControl *cursor_control =
+ RenderingEngine::get_raw_device()->getCursorControl();
+ if (cursor_control->isVisible())
+ cursor_control->setActiveIcon(gui::ECI_NORMAL);
+#endif
+ }
+
+ if (event.EventType == EET_MOUSE_INPUT_EVENT) {
+ if (event.MouseInput.Event == EMIE_MOUSE_MOVED)
+ checkHover(event.MouseInput.X, event.MouseInput.Y);
+
+ if (event.MouseInput.Event == EMIE_MOUSE_WHEEL) {
+ m_vscrollbar->setPos(m_vscrollbar->getPos() -
+ event.MouseInput.Wheel * m_vscrollbar->getSmallStep());
+ m_text_scrollpos.Y = -m_vscrollbar->getPos();
+ m_drawer.draw(m_display_text_rect, m_text_scrollpos);
+ checkHover(event.MouseInput.X, event.MouseInput.Y);
+
+ } else if (event.MouseInput.Event == EMIE_LMOUSE_PRESSED_DOWN) {
+ ParsedText::Element *element = getElementAt(
+ event.MouseInput.X, event.MouseInput.Y);
+
+ if (element) {
+ for (auto &tag : element->tags) {
+ if (tag->name == "action") {
+ Text = core::stringw(L"action:") +
+ utf8_to_stringw(tag->attrs["name"]);
+ if (Parent) {
+ SEvent newEvent;
+ newEvent.EventType = EET_GUI_EVENT;
+ newEvent.GUIEvent.Caller = this;
+ newEvent.GUIEvent.Element = 0;
+ newEvent.GUIEvent.EventType = EGET_BUTTON_CLICKED;
+ Parent->OnEvent(newEvent);
+ }
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ return IGUIElement::OnEvent(event);
+}
+
+//! draws the element and its children
+void GUIHyperText::draw()
+{
+ if (!IsVisible)
+ return;
+
+ // Text
+ m_display_text_rect = AbsoluteRect;
+ m_drawer.place(m_display_text_rect);
+
+ // Show scrollbar if text overflow
+ if (m_drawer.getHeight() > m_display_text_rect.getHeight()) {
+ m_vscrollbar->setSmallStep(m_display_text_rect.getHeight() * 0.1f);
+ m_vscrollbar->setLargeStep(m_display_text_rect.getHeight() * 0.5f);
+ m_vscrollbar->setMax(m_drawer.getHeight() - m_display_text_rect.getHeight());
+
+ m_vscrollbar->setVisible(true);
+
+ m_vscrollbar->setPageSize(s32(m_drawer.getHeight()));
+
+ core::rect<s32> smaller_rect = m_display_text_rect;
+
+ smaller_rect.LowerRightCorner.X -= m_scrollbar_width;
+ m_drawer.place(smaller_rect);
+ } else {
+ m_vscrollbar->setMax(0);
+ m_vscrollbar->setPos(0);
+ m_vscrollbar->setVisible(false);
+ }
+ m_drawer.draw(m_display_text_rect, m_text_scrollpos);
+
+ // draw children
+ IGUIElement::draw();
+}
diff --git a/src/gui/guiHyperText.h b/src/gui/guiHyperText.h
new file mode 100644
index 000000000..c55f8a705
--- /dev/null
+++ b/src/gui/guiHyperText.h
@@ -0,0 +1,229 @@
+/*
+Minetest
+Copyright (C) 2019 EvicenceBKidscode / Pierre-Yves Rollo <dev@pyrollo.com>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#pragma once
+
+#include "config.h" // for USE_FREETYPE
+
+using namespace irr;
+
+class ISimpleTextureSource;
+class Client;
+
+#if USE_FREETYPE
+#include "irrlicht_changes/CGUITTFont.h"
+#endif
+
+class ParsedText
+{
+public:
+ ParsedText(const wchar_t *text);
+ ~ParsedText();
+
+ enum ElementType
+ {
+ ELEMENT_TEXT,
+ ELEMENT_SEPARATOR,
+ ELEMENT_IMAGE,
+ ELEMENT_ITEM
+ };
+
+ enum BackgroundType
+ {
+ BACKGROUND_NONE,
+ BACKGROUND_COLOR
+ };
+
+ enum FloatType
+ {
+ FLOAT_NONE,
+ FLOAT_RIGHT,
+ FLOAT_LEFT
+ };
+
+ enum HalignType
+ {
+ HALIGN_CENTER,
+ HALIGN_LEFT,
+ HALIGN_RIGHT,
+ HALIGN_JUSTIFY
+ };
+
+ enum ValignType
+ {
+ VALIGN_MIDDLE,
+ VALIGN_TOP,
+ VALIGN_BOTTOM
+ };
+
+ typedef std::unordered_map<std::string, std::string> StyleList;
+ typedef std::unordered_map<std::string, std::string> AttrsList;
+
+ struct Tag
+ {
+ std::string name;
+ AttrsList attrs;
+ StyleList style;
+ };
+
+ struct Element
+ {
+ std::list<Tag *> tags;
+ ElementType type;
+ core::stringw text = "";
+
+ core::dimension2d<u32> dim;
+ core::position2d<s32> pos;
+ s32 drawwidth;
+
+ FloatType floating = FLOAT_NONE;
+
+ ValignType valign;
+
+ gui::IGUIFont *font;
+
+ irr::video::SColor color;
+ irr::video::SColor hovercolor;
+ bool underline;
+
+ s32 baseline = 0;
+
+ // img & item specific attributes
+ std::string name;
+ v3s16 angle{0, 0, 0};
+ v3s16 rotation{0, 0, 0};
+
+ s32 margin = 10;
+
+ void setStyle(StyleList &style);
+ };
+
+ struct Paragraph
+ {
+ std::vector<Element> elements;
+ HalignType halign;
+ s32 margin = 10;
+
+ void setStyle(StyleList &style);
+ };
+
+ std::vector<Paragraph> m_paragraphs;
+
+ // Element style
+ s32 margin = 3;
+ ValignType valign = VALIGN_TOP;
+ BackgroundType background_type = BACKGROUND_NONE;
+ irr::video::SColor background_color;
+
+ Tag m_root_tag;
+
+protected:
+ typedef enum { ER_NONE, ER_TAG, ER_NEWLINE } EndReason;
+
+ // Parser functions
+ void enterElement(ElementType type);
+ void endElement();
+ void enterParagraph();
+ void endParagraph(EndReason reason);
+ void pushChar(wchar_t c);
+ ParsedText::Tag *newTag(const std::string &name, const AttrsList &attrs);
+ ParsedText::Tag *openTag(const std::string &name, const AttrsList &attrs);
+ bool closeTag(const std::string &name);
+ void parseGenericStyleAttr(const std::string &name, const std::string &value,
+ StyleList &style);
+ void parseStyles(const AttrsList &attrs, StyleList &style);
+ void globalTag(const ParsedText::AttrsList &attrs);
+ u32 parseTag(const wchar_t *text, u32 cursor);
+ void parse(const wchar_t *text);
+
+ std::unordered_map<std::string, StyleList> m_elementtags;
+ std::unordered_map<std::string, StyleList> m_paragraphtags;
+
+ std::vector<Tag *> m_not_root_tags;
+ std::list<Tag *> m_active_tags;
+
+ // Current values
+ StyleList m_style;
+ Element *m_element;
+ Paragraph *m_paragraph;
+ bool m_empty_paragraph;
+ EndReason m_end_paragraph_reason;
+};
+
+class TextDrawer
+{
+public:
+ TextDrawer(const wchar_t *text, Client *client, gui::IGUIEnvironment *environment,
+ ISimpleTextureSource *tsrc);
+
+ void place(const core::rect<s32> &dest_rect);
+ inline s32 getHeight() { return m_height; };
+ void draw(const core::rect<s32> &dest_rect,
+ const core::position2d<s32> &dest_offset);
+ ParsedText::Element *getElementAt(core::position2d<s32> pos);
+ ParsedText::Tag *m_hovertag;
+
+protected:
+ struct RectWithMargin
+ {
+ core::rect<s32> rect;
+ s32 margin;
+ };
+
+ ParsedText m_text;
+ Client *m_client;
+ gui::IGUIEnvironment *m_environment;
+ s32 m_height;
+ s32 m_voffset;
+ std::vector<RectWithMargin> m_floating;
+};
+
+class GUIHyperText : public gui::IGUIElement
+{
+public:
+ //! constructor
+ GUIHyperText(const wchar_t *text, gui::IGUIEnvironment *environment,
+ gui::IGUIElement *parent, s32 id,
+ const core::rect<s32> &rectangle, Client *client,
+ ISimpleTextureSource *tsrc);
+
+ //! destructor
+ virtual ~GUIHyperText();
+
+ //! draws the element and its children
+ virtual void draw();
+
+ core::dimension2du getTextDimension();
+
+ bool OnEvent(const SEvent &event);
+
+protected:
+ // GUI members
+ Client *m_client;
+ GUIScrollBar *m_vscrollbar;
+ TextDrawer m_drawer;
+
+ // Positioning
+ u32 m_scrollbar_width;
+ core::rect<s32> m_display_text_rect;
+ core::position2d<s32> m_text_scrollpos;
+
+ ParsedText::Element *getElementAt(s32 X, s32 Y);
+ void checkHover(s32 X, s32 Y);
+};
diff --git a/src/gui/guiInventoryList.cpp b/src/gui/guiInventoryList.cpp
new file mode 100644
index 000000000..536471229
--- /dev/null
+++ b/src/gui/guiInventoryList.cpp
@@ -0,0 +1,217 @@
+/*
+Minetest
+Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "guiInventoryList.h"
+#include "guiFormSpecMenu.h"
+#include "client/hud.h"
+#include "client/client.h"
+
+GUIInventoryList::GUIInventoryList(gui::IGUIEnvironment *env,
+ gui::IGUIElement *parent,
+ s32 id,
+ const core::rect<s32> &rectangle,
+ InventoryManager *invmgr,
+ const InventoryLocation &inventoryloc,
+ const std::string &listname,
+ const v2s32 &geom,
+ const s32 start_item_i,
+ const v2s32 &slot_size,
+ const v2f32 &slot_spacing,
+ GUIFormSpecMenu *fs_menu,
+ const Options &options,
+ gui::IGUIFont *font) :
+ gui::IGUIElement(gui::EGUIET_ELEMENT, env, parent, id, rectangle),
+ m_invmgr(invmgr),
+ m_inventoryloc(inventoryloc),
+ m_listname(listname),
+ m_geom(geom),
+ m_start_item_i(start_item_i),
+ m_slot_size(slot_size),
+ m_slot_spacing(slot_spacing),
+ m_fs_menu(fs_menu),
+ m_options(options),
+ m_font(font),
+ m_hovered_i(-1)
+{
+}
+
+void GUIInventoryList::draw()
+{
+ if (!IsVisible)
+ return;
+
+ Inventory *inv = m_invmgr->getInventory(m_inventoryloc);
+ if (!inv) {
+ warningstream << "GUIInventoryList::draw(): "
+ << "The inventory location "
+ << "\"" << m_inventoryloc.dump() << "\" doesn't exist anymore"
+ << std::endl;
+ return;
+ }
+ InventoryList *ilist = inv->getList(m_listname);
+ if (!ilist) {
+ warningstream << "GUIInventoryList::draw(): "
+ << "The inventory list \"" << m_listname << "\" @ \""
+ << m_inventoryloc.dump() << "\" doesn't exist anymore"
+ << std::endl;
+ return;
+ }
+
+ video::IVideoDriver *driver = Environment->getVideoDriver();
+ Client *client = m_fs_menu->getClient();
+ const ItemSpec *selected_item = m_fs_menu->getSelectedItem();
+
+ core::rect<s32> imgrect(0, 0, m_slot_size.X, m_slot_size.Y);
+ v2s32 base_pos = AbsoluteRect.UpperLeftCorner;
+
+ for (s32 i = 0; i < m_geom.X * m_geom.Y; i++) {
+ s32 item_i = i + m_start_item_i;
+ if (item_i >= (s32)ilist->getSize())
+ break;
+
+ v2s32 p((i % m_geom.X) * m_slot_spacing.X,
+ (i / m_geom.X) * m_slot_spacing.Y);
+ core::rect<s32> rect = imgrect + base_pos + p;
+ ItemStack item = ilist->getItem(item_i);
+
+ bool selected = selected_item
+ && m_invmgr->getInventory(selected_item->inventoryloc) == inv
+ && selected_item->listname == m_listname
+ && selected_item->i == item_i;
+ core::rect<s32> clipped_rect(rect);
+ clipped_rect.clipAgainst(AbsoluteClippingRect);
+ bool hovering = m_hovered_i == item_i;
+ ItemRotationKind rotation_kind = selected ? IT_ROT_SELECTED :
+ (hovering ? IT_ROT_HOVERED : IT_ROT_NONE);
+
+ // layer 0
+ if (hovering) {
+ driver->draw2DRectangle(m_options.slotbg_h, rect, &AbsoluteClippingRect);
+ } else {
+ driver->draw2DRectangle(m_options.slotbg_n, rect, &AbsoluteClippingRect);
+ }
+
+ // Draw inv slot borders
+ if (m_options.slotborder) {
+ s32 x1 = rect.UpperLeftCorner.X;
+ s32 y1 = rect.UpperLeftCorner.Y;
+ s32 x2 = rect.LowerRightCorner.X;
+ s32 y2 = rect.LowerRightCorner.Y;
+ s32 border = 1;
+ core::rect<s32> clipping_rect = Parent ? Parent->getAbsoluteClippingRect()
+ : core::rect<s32>();
+ core::rect<s32> *clipping_rect_ptr = Parent ? &clipping_rect : nullptr;
+ driver->draw2DRectangle(m_options.slotbordercolor,
+ core::rect<s32>(v2s32(x1 - border, y1 - border),
+ v2s32(x2 + border, y1)), clipping_rect_ptr);
+ driver->draw2DRectangle(m_options.slotbordercolor,
+ core::rect<s32>(v2s32(x1 - border, y2),
+ v2s32(x2 + border, y2 + border)), clipping_rect_ptr);
+ driver->draw2DRectangle(m_options.slotbordercolor,
+ core::rect<s32>(v2s32(x1 - border, y1),
+ v2s32(x1, y2)), clipping_rect_ptr);
+ driver->draw2DRectangle(m_options.slotbordercolor,
+ core::rect<s32>(v2s32(x2, y1),
+ v2s32(x2 + border, y2)), clipping_rect_ptr);
+ }
+
+ // layer 1
+ if (selected)
+ item.takeItem(m_fs_menu->getSelectedAmount());
+
+ if (!item.empty()) {
+ // Draw item stack
+ drawItemStack(driver, m_font, item, rect, &AbsoluteClippingRect,
+ client, rotation_kind);
+ // Add hovering tooltip
+ if (hovering && !selected_item) {
+ std::string tooltip = item.getDescription(client->idef());
+ if (m_fs_menu->doTooltipAppendItemname())
+ tooltip += "\n[" + item.name + "]";
+ m_fs_menu->addHoveredItemTooltip(tooltip);
+ }
+ }
+ }
+
+ IGUIElement::draw();
+}
+
+bool GUIInventoryList::OnEvent(const SEvent &event)
+{
+ if (event.EventType != EET_MOUSE_INPUT_EVENT) {
+ if (event.EventType == EET_GUI_EVENT &&
+ event.GUIEvent.EventType == EGET_ELEMENT_LEFT) {
+ // element is no longer hovered
+ m_hovered_i = -1;
+ }
+ return IGUIElement::OnEvent(event);
+ }
+
+ m_hovered_i = getItemIndexAtPos(v2s32(event.MouseInput.X, event.MouseInput.Y));
+
+ if (m_hovered_i != -1)
+ return IGUIElement::OnEvent(event);
+
+ // no item slot at pos of mouse event => allow clicking through
+ // find the element that would be hovered if this inventorylist was invisible
+ bool was_visible = IsVisible;
+ IsVisible = false;
+ IGUIElement *hovered =
+ Environment->getRootGUIElement()->getElementFromPoint(
+ core::position2d<s32>(event.MouseInput.X, event.MouseInput.Y));
+
+ // if the player clicks outside of the formspec window, hovered is not
+ // m_fs_menu, but some other weird element (with ID -1). we do however need
+ // hovered to be m_fs_menu as item dropping when clicking outside of the
+ // formspec window is handled in its OnEvent callback
+ if (!hovered || hovered->getID() == -1)
+ hovered = m_fs_menu;
+
+ bool ret = hovered->OnEvent(event);
+
+ IsVisible = was_visible;
+
+ return ret;
+}
+
+s32 GUIInventoryList::getItemIndexAtPos(v2s32 p) const
+{
+ if (!IsVisible || AbsoluteClippingRect.getArea() <= 0 ||
+ !AbsoluteClippingRect.isPointInside(p))
+ return -1;
+
+ core::rect<s32> imgrect(0, 0, m_slot_size.X, m_slot_size.Y);
+ v2s32 base_pos = AbsoluteRect.UpperLeftCorner;
+
+ // instead of looping through each slot, we look where p would be in the grid
+ s32 i = (p.X - base_pos.X) / (s32)m_slot_spacing.X
+ + m_geom.X * ((p.Y - base_pos.Y) / (s32)m_slot_spacing.Y);
+
+ v2s32 p0((i % m_geom.X) * m_slot_spacing.X,
+ (i / m_geom.X) * m_slot_spacing.Y);
+
+ core::rect<s32> rect = imgrect + base_pos + p0;
+
+ rect.clipAgainst(AbsoluteClippingRect);
+
+ if (rect.getArea() > 0 && rect.isPointInside(p))
+ return i + m_start_item_i;
+
+ return -1;
+}
diff --git a/src/gui/guiInventoryList.h b/src/gui/guiInventoryList.h
new file mode 100644
index 000000000..fd2c3601b
--- /dev/null
+++ b/src/gui/guiInventoryList.h
@@ -0,0 +1,130 @@
+/*
+Minetest
+Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#pragma once
+
+#include "inventorymanager.h"
+#include "irrlichttypes_extrabloated.h"
+#include "util/string.h"
+
+class GUIFormSpecMenu;
+
+class GUIInventoryList : public gui::IGUIElement
+{
+public:
+ struct ItemSpec
+ {
+ ItemSpec() = default;
+
+ ItemSpec(const InventoryLocation &a_inventoryloc,
+ const std::string &a_listname,
+ s32 a_i) :
+ inventoryloc(a_inventoryloc),
+ listname(a_listname),
+ i(a_i)
+ {
+ }
+
+ bool isValid() const { return i != -1; }
+
+ InventoryLocation inventoryloc;
+ std::string listname;
+ s32 i = -1;
+ };
+
+ // options for inventorylists that are setable with the lua api
+ struct Options {
+ // whether a one-pixel border for the slots should be drawn and its color
+ bool slotborder = false;
+ video::SColor slotbordercolor = video::SColor(200, 0, 0, 0);
+ // colors for normal and highlighted slot background
+ video::SColor slotbg_n = video::SColor(255, 128, 128, 128);
+ video::SColor slotbg_h = video::SColor(255, 192, 192, 192);
+ };
+
+ GUIInventoryList(gui::IGUIEnvironment *env,
+ gui::IGUIElement *parent,
+ s32 id,
+ const core::rect<s32> &rectangle,
+ InventoryManager *invmgr,
+ const InventoryLocation &inventoryloc,
+ const std::string &listname,
+ const v2s32 &geom,
+ const s32 start_item_i,
+ const v2s32 &slot_size,
+ const v2f32 &slot_spacing,
+ GUIFormSpecMenu *fs_menu,
+ const Options &options,
+ gui::IGUIFont *font);
+
+ virtual void draw() override;
+
+ virtual bool OnEvent(const SEvent &event) override;
+
+ const InventoryLocation &getInventoryloc() const
+ {
+ return m_inventoryloc;
+ }
+
+ const std::string &getListname() const
+ {
+ return m_listname;
+ }
+
+ void setSlotBGColors(const video::SColor &slotbg_n, const video::SColor &slotbg_h)
+ {
+ m_options.slotbg_n = slotbg_n;
+ m_options.slotbg_h = slotbg_h;
+ }
+
+ void setSlotBorders(bool slotborder, const video::SColor &slotbordercolor)
+ {
+ m_options.slotborder = slotborder;
+ m_options.slotbordercolor = slotbordercolor;
+ }
+
+ // returns -1 if not item is at pos p
+ s32 getItemIndexAtPos(v2s32 p) const;
+
+private:
+ InventoryManager *m_invmgr;
+ const InventoryLocation m_inventoryloc;
+ const std::string m_listname;
+
+ // specifies the width and height of the inventorylist in itemslots
+ const v2s32 m_geom;
+ // the first item's index in inventory
+ const s32 m_start_item_i;
+
+ // specifies how large the slot rects are
+ const v2s32 m_slot_size;
+ // specifies how large the space between slots is (space between is spacing-size)
+ const v2f32 m_slot_spacing;
+
+ // the GUIFormSpecMenu can have an item selected and co.
+ GUIFormSpecMenu *m_fs_menu;
+
+ Options m_options;
+
+ // the font
+ gui::IGUIFont *m_font;
+
+ // the index of the hovered item; -1 if no item is hovered
+ s32 m_hovered_i;
+};
diff --git a/src/gui/guiItemImage.cpp b/src/gui/guiItemImage.cpp
new file mode 100644
index 000000000..f93d5476c
--- /dev/null
+++ b/src/gui/guiItemImage.cpp
@@ -0,0 +1,52 @@
+/*
+Minetest
+Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "guiItemImage.h"
+#include "client/client.h"
+
+GUIItemImage::GUIItemImage(gui::IGUIEnvironment *env, gui::IGUIElement *parent,
+ s32 id, const core::rect<s32> &rectangle, const std::string &item_name,
+ gui::IGUIFont *font, Client *client) :
+ gui::IGUIElement(gui::EGUIET_ELEMENT, env, parent, id, rectangle),
+ m_item_name(item_name), m_font(font), m_client(client), m_label(core::stringw())
+{
+}
+
+void GUIItemImage::draw()
+{
+ if (!IsVisible)
+ return;
+
+ if (!m_client) {
+ IGUIElement::draw();
+ return;
+ }
+
+ IItemDefManager *idef = m_client->idef();
+ ItemStack item;
+ item.deSerialize(m_item_name, idef);
+ // Viewport rectangle on screen
+ core::rect<s32> rect = core::rect<s32>(AbsoluteRect);
+ drawItemStack(Environment->getVideoDriver(), m_font, item, rect,
+ &AbsoluteClippingRect, m_client, IT_ROT_NONE);
+ video::SColor color(255, 255, 255, 255);
+ m_font->draw(m_label, rect, color, true, true, &AbsoluteClippingRect);
+
+ IGUIElement::draw();
+}
diff --git a/src/gui/guiItemImage.h b/src/gui/guiItemImage.h
new file mode 100644
index 000000000..6fede6564
--- /dev/null
+++ b/src/gui/guiItemImage.h
@@ -0,0 +1,46 @@
+/*
+Minetest
+Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#pragma once
+
+#include "irrlichttypes_extrabloated.h"
+#include "util/string.h"
+
+class Client;
+
+class GUIItemImage : public gui::IGUIElement
+{
+public:
+ GUIItemImage(gui::IGUIEnvironment *env, gui::IGUIElement *parent, s32 id,
+ const core::rect<s32> &rectangle, const std::string &item_name,
+ gui::IGUIFont *font, Client *client);
+
+ virtual void draw() override;
+
+ virtual void setText(const wchar_t *text) override
+ {
+ m_label = text;
+ }
+
+private:
+ std::string m_item_name;
+ gui::IGUIFont *m_font;
+ Client *m_client;
+ core::stringw m_label;
+};
diff --git a/src/gui/guiKeyChangeMenu.cpp b/src/gui/guiKeyChangeMenu.cpp
index ca331a7d4..3f270fc7a 100644
--- a/src/gui/guiKeyChangeMenu.cpp
+++ b/src/gui/guiKeyChangeMenu.cpp
@@ -21,6 +21,7 @@
#include "guiKeyChangeMenu.h"
#include "debug.h"
+#include "guiButton.h"
#include "serialization.h"
#include <string>
#include <IGUICheckBox.h>
@@ -85,8 +86,6 @@ GUIKeyChangeMenu::GUIKeyChangeMenu(gui::IGUIEnvironment* env,
GUIModalMenu(env, parent, id, menumgr)
{
init_keys();
- for (key_setting *ks : key_settings)
- key_used.push_back(ks->key);
}
GUIKeyChangeMenu::~GUIKeyChangeMenu()
@@ -111,6 +110,7 @@ void GUIKeyChangeMenu::removeChildren()
for (gui::IGUIElement *i : children_copy) {
i->remove();
}
+ key_used_text = nullptr;
}
void GUIKeyChangeMenu::regenerateGui(v2u32 screensize)
@@ -157,7 +157,7 @@ void GUIKeyChangeMenu::regenerateGui(v2u32 screensize)
core::rect<s32> rect(0, 0, 100 * s, 30 * s);
rect += topleft + v2s32(offset.X + 150 * s, offset.Y - 5 * s);
const wchar_t *text = wgettext(k->key.name());
- k->button = Environment->addButton(rect, this, k->id, text);
+ k->button = GUIButton::addButton(Environment, rect, this, k->id, text);
delete[] text;
}
if ((i + 1) % KMaxButtonPerColumns == 0) {
@@ -217,16 +217,14 @@ void GUIKeyChangeMenu::regenerateGui(v2u32 screensize)
core::rect<s32> rect(0, 0, 100 * s, 30 * s);
rect += topleft + v2s32(size.X / 2 - 105 * s, size.Y - 40 * s);
const wchar_t *text = wgettext("Save");
- Environment->addButton(rect, this, GUI_ID_BACK_BUTTON,
- text);
+ GUIButton::addButton(Environment, rect, this, GUI_ID_BACK_BUTTON, text);
delete[] text;
}
{
core::rect<s32> rect(0, 0, 100 * s, 30 * s);
rect += topleft + v2s32(size.X / 2 + 5 * s, size.Y - 40 * s);
const wchar_t *text = wgettext("Cancel");
- Environment->addButton(rect, this, GUI_ID_ABORT_BUTTON,
- text);
+ GUIButton::addButton(Environment, rect, this, GUI_ID_ABORT_BUTTON, text);
delete[] text;
}
}
@@ -247,7 +245,13 @@ void GUIKeyChangeMenu::drawMenu()
bool GUIKeyChangeMenu::acceptInput()
{
for (key_setting *k : key_settings) {
- g_settings->set(k->setting_name, k->key.sym());
+ std::string default_key;
+ g_settings->getDefaultNoEx(k->setting_name, default_key);
+
+ if (k->key.sym() != default_key)
+ g_settings->set(k->setting_name, k->key.sym());
+ else
+ g_settings->remove(k->setting_name);
}
{
@@ -275,29 +279,28 @@ bool GUIKeyChangeMenu::acceptInput()
bool GUIKeyChangeMenu::resetMenu()
{
- if (activeKey >= 0)
- {
- for (key_setting *k : key_settings) {
- if (k->id == activeKey) {
- const wchar_t *text = wgettext(k->key.name());
- k->button->setText(text);
- delete[] text;
- break;
- }
- }
- activeKey = -1;
+ if (active_key) {
+ const wchar_t *text = wgettext(active_key->key.name());
+ active_key->button->setText(text);
+ delete[] text;
+ active_key = nullptr;
return false;
}
return true;
}
bool GUIKeyChangeMenu::OnEvent(const SEvent& event)
{
- if (event.EventType == EET_KEY_INPUT_EVENT && activeKey >= 0
+ if (event.EventType == EET_KEY_INPUT_EVENT && active_key
&& event.KeyInput.PressedDown) {
bool prefer_character = shift_down;
KeyPress kp(event.KeyInput, prefer_character);
+ if (event.KeyInput.Key == irr::KEY_DELETE)
+ kp = KeyPress(""); // To erase key settings
+ else if (event.KeyInput.Key == irr::KEY_ESCAPE)
+ kp = active_key->key; // Cancel
+
bool shift_went_down = false;
if(!shift_down &&
(event.KeyInput.Key == irr::KEY_SHIFT ||
@@ -305,51 +308,46 @@ bool GUIKeyChangeMenu::OnEvent(const SEvent& event)
event.KeyInput.Key == irr::KEY_RSHIFT))
shift_went_down = true;
- // Remove Key already in use message
- if(this->key_used_text)
- {
- this->key_used_text->remove();
- this->key_used_text = NULL;
- }
// Display Key already in use message
- if (std::find(this->key_used.begin(), this->key_used.end(), kp) != this->key_used.end())
- {
- core::rect < s32 > rect(0, 0, 600, 40);
+ bool key_in_use = false;
+ if (strcmp(kp.sym(), "") != 0) {
+ for (key_setting *ks : key_settings) {
+ if (ks != active_key && ks->key == kp) {
+ key_in_use = true;
+ break;
+ }
+ }
+ }
+
+ if (key_in_use && !this->key_used_text) {
+ core::rect<s32> rect(0, 0, 600, 40);
rect += v2s32(0, 0) + v2s32(25, 30);
const wchar_t *text = wgettext("Key already in use");
this->key_used_text = Environment->addStaticText(text,
rect, false, true, this, -1);
delete[] text;
- //infostream << "Key already in use" << std::endl;
+ } else if (!key_in_use && this->key_used_text) {
+ this->key_used_text->remove();
+ this->key_used_text = nullptr;
}
// But go on
{
- key_setting *k = NULL;
- for (key_setting *ks : key_settings) {
- if (ks->id == activeKey) {
- k = ks;
- break;
- }
- }
- FATAL_ERROR_IF(k == NULL, "Key setting not found");
- k->key = kp;
- const wchar_t *text = wgettext(k->key.name());
- k->button->setText(text);
+ active_key->key = kp;
+ const wchar_t *text = wgettext(kp.name());
+ active_key->button->setText(text);
delete[] text;
- this->key_used.push_back(kp);
-
// Allow characters made with shift
- if(shift_went_down){
+ if (shift_went_down){
shift_down = true;
return false;
}
- activeKey = -1;
+ active_key = nullptr;
return true;
}
- } else if (event.EventType == EET_KEY_INPUT_EVENT && activeKey < 0
+ } else if (event.EventType == EET_KEY_INPUT_EVENT && !active_key
&& event.KeyInput.PressedDown
&& event.KeyInput.Key == irr::KEY_ESCAPE) {
quitMenu();
@@ -378,24 +376,19 @@ bool GUIKeyChangeMenu::OnEvent(const SEvent& event)
quitMenu();
return true;
default:
- key_setting *k = NULL;
-
+ resetMenu();
for (key_setting *ks : key_settings) {
if (ks->id == event.GUIEvent.Caller->getID()) {
- k = ks;
+ active_key = ks;
break;
}
}
- FATAL_ERROR_IF(k == NULL, "Key setting not found");
+ FATAL_ERROR_IF(!active_key, "Key setting not found");
- resetMenu();
shift_down = false;
- activeKey = event.GUIEvent.Caller->getID();
const wchar_t *text = wgettext("press key");
- k->button->setText(text);
+ active_key->button->setText(text);
delete[] text;
- this->key_used.erase(std::remove(this->key_used.begin(),
- this->key_used.end(), k->key), this->key_used.end());
break;
}
Environment->setFocus(this);
diff --git a/src/gui/guiKeyChangeMenu.h b/src/gui/guiKeyChangeMenu.h
index 0aaa05e18..528827fd9 100644
--- a/src/gui/guiKeyChangeMenu.h
+++ b/src/gui/guiKeyChangeMenu.h
@@ -70,9 +70,8 @@ private:
void add_key(int id, const wchar_t *button_name, const std::string &setting_name);
bool shift_down = false;
- s32 activeKey = -1;
- std::vector<KeyPress> key_used;
+ key_setting *active_key = nullptr;
gui::IGUIStaticText *key_used_text = nullptr;
std::vector<key_setting *> key_settings;
};
diff --git a/src/gui/guiPasswordChange.cpp b/src/gui/guiPasswordChange.cpp
index 469c38dbe..af91ce84c 100644
--- a/src/gui/guiPasswordChange.cpp
+++ b/src/gui/guiPasswordChange.cpp
@@ -18,6 +18,7 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
#include "guiPasswordChange.h"
#include "client/client.h"
+#include "guiButton.h"
#include <IGUICheckBox.h>
#include <IGUIEditBox.h>
#include <IGUIButton.h>
@@ -145,14 +146,14 @@ void GUIPasswordChange::regenerateGui(v2u32 screensize)
core::rect<s32> rect(0, 0, 100 * s, 30 * s);
rect = rect + v2s32(size.X / 4 + 56 * s, ypos);
text = wgettext("Change");
- Environment->addButton(rect, this, ID_change, text);
+ GUIButton::addButton(Environment, rect, this, ID_change, text);
delete[] text;
}
{
core::rect<s32> rect(0, 0, 100 * s, 30 * s);
rect = rect + v2s32(size.X / 4 + 185 * s, ypos);
text = wgettext("Cancel");
- Environment->addButton(rect, this, ID_cancel, text);
+ GUIButton::addButton(Environment, rect, this, ID_cancel, text);
delete[] text;
}
diff --git a/src/gui/guiScrollBar.cpp b/src/gui/guiScrollBar.cpp
index f7218e733..b04ccb9d5 100644
--- a/src/gui/guiScrollBar.cpp
+++ b/src/gui/guiScrollBar.cpp
@@ -247,7 +247,7 @@ s32 GUIScrollBar::getPosFromMousePos(const core::position2di &pos) const
w = RelativeRect.getHeight() - border_size * 2 - thumb_size;
p = pos.Y - AbsoluteRect.UpperLeftCorner.Y - border_size - offset;
}
- return core::isnotzero(range()) ? s32(f32(p) / f32(w) * range()) + min_pos : 0;
+ return core::isnotzero(range()) ? s32(f32(p) / f32(w) * range() + 0.5f) + min_pos : 0;
}
void GUIScrollBar::setPos(const s32 &pos)
@@ -272,7 +272,8 @@ void GUIScrollBar::setPos(const s32 &pos)
f32 f = core::isnotzero(range()) ? (f32(thumb_area) - f32(thumb_size)) / range()
: 1.0f;
- draw_center = s32((f32(scroll_pos) * f) + (f32(thumb_size) * 0.5f)) + border_size;
+ draw_center = s32((f32(scroll_pos - min_pos) * f) + (f32(thumb_size) * 0.5f)) +
+ border_size;
}
void GUIScrollBar::setSmallStep(const s32 &step)
@@ -315,6 +316,12 @@ void GUIScrollBar::setPageSize(const s32 &size)
setPos(scroll_pos);
}
+void GUIScrollBar::setArrowsVisible(ArrowVisibility visible)
+{
+ arrow_visibility = visible;
+ refreshControls();
+}
+
s32 GUIScrollBar::getPos() const
{
return scroll_pos;
@@ -419,7 +426,21 @@ void GUIScrollBar::refreshControls()
down_button->setAlignment(EGUIA_UPPERLEFT, EGUIA_LOWERRIGHT,
EGUIA_LOWERRIGHT, EGUIA_LOWERRIGHT);
}
- bool visible = (border_size != 0);
+
+ bool visible;
+ if (arrow_visibility == DEFAULT)
+ visible = (border_size != 0);
+ else if (arrow_visibility == HIDE) {
+ visible = false;
+ border_size = 0;
+ } else {
+ visible = true;
+ if (is_horizontal)
+ border_size = RelativeRect.getHeight();
+ else
+ border_size = RelativeRect.getWidth();
+ }
+
up_button->setVisible(visible);
down_button->setVisible(visible);
}
diff --git a/src/gui/guiScrollBar.h b/src/gui/guiScrollBar.h
index 349411fc1..29493bb99 100644
--- a/src/gui/guiScrollBar.h
+++ b/src/gui/guiScrollBar.h
@@ -23,6 +23,13 @@ public:
GUIScrollBar(IGUIEnvironment *environment, IGUIElement *parent, s32 id,
core::rect<s32> rectangle, bool horizontal, bool auto_scale);
+ enum ArrowVisibility
+ {
+ HIDE,
+ SHOW,
+ DEFAULT
+ };
+
virtual void draw();
virtual void updateAbsolutePosition();
virtual bool OnEvent(const SEvent &event);
@@ -39,6 +46,7 @@ public:
void setLargeStep(const s32 &step);
void setPos(const s32 &pos);
void setPageSize(const s32 &size);
+ void setArrowsVisible(ArrowVisibility visible);
private:
void refreshControls();
@@ -47,6 +55,7 @@ private:
IGUIButton *up_button;
IGUIButton *down_button;
+ ArrowVisibility arrow_visibility = DEFAULT;
bool is_dragging;
bool is_horizontal;
bool is_auto_scaling;
diff --git a/src/gui/guiVolumeChange.cpp b/src/gui/guiVolumeChange.cpp
index 45d2ee139..9428cde83 100644
--- a/src/gui/guiVolumeChange.cpp
+++ b/src/gui/guiVolumeChange.cpp
@@ -19,6 +19,7 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
#include "guiVolumeChange.h"
#include "debug.h"
+#include "guiButton.h"
#include "serialization.h"
#include <string>
#include <IGUICheckBox.h>
@@ -103,8 +104,7 @@ void GUIVolumeChange::regenerateGui(v2u32 screensize)
core::rect<s32> rect(0, 0, 80 * s, 30 * s);
rect = rect + v2s32(size.X / 2 - 80 * s / 2, size.Y / 2 + 55 * s);
const wchar_t *text = wgettext("Exit");
- Environment->addButton(rect, this, ID_soundExitButton,
- text);
+ GUIButton::addButton(Environment, rect, this, ID_soundExitButton, text);
delete[] text;
}
{
diff --git a/src/gui/modalMenu.cpp b/src/gui/modalMenu.cpp
index 30417943d..8fb6c6f0f 100644
--- a/src/gui/modalMenu.cpp
+++ b/src/gui/modalMenu.cpp
@@ -134,6 +134,8 @@ bool GUIModalMenu::preprocessEvent(const SEvent &event)
return retval;
m_jni_field_name = field_name;
+ /*~ Imperative, as in "Enter/type in text".
+ Don't forget the space. */
std::string message = gettext("Enter ");
std::string label = wide_to_utf8(getLabelByID(hovered->getID()));
if (label.empty())
diff --git a/src/gui/touchscreengui.h b/src/gui/touchscreengui.h
index 1f9adda22..2a3f24a3f 100644
--- a/src/gui/touchscreengui.h
+++ b/src/gui/touchscreengui.h
@@ -230,7 +230,7 @@ private:
int m_move_id = -1;
bool m_move_has_really_moved = false;
- s64 m_move_downtime = 0;
+ u64 m_move_downtime = 0;
bool m_move_sent_as_mouse_event = false;
v2s32 m_move_downlocation = v2s32(-10000, -10000);
@@ -296,7 +296,7 @@ private:
// doubleclick detection variables
struct key_event
{
- unsigned int down_time;
+ u64 down_time;
s32 x;
s32 y;
};