diff options
Diffstat (limited to 'src/gui')
40 files changed, 2599 insertions, 2708 deletions
diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index 147f445f4..fdd36914a 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -7,6 +7,7 @@ set(gui_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/guiButtonItemImage.cpp ${CMAKE_CURRENT_SOURCE_DIR}/guiChatConsole.cpp ${CMAKE_CURRENT_SOURCE_DIR}/guiConfirmRegistration.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/guiEditBox.cpp ${CMAKE_CURRENT_SOURCE_DIR}/guiEditBoxWithScrollbar.cpp ${CMAKE_CURRENT_SOURCE_DIR}/guiEngine.cpp ${CMAKE_CURRENT_SOURCE_DIR}/guiFormSpecMenu.cpp @@ -15,6 +16,7 @@ set(gui_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/guiKeyChangeMenu.cpp ${CMAKE_CURRENT_SOURCE_DIR}/guiPasswordChange.cpp ${CMAKE_CURRENT_SOURCE_DIR}/guiPathSelectMenu.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/guiScene.cpp ${CMAKE_CURRENT_SOURCE_DIR}/guiScrollBar.cpp ${CMAKE_CURRENT_SOURCE_DIR}/guiScrollContainer.cpp ${CMAKE_CURRENT_SOURCE_DIR}/guiSkin.cpp diff --git a/src/gui/StyleSpec.h b/src/gui/StyleSpec.h index 3e842e826..fc92a861b 100644 --- a/src/gui/StyleSpec.h +++ b/src/gui/StyleSpec.h @@ -18,10 +18,13 @@ with this program; if not, write to the Free Software Foundation, Inc., */ #include "client/tile.h" // ITextureSource +#include "client/fontengine.h" #include "debug.h" #include "irrlichttypes_extrabloated.h" #include "util/string.h" +#include <algorithm> #include <array> +#include <vector> #pragma once @@ -46,6 +49,14 @@ public: ALPHA, CONTENT_OFFSET, PADDING, + FONT, + FONT_SIZE, + COLORS, + BORDERCOLORS, + BORDERWIDTHS, + SOUND, + SPACING, + SIZE, NUM_PROPERTIES, NONE }; @@ -98,6 +109,22 @@ public: return CONTENT_OFFSET; } else if (name == "padding") { return PADDING; + } else if (name == "font") { + return FONT; + } else if (name == "font_size") { + return FONT_SIZE; + } else if (name == "colors") { + return COLORS; + } else if (name == "bordercolors") { + return BORDERCOLORS; + } else if (name == "borderwidths") { + return BORDERWIDTHS; + } else if (name == "sound") { + return SOUND; + } else if (name == "spacing") { + return SPACING; + } else if (name == "size") { + return SIZE; } else { return NONE; } @@ -179,6 +206,42 @@ public: return color; } + std::array<video::SColor, 4> getColorArray(Property prop, + std::array<video::SColor, 4> def) const + { + const auto &val = properties[prop]; + if (val.empty()) + return def; + + std::vector<std::string> strs; + if (!parseArray(val, strs)) + return def; + + for (size_t i = 0; i <= 3; i++) { + video::SColor color; + if (parseColorString(strs[i], color, false, 0xff)) + def[i] = color; + } + + return def; + } + + std::array<s32, 4> getIntArray(Property prop, std::array<s32, 4> def) const + { + const auto &val = properties[prop]; + if (val.empty()) + return def; + + std::vector<std::string> strs; + if (!parseArray(val, strs)) + return def; + + for (size_t i = 0; i <= 3; i++) + def[i] = stoi(strs[i]); + + return def; + } + irr::core::rect<s32> getRect(Property prop, irr::core::rect<s32> def) const { const auto &val = properties[prop]; @@ -202,27 +265,81 @@ public: return rect; } - irr::core::vector2d<s32> getVector2i(Property prop, irr::core::vector2d<s32> def) const + v2f32 getVector2f(Property prop, v2f32 def) const { const auto &val = properties[prop]; if (val.empty()) return def; - irr::core::vector2d<s32> vec; - if (!parseVector2i(val, &vec)) + v2f32 vec; + if (!parseVector2f(val, &vec)) return def; return vec; } - irr::core::vector2d<s32> getVector2i(Property prop) const + v2s32 getVector2i(Property prop, v2s32 def) const + { + const auto &val = properties[prop]; + if (val.empty()) + return def; + + v2f32 vec; + if (!parseVector2f(val, &vec)) + return def; + + return v2s32(vec.X, vec.Y); + } + + v2s32 getVector2i(Property prop) const { const auto &val = properties[prop]; FATAL_ERROR_IF(val.empty(), "Unexpected missing property"); - irr::core::vector2d<s32> vec; - parseVector2i(val, &vec); - return vec; + v2f32 vec; + parseVector2f(val, &vec); + return v2s32(vec.X, vec.Y); + } + + gui::IGUIFont *getFont() const + { + FontSpec spec(FONT_SIZE_UNSPECIFIED, FM_Standard, false, false); + + const std::string &font = properties[FONT]; + const std::string &size = properties[FONT_SIZE]; + + if (font.empty() && size.empty()) + return nullptr; + + std::vector<std::string> modes = split(font, ','); + + for (size_t i = 0; i < modes.size(); i++) { + if (modes[i] == "normal") + spec.mode = FM_Standard; + else if (modes[i] == "mono") + spec.mode = FM_Mono; + else if (modes[i] == "bold") + spec.bold = true; + else if (modes[i] == "italic") + spec.italic = true; + } + + if (!size.empty()) { + int calc_size = 1; + + if (size[0] == '*') { + std::string new_size = size.substr(1); // Remove '*' (invalid for stof) + calc_size = stof(new_size) * g_fontengine->getFontSize(spec.mode); + } else if (size[0] == '+' || size[0] == '-') { + calc_size = stoi(size) + g_fontengine->getFontSize(spec.mode); + } else { + calc_size = stoi(size); + } + + spec.size = (unsigned)std::min(std::max(calc_size, 1), 999); + } + + return g_fontengine->getFont(spec); } video::ITexture *getTexture(Property prop, ISimpleTextureSource *tsrc, @@ -285,6 +402,24 @@ public: } private: + bool parseArray(const std::string &value, std::vector<std::string> &arr) const + { + std::vector<std::string> strs = split(value, ','); + + if (strs.size() == 1) { + arr = {strs[0], strs[0], strs[0], strs[0]}; + } else if (strs.size() == 2) { + arr = {strs[0], strs[1], strs[0], strs[1]}; + } else if (strs.size() == 4) { + arr = strs; + } else { + warningstream << "Invalid array size (" << strs.size() + << " arguments): \"" << value << "\"" << std::endl; + return false; + } + return true; + } + bool parseRect(const std::string &value, irr::core::rect<s32> *parsed_rect) const { irr::core::rect<s32> rect; @@ -316,22 +451,20 @@ private: return true; } - bool parseVector2i(const std::string &value, irr::core::vector2d<s32> *parsed_vec) const + bool parseVector2f(const std::string &value, v2f32 *parsed_vec) const { - irr::core::vector2d<s32> vec; + v2f32 vec; std::vector<std::string> v_vector = split(value, ','); if (v_vector.size() == 1) { - s32 x = stoi(v_vector[0]); + f32 x = stof(v_vector[0]); vec.X = x; vec.Y = x; } else if (v_vector.size() == 2) { - s32 x = stoi(v_vector[0]); - s32 y = stoi(v_vector[1]); - vec.X = x; - vec.Y = y; + vec.X = stof(v_vector[0]); + vec.Y = stof(v_vector[1]); } else { - warningstream << "Invalid vector2d string format: \"" << value + warningstream << "Invalid 2d vector string format: \"" << value << "\"" << std::endl; return false; } diff --git a/src/gui/guiBox.cpp b/src/gui/guiBox.cpp index 7f329cc32..443f1064f 100644 --- a/src/gui/guiBox.cpp +++ b/src/gui/guiBox.cpp @@ -20,9 +20,14 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "guiBox.h" GUIBox::GUIBox(gui::IGUIEnvironment *env, gui::IGUIElement *parent, s32 id, - const core::rect<s32> &rectangle, const video::SColor &color) : + const core::rect<s32> &rectangle, + const std::array<video::SColor, 4> &colors, + const std::array<video::SColor, 4> &bordercolors, + const std::array<s32, 4> &borderwidths) : gui::IGUIElement(gui::EGUIET_ELEMENT, env, parent, id, rectangle), - m_color(color) + m_colors(colors), + m_bordercolors(bordercolors), + m_borderwidths(borderwidths) { } @@ -31,8 +36,82 @@ void GUIBox::draw() if (!IsVisible) return; - Environment->getVideoDriver()->draw2DRectangle(m_color, AbsoluteRect, - &AbsoluteClippingRect); + std::array<s32, 4> negative_borders = {0, 0, 0, 0}; + std::array<s32, 4> positive_borders = {0, 0, 0, 0}; + + for (size_t i = 0; i <= 3; i++) { + if (m_borderwidths[i] > 0) + positive_borders[i] = m_borderwidths[i]; + else + negative_borders[i] = m_borderwidths[i]; + } + + v2s32 upperleft = AbsoluteRect.UpperLeftCorner; + v2s32 lowerright = AbsoluteRect.LowerRightCorner; + + v2s32 topleft_border = { + upperleft.X - positive_borders[3], + upperleft.Y - positive_borders[0] + }; + v2s32 topleft_rect = { + upperleft.X - negative_borders[3], + upperleft.Y - negative_borders[0] + }; + + v2s32 lowerright_border = { + lowerright.X + positive_borders[1], + lowerright.Y + positive_borders[2] + }; + v2s32 lowerright_rect = { + lowerright.X + negative_borders[1], + lowerright.Y + negative_borders[2] + }; + + core::rect<s32> main_rect( + topleft_rect.X, + topleft_rect.Y, + lowerright_rect.X, + lowerright_rect.Y + ); + + std::array<core::rect<s32>, 4> border_rects; + + border_rects[0] = core::rect<s32>( + topleft_border.X, + topleft_border.Y, + lowerright_border.X, + topleft_rect.Y + ); + + border_rects[1] = core::rect<s32>( + lowerright_rect.X, + topleft_rect.Y, + lowerright_border.X, + lowerright_rect.Y + ); + + border_rects[2] = core::rect<s32>( + topleft_border.X, + lowerright_rect.Y, + lowerright_border.X, + lowerright_border.Y + ); + + border_rects[3] = core::rect<s32>( + topleft_border.X, + topleft_rect.Y, + topleft_rect.X, + lowerright_rect.Y + ); + + video::IVideoDriver *driver = Environment->getVideoDriver(); + + driver->draw2DRectangle(main_rect, m_colors[0], m_colors[1], m_colors[3], + m_colors[2], &AbsoluteClippingRect); + + for (size_t i = 0; i <= 3; i++) + driver->draw2DRectangle(m_bordercolors[i], border_rects[i], + &AbsoluteClippingRect); IGUIElement::draw(); } diff --git a/src/gui/guiBox.h b/src/gui/guiBox.h index 5306fdf65..ca8f83771 100644 --- a/src/gui/guiBox.h +++ b/src/gui/guiBox.h @@ -19,16 +19,23 @@ with this program; if not, write to the Free Software Foundation, Inc., #pragma once +#include <vector> +#include <array> #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); + const core::rect<s32> &rectangle, + const std::array<video::SColor, 4> &colors, + const std::array<video::SColor, 4> &bordercolors, + const std::array<s32, 4> &borderwidths); virtual void draw() override; private: - video::SColor m_color; + std::array<video::SColor, 4> m_colors; + std::array<video::SColor, 4> m_bordercolors; + std::array<s32, 4> m_borderwidths; }; diff --git a/src/gui/guiButton.cpp b/src/gui/guiButton.cpp index 6732a9233..b98e5de82 100644 --- a/src/gui/guiButton.cpp +++ b/src/gui/guiButton.cpp @@ -313,11 +313,12 @@ void GUIButton::draw() // PATCH
video::ITexture* texture = ButtonImages[(u32)imageState].Texture;
+ video::SColor image_colors[] = { BgColor, BgColor, BgColor, BgColor };
if (BgMiddle.getArea() == 0) {
driver->draw2DImage(texture,
ScaleImage? AbsoluteRect : core::rect<s32>(pos, sourceRect.getSize()),
sourceRect, &AbsoluteClippingRect,
- 0, UseAlphaChannel);
+ image_colors, UseAlphaChannel);
} else {
core::rect<s32> middle = BgMiddle;
// `-x` is interpreted as `w - x`
@@ -327,7 +328,7 @@ void GUIButton::draw() middle.LowerRightCorner.Y += texture->getOriginalSize().Height;
draw2DImage9Slice(driver, texture,
ScaleImage ? AbsoluteRect : core::rect<s32>(pos, sourceRect.getSize()),
- middle, &AbsoluteClippingRect);
+ middle, &AbsoluteClippingRect, image_colors);
}
// END PATCH
}
@@ -722,6 +723,8 @@ GUIButton* GUIButton::addButton(IGUIEnvironment *environment, void GUIButton::setColor(video::SColor color)
{
+ BgColor = color;
+
float d = 0.65f;
for (size_t i = 0; i < 4; i++) {
video::SColor base = Environment->getSkin()->getColor((gui::EGUI_DEFAULT_COLOR)i);
@@ -750,22 +753,26 @@ void GUIButton::setFromStyle(const StyleSpec& style) bool pressed = (style.getState() & StyleSpec::STATE_PRESSED) != 0;
if (style.isNotDefault(StyleSpec::BGCOLOR)) {
-
setColor(style.getColor(StyleSpec::BGCOLOR));
// If we have a propagated hover/press color, we need to automatically
// lighten/darken it
if (!Styles[style.getState()].isNotDefault(StyleSpec::BGCOLOR)) {
- for (size_t i = 0; i < 4; i++) {
if (pressed) {
- Colors[i] = multiplyColorValue(Colors[i], COLOR_PRESSED_MOD);
+ BgColor = multiplyColorValue(BgColor, COLOR_PRESSED_MOD);
+
+ for (size_t i = 0; i < 4; i++)
+ Colors[i] = multiplyColorValue(Colors[i], COLOR_PRESSED_MOD);
} else if (hovered) {
- Colors[i] = multiplyColorValue(Colors[i], COLOR_HOVERED_MOD);
+ BgColor = multiplyColorValue(BgColor, COLOR_HOVERED_MOD);
+
+ for (size_t i = 0; i < 4; i++)
+ Colors[i] = multiplyColorValue(Colors[i], COLOR_HOVERED_MOD);
}
- }
}
} else {
+ BgColor = video::SColor(255, 255, 255, 255);
for (size_t i = 0; i < 4; i++) {
video::SColor base =
Environment->getSkin()->getColor((gui::EGUI_DEFAULT_COLOR)i);
@@ -788,6 +795,7 @@ void GUIButton::setFromStyle(const StyleSpec& style) setNotClipped(style.getBool(StyleSpec::NOCLIP, false));
setDrawBorder(style.getBool(StyleSpec::BORDER, true));
setUseAlphaChannel(style.getBool(StyleSpec::ALPHA, true));
+ setOverrideFont(style.getFont());
if (style.isNotDefault(StyleSpec::BGIMG)) {
video::ITexture *texture = style.getTexture(StyleSpec::BGIMG,
diff --git a/src/gui/guiButton.h b/src/gui/guiButton.h index 95fa1a2a1..4e1b04aac 100644 --- a/src/gui/guiButton.h +++ b/src/gui/guiButton.h @@ -338,5 +338,6 @@ private: core::rect<s32> BgMiddle;
core::rect<s32> Padding;
core::vector2d<s32> ContentOffset;
+ video::SColor BgColor;
// END PATCH
};
diff --git a/src/gui/guiButtonItemImage.cpp b/src/gui/guiButtonItemImage.cpp index d8b9042ac..39272fe37 100644 --- a/src/gui/guiButtonItemImage.cpp +++ b/src/gui/guiButtonItemImage.cpp @@ -39,7 +39,6 @@ GUIButtonItemImage::GUIButtonItemImage(gui::IGUIEnvironment *environment, item, getActiveFont(), client); sendToBack(m_image); - m_item_name = item; m_client = client; } diff --git a/src/gui/guiButtonItemImage.h b/src/gui/guiButtonItemImage.h index aad923bda..b90ac757e 100644 --- a/src/gui/guiButtonItemImage.h +++ b/src/gui/guiButtonItemImage.h @@ -42,7 +42,6 @@ public: Client *client); private: - std::string m_item_name; Client *m_client; GUIItemImage *m_image; }; diff --git a/src/gui/guiChatConsole.cpp b/src/gui/guiChatConsole.cpp index 8de00c12f..ef471106d 100644 --- a/src/gui/guiChatConsole.cpp +++ b/src/gui/guiChatConsole.cpp @@ -136,11 +136,6 @@ void GUIChatConsole::closeConsoleAtOnce() recalculateConsolePosition(); } -f32 GUIChatConsole::getDesiredHeight() const -{ - return m_desired_height_fraction; -} - void GUIChatConsole::replaceAndAddToHistory(const std::wstring &line) { ChatPrompt& prompt = m_chat_backend->getPrompt(); @@ -545,7 +540,7 @@ bool GUIChatConsole::OnEvent(const SEvent& event) if (prompt.getCursorLength() <= 0) return true; std::wstring wselected = prompt.getSelection(); - std::string selected(wselected.begin(), wselected.end()); + std::string selected = wide_to_utf8(wselected); Environment->getOSOperator()->copyToClipboard(selected.c_str()); return true; } @@ -575,7 +570,7 @@ bool GUIChatConsole::OnEvent(const SEvent& event) if (prompt.getCursorLength() <= 0) return true; std::wstring wselected = prompt.getSelection(); - std::string selected(wselected.begin(), wselected.end()); + std::string selected = wide_to_utf8(wselected); Environment->getOSOperator()->copyToClipboard(selected.c_str()); prompt.cursorOperation( ChatPrompt::CURSOROP_DELETE, diff --git a/src/gui/guiChatConsole.h b/src/gui/guiChatConsole.h index 7be40e27c..896342ab0 100644 --- a/src/gui/guiChatConsole.h +++ b/src/gui/guiChatConsole.h @@ -55,10 +55,6 @@ public: // Set whether to close the console after the user presses enter. void setCloseOnEnter(bool close) { m_close_on_enter = close; } - // Return the desired height (fraction of screen size) - // Zero if the console is closed or getting closed - f32 getDesiredHeight() const; - // Replace actual line when adding the actual to the history (if there is any) void replaceAndAddToHistory(const std::wstring &line); @@ -72,8 +68,6 @@ public: // Irrlicht draw method virtual void draw(); - bool canTakeFocus(gui::IGUIElement* element) { return false; } - virtual bool OnEvent(const SEvent& event); virtual void setVisible(bool visible); diff --git a/src/gui/guiConfirmRegistration.cpp b/src/gui/guiConfirmRegistration.cpp index 58ac42740..4a798c39b 100644 --- a/src/gui/guiConfirmRegistration.cpp +++ b/src/gui/guiConfirmRegistration.cpp @@ -73,7 +73,11 @@ void GUIConfirmRegistration::regenerateGui(v2u32 screensize) /* Calculate new sizes and positions */ +#ifdef __ANDROID__ + const float s = m_gui_scale * porting::getDisplayDensity() / 2; +#else const float s = m_gui_scale; +#endif DesiredRect = core::rect<s32>( screensize.X / 2 - 600 * s / 2, screensize.Y / 2 - 360 * s / 2, @@ -188,8 +192,7 @@ void GUIConfirmRegistration::acceptInput() bool GUIConfirmRegistration::processInput() { - std::wstring m_password_ws = narrow_to_wide(m_password); - if (m_password_ws != m_pass_confirm) { + if (utf8_to_wide(m_password) != m_pass_confirm) { gui::IGUIElement *e = getElementFromId(ID_message); if (e) e->setVisible(true); @@ -222,7 +225,7 @@ bool GUIConfirmRegistration::OnEvent(const SEvent &event) if (event.GUIEvent.EventType == gui::EGET_ELEMENT_FOCUS_LOST && isVisible()) { if (!canTakeFocus(event.GUIEvent.Element)) { - dstream << "GUIConfirmRegistration: Not allowing focus change." + infostream << "GUIConfirmRegistration: Not allowing focus change." << std::endl; // Returning true disables focus change return true; @@ -257,12 +260,19 @@ bool GUIConfirmRegistration::getAndroidUIInput() if (!hasAndroidUIInput() || m_jni_field_name != "password") return false; - std::string text = porting::getInputDialogValue(); - gui::IGUIElement *e = getElementFromId(ID_confirmPassword); - if (e) - e->setText(utf8_to_wide(text).c_str()); + // still waiting + if (porting::getInputDialogState() == -1) + return true; m_jni_field_name.clear(); + + gui::IGUIElement *e = getElementFromId(ID_confirmPassword); + + if (!e || e->getType() != irr::gui::EGUIET_EDIT_BOX) + return false; + + std::string text = porting::getInputDialogValue(); + e->setText(utf8_to_wide(text).c_str()); return false; } #endif diff --git a/src/gui/guiEditBox.cpp b/src/gui/guiEditBox.cpp new file mode 100644 index 000000000..79979dbc3 --- /dev/null +++ b/src/gui/guiEditBox.cpp @@ -0,0 +1,911 @@ +/* +Minetest +Copyright (C) 2021 Minetest + +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 "guiEditBox.h" + +#include "IGUISkin.h" +#include "IGUIEnvironment.h" +#include "IGUIFont.h" + +#include "porting.h" + +GUIEditBox::~GUIEditBox() +{ + if (m_override_font) + m_override_font->drop(); + + if (m_operator) + m_operator->drop(); + + if (m_vscrollbar) + m_vscrollbar->drop(); +} + +void GUIEditBox::setOverrideFont(IGUIFont *font) +{ + if (m_override_font == font) + return; + + if (m_override_font) + m_override_font->drop(); + + m_override_font = font; + + if (m_override_font) + m_override_font->grab(); + + breakText(); +} + +//! Get the font which is used right now for drawing +IGUIFont *GUIEditBox::getActiveFont() const +{ + if (m_override_font) + return m_override_font; + IGUISkin *skin = Environment->getSkin(); + if (skin) + return skin->getFont(); + return 0; +} + +//! Sets another color for the text. +void GUIEditBox::setOverrideColor(video::SColor color) +{ + m_override_color = color; + m_override_color_enabled = true; +} + +video::SColor GUIEditBox::getOverrideColor() const +{ + return m_override_color; +} + +//! Sets if the text should use the overide color or the color in the gui skin. +void GUIEditBox::enableOverrideColor(bool enable) +{ + m_override_color_enabled = enable; +} + +//! Enables or disables word wrap +void GUIEditBox::setWordWrap(bool enable) +{ + m_word_wrap = enable; + breakText(); +} + +//! Enables or disables newlines. +void GUIEditBox::setMultiLine(bool enable) +{ + m_multiline = enable; +} + +//! Enables or disables automatic scrolling with cursor position +//! \param enable: If set to true, the text will move around with the cursor position +void GUIEditBox::setAutoScroll(bool enable) +{ + m_autoscroll = enable; +} + +void GUIEditBox::setPasswordBox(bool password_box, wchar_t password_char) +{ + m_passwordbox = password_box; + if (m_passwordbox) { + m_passwordchar = password_char; + setMultiLine(false); + setWordWrap(false); + m_broken_text.clear(); + } +} + +//! Sets text justification +void GUIEditBox::setTextAlignment(EGUI_ALIGNMENT horizontal, EGUI_ALIGNMENT vertical) +{ + m_halign = horizontal; + m_valign = vertical; +} + +//! Sets the new caption of this element. +void GUIEditBox::setText(const wchar_t *text) +{ + Text = text; + if (u32(m_cursor_pos) > Text.size()) + m_cursor_pos = Text.size(); + m_hscroll_pos = 0; + breakText(); +} + +//! Sets the maximum amount of characters which may be entered in the box. +//! \param max: Maximum amount of characters. If 0, the character amount is +//! infinity. +void GUIEditBox::setMax(u32 max) +{ + m_max = max; + + if (Text.size() > m_max && m_max != 0) + Text = Text.subString(0, m_max); +} + +//! Gets the area of the text in the edit box +//! \return Returns the size in pixels of the text +core::dimension2du GUIEditBox::getTextDimension() +{ + core::rect<s32> ret; + + setTextRect(0); + ret = m_current_text_rect; + + for (u32 i = 1; i < m_broken_text.size(); ++i) { + setTextRect(i); + ret.addInternalPoint(m_current_text_rect.UpperLeftCorner); + ret.addInternalPoint(m_current_text_rect.LowerRightCorner); + } + + return core::dimension2du(ret.getSize()); +} + +//! Turns the border on or off +void GUIEditBox::setDrawBorder(bool border) +{ + m_border = border; +} + +void GUIEditBox::setWritable(bool can_write_text) +{ + m_writable = can_write_text; +} + +//! set text markers +void GUIEditBox::setTextMarkers(s32 begin, s32 end) +{ + if (begin != m_mark_begin || end != m_mark_end) { + m_mark_begin = begin; + m_mark_end = end; + sendGuiEvent(EGET_EDITBOX_MARKING_CHANGED); + } +} + +//! send some gui event to parent +void GUIEditBox::sendGuiEvent(EGUI_EVENT_TYPE type) +{ + if (Parent) { + SEvent e; + e.EventType = EET_GUI_EVENT; + e.GUIEvent.Caller = this; + e.GUIEvent.Element = 0; + e.GUIEvent.EventType = type; + + Parent->OnEvent(e); + } +} + +//! called if an event happened. +bool GUIEditBox::OnEvent(const SEvent &event) +{ + if (isEnabled()) { + + switch (event.EventType) { + case EET_GUI_EVENT: + if (event.GUIEvent.EventType == EGET_ELEMENT_FOCUS_LOST) { + if (event.GUIEvent.Caller == this) { + m_mouse_marking = false; + setTextMarkers(0, 0); + } + } + break; + case EET_KEY_INPUT_EVENT: { +#if (defined(__linux__) || defined(__FreeBSD__)) || defined(__DragonFly__) + // ################################################################ + // ValkaTR: + // This part is the difference from the original intlGUIEditBox + // It converts UTF-8 character into a UCS-2 (wchar_t) + wchar_t wc = L'_'; + mbtowc(&wc, (char *)&event.KeyInput.Char, + sizeof(event.KeyInput.Char)); + + // printf( "char: %lc (%u) \r\n", wc, wc ); + + SEvent irrevent(event); + irrevent.KeyInput.Char = wc; + // ################################################################ + + if (processKey(irrevent)) + return true; +#else + if (processKey(event)) + return true; +#endif // defined(linux) + + break; + } + case EET_MOUSE_INPUT_EVENT: + if (processMouse(event)) + return true; + break; + default: + break; + } + } + + return IGUIElement::OnEvent(event); +} + +bool GUIEditBox::processKey(const SEvent &event) +{ + if (!m_writable) { + return false; + } + + if (!event.KeyInput.PressedDown) + return false; + + bool text_changed = false; + s32 new_mark_begin = m_mark_begin; + s32 new_mark_end = m_mark_end; + + // control shortcut handling + if (event.KeyInput.Control) { + + // german backlash '\' entered with control + '?' + if (event.KeyInput.Char == '\\') { + inputChar(event.KeyInput.Char); + return true; + } + + switch (event.KeyInput.Key) { + case KEY_KEY_A: + // select all + new_mark_begin = 0; + new_mark_end = Text.size(); + break; + case KEY_KEY_C: + onKeyControlC(event); + break; + case KEY_KEY_X: + text_changed = onKeyControlX(event, new_mark_begin, new_mark_end); + break; + case KEY_KEY_V: + text_changed = onKeyControlV(event, new_mark_begin, new_mark_end); + break; + case KEY_HOME: + // move/highlight to start of text + if (event.KeyInput.Shift) { + new_mark_end = m_cursor_pos; + new_mark_begin = 0; + m_cursor_pos = 0; + } else { + m_cursor_pos = 0; + new_mark_begin = 0; + new_mark_end = 0; + } + break; + case KEY_END: + // move/highlight to end of text + if (event.KeyInput.Shift) { + new_mark_begin = m_cursor_pos; + new_mark_end = Text.size(); + m_cursor_pos = 0; + } else { + m_cursor_pos = Text.size(); + new_mark_begin = 0; + new_mark_end = 0; + } + break; + default: + return false; + } + } else { + switch (event.KeyInput.Key) { + case KEY_END: { + s32 p = Text.size(); + if (m_word_wrap || m_multiline) { + p = getLineFromPos(m_cursor_pos); + p = m_broken_text_positions[p] + + (s32)m_broken_text[p].size(); + if (p > 0 && (Text[p - 1] == L'\r' || + Text[p - 1] == L'\n')) + p -= 1; + } + + if (event.KeyInput.Shift) { + if (m_mark_begin == m_mark_end) + new_mark_begin = m_cursor_pos; + + new_mark_end = p; + } else { + new_mark_begin = 0; + new_mark_end = 0; + } + m_cursor_pos = p; + m_blink_start_time = porting::getTimeMs(); + } break; + case KEY_HOME: { + + s32 p = 0; + if (m_word_wrap || m_multiline) { + p = getLineFromPos(m_cursor_pos); + p = m_broken_text_positions[p]; + } + + if (event.KeyInput.Shift) { + if (m_mark_begin == m_mark_end) + new_mark_begin = m_cursor_pos; + new_mark_end = p; + } else { + new_mark_begin = 0; + new_mark_end = 0; + } + m_cursor_pos = p; + m_blink_start_time = porting::getTimeMs(); + } break; + case KEY_RETURN: + if (m_multiline) { + inputChar(L'\n'); + } else { + calculateScrollPos(); + sendGuiEvent(EGET_EDITBOX_ENTER); + } + return true; + case KEY_LEFT: + if (event.KeyInput.Shift) { + if (m_cursor_pos > 0) { + if (m_mark_begin == m_mark_end) + new_mark_begin = m_cursor_pos; + + new_mark_end = m_cursor_pos - 1; + } + } else { + new_mark_begin = 0; + new_mark_end = 0; + } + + if (m_cursor_pos > 0) + m_cursor_pos--; + m_blink_start_time = porting::getTimeMs(); + break; + case KEY_RIGHT: + if (event.KeyInput.Shift) { + if (Text.size() > (u32)m_cursor_pos) { + if (m_mark_begin == m_mark_end) + new_mark_begin = m_cursor_pos; + + new_mark_end = m_cursor_pos + 1; + } + } else { + new_mark_begin = 0; + new_mark_end = 0; + } + + if (Text.size() > (u32)m_cursor_pos) + m_cursor_pos++; + m_blink_start_time = porting::getTimeMs(); + break; + case KEY_UP: + if (!onKeyUp(event, new_mark_begin, new_mark_end)) { + return false; + } + break; + case KEY_DOWN: + if (!onKeyDown(event, new_mark_begin, new_mark_end)) { + return false; + } + break; + case KEY_BACK: + text_changed = onKeyBack(event, new_mark_begin, new_mark_end); + break; + + case KEY_DELETE: + text_changed = onKeyDelete(event, new_mark_begin, new_mark_end); + break; + + case KEY_ESCAPE: + case KEY_TAB: + case KEY_SHIFT: + case KEY_F1: + case KEY_F2: + case KEY_F3: + case KEY_F4: + case KEY_F5: + case KEY_F6: + case KEY_F7: + case KEY_F8: + case KEY_F9: + case KEY_F10: + case KEY_F11: + case KEY_F12: + case KEY_F13: + case KEY_F14: + case KEY_F15: + case KEY_F16: + case KEY_F17: + case KEY_F18: + case KEY_F19: + case KEY_F20: + case KEY_F21: + case KEY_F22: + case KEY_F23: + case KEY_F24: + // ignore these keys + return false; + + default: + inputChar(event.KeyInput.Char); + return true; + } + } + + // Set new text markers + setTextMarkers(new_mark_begin, new_mark_end); + + // break the text if it has changed + if (text_changed) { + breakText(); + sendGuiEvent(EGET_EDITBOX_CHANGED); + } + + calculateScrollPos(); + + return true; +} + +bool GUIEditBox::onKeyUp(const SEvent &event, s32 &mark_begin, s32 &mark_end) +{ + // clang-format off + if (m_multiline || (m_word_wrap && m_broken_text.size() > 1)) { + s32 lineNo = getLineFromPos(m_cursor_pos); + s32 mb = (m_mark_begin == m_mark_end) ? m_cursor_pos : + (m_mark_begin > m_mark_end ? m_mark_begin : m_mark_end); + if (lineNo > 0) { + s32 cp = m_cursor_pos - m_broken_text_positions[lineNo]; + if ((s32)m_broken_text[lineNo - 1].size() < cp) { + m_cursor_pos = m_broken_text_positions[lineNo - 1] + + core::max_((u32)1, m_broken_text[lineNo - 1].size()) - 1; + } + else + m_cursor_pos = m_broken_text_positions[lineNo - 1] + cp; + } + + if (event.KeyInput.Shift) { + mark_begin = mb; + mark_end = m_cursor_pos; + } else { + mark_begin = 0; + mark_end = 0; + } + + return true; + } + + // clang-format on + return false; +} + +bool GUIEditBox::onKeyDown(const SEvent &event, s32 &mark_begin, s32 &mark_end) +{ + // clang-format off + if (m_multiline || (m_word_wrap && m_broken_text.size() > 1)) { + s32 lineNo = getLineFromPos(m_cursor_pos); + s32 mb = (m_mark_begin == m_mark_end) ? m_cursor_pos : + (m_mark_begin < m_mark_end ? m_mark_begin : m_mark_end); + if (lineNo < (s32)m_broken_text.size() - 1) { + s32 cp = m_cursor_pos - m_broken_text_positions[lineNo]; + if ((s32)m_broken_text[lineNo + 1].size() < cp) { + m_cursor_pos = m_broken_text_positions[lineNo + 1] + + core::max_((u32)1, m_broken_text[lineNo + 1].size()) - 1; + } + else + m_cursor_pos = m_broken_text_positions[lineNo + 1] + cp; + } + + if (event.KeyInput.Shift) { + mark_begin = mb; + mark_end = m_cursor_pos; + } else { + mark_begin = 0; + mark_end = 0; + } + + return true; + } + + // clang-format on + return false; +} + +void GUIEditBox::onKeyControlC(const SEvent &event) +{ + // copy to clipboard + if (m_passwordbox || !m_operator || m_mark_begin == m_mark_end) + return; + + const s32 realmbgn = m_mark_begin < m_mark_end ? m_mark_begin : m_mark_end; + const s32 realmend = m_mark_begin < m_mark_end ? m_mark_end : m_mark_begin; + + core::stringc s; + s = Text.subString(realmbgn, realmend - realmbgn).c_str(); + m_operator->copyToClipboard(s.c_str()); +} + +bool GUIEditBox::onKeyControlX(const SEvent &event, s32 &mark_begin, s32 &mark_end) +{ + // First copy to clipboard + onKeyControlC(event); + + if (m_passwordbox || !m_operator || m_mark_begin == m_mark_end) + return false; + + const s32 realmbgn = m_mark_begin < m_mark_end ? m_mark_begin : m_mark_end; + const s32 realmend = m_mark_begin < m_mark_end ? m_mark_end : m_mark_begin; + + // Now remove from box if enabled + if (isEnabled()) { + // delete + core::stringw s; + s = Text.subString(0, realmbgn); + s.append(Text.subString(realmend, Text.size() - realmend)); + Text = s; + + m_cursor_pos = realmbgn; + mark_begin = 0; + mark_end = 0; + return true; + } + + return false; +} + +bool GUIEditBox::onKeyControlV(const SEvent &event, s32 &mark_begin, s32 &mark_end) +{ + if (!isEnabled()) + return false; + + // paste from the clipboard + if (!m_operator) + return false; + + const s32 realmbgn = m_mark_begin < m_mark_end ? m_mark_begin : m_mark_end; + const s32 realmend = m_mark_begin < m_mark_end ? m_mark_end : m_mark_begin; + + // add new character + if (const c8 *p = m_operator->getTextFromClipboard()) { + if (m_mark_begin == m_mark_end) { + // insert text + core::stringw s = Text.subString(0, m_cursor_pos); + s.append(p); + s.append(Text.subString( + m_cursor_pos, Text.size() - m_cursor_pos)); + + if (!m_max || s.size() <= m_max) { + Text = s; + s = p; + m_cursor_pos += s.size(); + } + } else { + // replace text + + core::stringw s = Text.subString(0, realmbgn); + s.append(p); + s.append(Text.subString(realmend, Text.size() - realmend)); + + if (!m_max || s.size() <= m_max) { + Text = s; + s = p; + m_cursor_pos = realmbgn + s.size(); + } + } + } + + mark_begin = 0; + mark_end = 0; + return true; +} + +bool GUIEditBox::onKeyBack(const SEvent &event, s32 &mark_begin, s32 &mark_end) +{ + if (!isEnabled() || Text.empty()) + return false; + + core::stringw s; + + if (m_mark_begin != m_mark_end) { + // delete marked text + const s32 realmbgn = + m_mark_begin < m_mark_end ? m_mark_begin : m_mark_end; + const s32 realmend = + m_mark_begin < m_mark_end ? m_mark_end : m_mark_begin; + + s = Text.subString(0, realmbgn); + s.append(Text.subString(realmend, Text.size() - realmend)); + Text = s; + + m_cursor_pos = realmbgn; + } else { + // delete text behind cursor + if (m_cursor_pos > 0) + s = Text.subString(0, m_cursor_pos - 1); + else + s = L""; + s.append(Text.subString(m_cursor_pos, Text.size() - m_cursor_pos)); + Text = s; + --m_cursor_pos; + } + + if (m_cursor_pos < 0) + m_cursor_pos = 0; + m_blink_start_time = porting::getTimeMs(); // os::Timer::getTime(); + mark_begin = 0; + mark_end = 0; + return true; +} + +bool GUIEditBox::onKeyDelete(const SEvent &event, s32 &mark_begin, s32 &mark_end) +{ + if (!isEnabled() || Text.empty()) + return false; + + core::stringw s; + + if (m_mark_begin != m_mark_end) { + // delete marked text + const s32 realmbgn = + m_mark_begin < m_mark_end ? m_mark_begin : m_mark_end; + const s32 realmend = + m_mark_begin < m_mark_end ? m_mark_end : m_mark_begin; + + s = Text.subString(0, realmbgn); + s.append(Text.subString(realmend, Text.size() - realmend)); + Text = s; + + m_cursor_pos = realmbgn; + } else { + // delete text before cursor + s = Text.subString(0, m_cursor_pos); + s.append(Text.subString( + m_cursor_pos + 1, Text.size() - m_cursor_pos - 1)); + Text = s; + } + + if (m_cursor_pos > (s32)Text.size()) + m_cursor_pos = (s32)Text.size(); + + m_blink_start_time = porting::getTimeMs(); // os::Timer::getTime(); + mark_begin = 0; + mark_end = 0; + return true; +} + +void GUIEditBox::inputChar(wchar_t c) +{ + if (!isEnabled() || !m_writable) + return; + + if (c != 0) { + if (Text.size() < m_max || m_max == 0) { + core::stringw s; + + if (m_mark_begin != m_mark_end) { + // clang-format off + // replace marked text + s32 real_begin = m_mark_begin < m_mark_end ? m_mark_begin : m_mark_end; + s32 real_end = m_mark_begin < m_mark_end ? m_mark_end : m_mark_begin; + + s = Text.subString(0, real_begin); + s.append(c); + s.append(Text.subString(real_end, Text.size() - real_end)); + Text = s; + m_cursor_pos = real_begin + 1; + // clang-format on + } else { + // add new character + s = Text.subString(0, m_cursor_pos); + s.append(c); + s.append(Text.subString(m_cursor_pos, + Text.size() - m_cursor_pos)); + Text = s; + ++m_cursor_pos; + } + + m_blink_start_time = porting::getTimeMs(); + setTextMarkers(0, 0); + } + } + breakText(); + sendGuiEvent(EGET_EDITBOX_CHANGED); + calculateScrollPos(); +} + +bool GUIEditBox::processMouse(const SEvent &event) +{ + switch (event.MouseInput.Event) { + case irr::EMIE_LMOUSE_LEFT_UP: + if (Environment->hasFocus(this)) { + m_cursor_pos = getCursorPos( + event.MouseInput.X, event.MouseInput.Y); + if (m_mouse_marking) { + setTextMarkers(m_mark_begin, m_cursor_pos); + } + m_mouse_marking = false; + calculateScrollPos(); + return true; + } + break; + case irr::EMIE_MOUSE_MOVED: { + if (m_mouse_marking) { + m_cursor_pos = getCursorPos( + event.MouseInput.X, event.MouseInput.Y); + setTextMarkers(m_mark_begin, m_cursor_pos); + calculateScrollPos(); + return true; + } + } break; + case EMIE_LMOUSE_PRESSED_DOWN: + + if (!Environment->hasFocus(this)) { + m_blink_start_time = porting::getTimeMs(); + m_mouse_marking = true; + m_cursor_pos = getCursorPos( + event.MouseInput.X, event.MouseInput.Y); + setTextMarkers(m_cursor_pos, m_cursor_pos); + calculateScrollPos(); + return true; + } else { + if (!AbsoluteClippingRect.isPointInside(core::position2d<s32>( + event.MouseInput.X, event.MouseInput.Y))) { + return false; + } else { + // move cursor + m_cursor_pos = getCursorPos( + event.MouseInput.X, event.MouseInput.Y); + + s32 newMarkBegin = m_mark_begin; + if (!m_mouse_marking) + newMarkBegin = m_cursor_pos; + + m_mouse_marking = true; + setTextMarkers(newMarkBegin, m_cursor_pos); + calculateScrollPos(); + return true; + } + } + case EMIE_MOUSE_WHEEL: + if (m_vscrollbar && m_vscrollbar->isVisible()) { + s32 pos = m_vscrollbar->getPos(); + s32 step = m_vscrollbar->getSmallStep(); + m_vscrollbar->setPos(pos - event.MouseInput.Wheel * step); + return true; + } + break; + default: + break; + } + + return false; +} + +s32 GUIEditBox::getLineFromPos(s32 pos) +{ + if (!m_word_wrap && !m_multiline) + return 0; + + s32 i = 0; + while (i < (s32)m_broken_text_positions.size()) { + if (m_broken_text_positions[i] > pos) + return i - 1; + ++i; + } + return (s32)m_broken_text_positions.size() - 1; +} + +void GUIEditBox::updateVScrollBar() +{ + if (!m_vscrollbar) { + return; + } + + // OnScrollBarChanged(...) + if (m_vscrollbar->getPos() != m_vscroll_pos) { + s32 deltaScrollY = m_vscrollbar->getPos() - m_vscroll_pos; + m_current_text_rect.UpperLeftCorner.Y -= deltaScrollY; + m_current_text_rect.LowerRightCorner.Y -= deltaScrollY; + + s32 scrollymax = getTextDimension().Height - m_frame_rect.getHeight(); + if (scrollymax != m_vscrollbar->getMax()) { + // manage a newline or a deleted line + m_vscrollbar->setMax(scrollymax); + m_vscrollbar->setPageSize(s32(getTextDimension().Height)); + calculateScrollPos(); + } else { + // manage a newline or a deleted line + m_vscroll_pos = m_vscrollbar->getPos(); + } + } + + // check if a vertical scrollbar is needed ? + if (getTextDimension().Height > (u32)m_frame_rect.getHeight()) { + m_frame_rect.LowerRightCorner.X -= m_scrollbar_width; + + s32 scrollymax = getTextDimension().Height - m_frame_rect.getHeight(); + if (scrollymax != m_vscrollbar->getMax()) { + m_vscrollbar->setMax(scrollymax); + m_vscrollbar->setPageSize(s32(getTextDimension().Height)); + } + + if (!m_vscrollbar->isVisible()) { + m_vscrollbar->setVisible(true); + } + } else { + if (m_vscrollbar->isVisible()) { + m_vscrollbar->setVisible(false); + m_vscroll_pos = 0; + m_vscrollbar->setPos(0); + m_vscrollbar->setMax(1); + m_vscrollbar->setPageSize(s32(getTextDimension().Height)); + } + } +} + +void GUIEditBox::deserializeAttributes( + io::IAttributes *in, io::SAttributeReadWriteOptions *options = 0) +{ + IGUIEditBox::deserializeAttributes(in, options); + + setOverrideColor(in->getAttributeAsColor("OverrideColor")); + enableOverrideColor(in->getAttributeAsBool("OverrideColorEnabled")); + setMax(in->getAttributeAsInt("MaxChars")); + setWordWrap(in->getAttributeAsBool("WordWrap")); + setMultiLine(in->getAttributeAsBool("MultiLine")); + setAutoScroll(in->getAttributeAsBool("AutoScroll")); + core::stringw ch = in->getAttributeAsStringW("PasswordChar"); + + if (ch.empty()) + setPasswordBox(in->getAttributeAsBool("PasswordBox")); + else + setPasswordBox(in->getAttributeAsBool("PasswordBox"), ch[0]); + + setTextAlignment((EGUI_ALIGNMENT)in->getAttributeAsEnumeration( + "HTextAlign", GUIAlignmentNames), + (EGUI_ALIGNMENT)in->getAttributeAsEnumeration( + "VTextAlign", GUIAlignmentNames)); + + setWritable(in->getAttributeAsBool("Writable")); + // setOverrideFont(in->getAttributeAsFont("OverrideFont")); +} + +//! Writes attributes of the element. +void GUIEditBox::serializeAttributes( + io::IAttributes *out, io::SAttributeReadWriteOptions *options = 0) const +{ + // IGUIEditBox::serializeAttributes(out,options); + + out->addBool("OverrideColorEnabled", m_override_color_enabled); + out->addColor("OverrideColor", m_override_color); + // out->addFont("OverrideFont",m_override_font); + out->addInt("MaxChars", m_max); + out->addBool("WordWrap", m_word_wrap); + out->addBool("MultiLine", m_multiline); + out->addBool("AutoScroll", m_autoscroll); + out->addBool("PasswordBox", m_passwordbox); + core::stringw ch = L" "; + ch[0] = m_passwordchar; + out->addString("PasswordChar", ch.c_str()); + out->addEnum("HTextAlign", m_halign, GUIAlignmentNames); + out->addEnum("VTextAlign", m_valign, GUIAlignmentNames); + out->addBool("Writable", m_writable); + + IGUIEditBox::serializeAttributes(out, options); +} diff --git a/src/gui/guiEditBox.h b/src/gui/guiEditBox.h new file mode 100644 index 000000000..c616d75d1 --- /dev/null +++ b/src/gui/guiEditBox.h @@ -0,0 +1,220 @@ +/* +Minetest +Copyright (C) 2021 Minetest + +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.h" +#include "IGUIEditBox.h" +#include "IOSOperator.h" +#include "guiScrollBar.h" +#include <vector> + +using namespace irr; +using namespace irr::gui; + +class GUIEditBox : public IGUIEditBox +{ +public: + GUIEditBox(IGUIEnvironment *environment, IGUIElement *parent, s32 id, + core::rect<s32> rectangle, bool border, bool writable) : + IGUIEditBox(environment, parent, id, rectangle), + m_border(border), m_writable(writable), m_frame_rect(rectangle) + { + } + + virtual ~GUIEditBox(); + + //! Sets another skin independent font. + virtual void setOverrideFont(IGUIFont *font = 0); + + virtual IGUIFont *getOverrideFont() const { return m_override_font; } + + //! Get the font which is used right now for drawing + /** Currently this is the override font when one is set and the + font of the active skin otherwise */ + virtual IGUIFont *getActiveFont() const; + + //! Sets another color for the text. + virtual void setOverrideColor(video::SColor color); + + //! Gets the override color + virtual video::SColor getOverrideColor() const; + + //! Sets if the text should use the overide color or the + //! color in the gui skin. + virtual void enableOverrideColor(bool enable); + + //! Checks if an override color is enabled + /** \return true if the override color is enabled, false otherwise */ + virtual bool isOverrideColorEnabled(void) const + { + return m_override_color_enabled; + } + + //! Enables or disables word wrap for using the edit box as multiline text editor. + virtual void setWordWrap(bool enable); + + //! Checks if word wrap is enabled + //! \return true if word wrap is enabled, false otherwise + virtual bool isWordWrapEnabled() const { return m_word_wrap; } + + //! Turns the border on or off + virtual void setDrawBorder(bool border); + + virtual bool isDrawBorderEnabled() const { return m_border; } + + //! Enables or disables newlines. + /** \param enable: If set to true, the EGET_EDITBOX_ENTER event will not be fired, + instead a newline character will be inserted. */ + virtual void setMultiLine(bool enable); + + //! Checks if multi line editing is enabled + //! \return true if mult-line is enabled, false otherwise + virtual bool isMultiLineEnabled() const { return m_multiline; } + + //! Enables or disables automatic scrolling with cursor position + //! \param enable: If set to true, the text will move around with the cursor + //! position + virtual void setAutoScroll(bool enable); + + //! Checks to see if automatic scrolling is enabled + //! \return true if automatic scrolling is enabled, false if not + virtual bool isAutoScrollEnabled() const { return m_autoscroll; } + + //! Sets whether the edit box is a password box. Setting this to true will + /** disable MultiLine, WordWrap and the ability to copy with ctrl+c or ctrl+x + \param passwordBox: true to enable password, false to disable + \param passwordChar: the character that is displayed instead of letters */ + virtual void setPasswordBox(bool passwordBox, wchar_t passwordChar = L'*'); + + //! Returns true if the edit box is currently a password box. + virtual bool isPasswordBox() const { return m_passwordbox; } + + //! Sets text justification + virtual void setTextAlignment(EGUI_ALIGNMENT horizontal, EGUI_ALIGNMENT vertical); + + //! Sets the new caption of this element. + virtual void setText(const wchar_t *text); + + //! Sets the maximum amount of characters which may be entered in the box. + //! \param max: Maximum amount of characters. If 0, the character amount is + //! infinity. + virtual void setMax(u32 max); + + //! Returns maximum amount of characters, previously set by setMax(); + virtual u32 getMax() const { return m_max; } + + //! Gets the size area of the text in the edit box + //! \return Returns the size in pixels of the text + virtual core::dimension2du getTextDimension(); + + //! set true if this EditBox is writable + virtual void setWritable(bool can_write_text); + + //! called if an event happened. + virtual bool OnEvent(const SEvent &event); + + //! Writes attributes of the element. + virtual void serializeAttributes(io::IAttributes *out, + io::SAttributeReadWriteOptions *options) const; + + //! Reads attributes of the element + virtual void deserializeAttributes( + io::IAttributes *in, io::SAttributeReadWriteOptions *options); + +protected: + virtual void breakText() = 0; + + //! sets the area of the given line + virtual void setTextRect(s32 line) = 0; + + //! set text markers + void setTextMarkers(s32 begin, s32 end); + + //! send some gui event to parent + void sendGuiEvent(EGUI_EVENT_TYPE type); + + //! calculates the current scroll position + virtual void calculateScrollPos() = 0; + + virtual s32 getCursorPos(s32 x, s32 y) = 0; + + bool processKey(const SEvent &event); + virtual void inputChar(wchar_t c); + + //! returns the line number that the cursor is on + s32 getLineFromPos(s32 pos); + + //! update the vertical scrollBar (visibilty & position) + void updateVScrollBar(); + + gui::IGUIFont *m_override_font = nullptr; + + bool m_override_color_enabled = false; + bool m_word_wrap = false; + bool m_multiline = false; + bool m_autoscroll = true; + + bool m_border; + + bool m_passwordbox = false; + wchar_t m_passwordchar = L'*'; + + std::vector<core::stringw> m_broken_text; + std::vector<s32> m_broken_text_positions; + + EGUI_ALIGNMENT m_halign = EGUIA_UPPERLEFT; + EGUI_ALIGNMENT m_valign = EGUIA_CENTER; + + u32 m_blink_start_time = 0; + s32 m_cursor_pos = 0; + s32 m_hscroll_pos = 0; + s32 m_vscroll_pos = 0; // scroll position in characters + u32 m_max = 0; + + video::SColor m_override_color = video::SColor(101, 255, 255, 255); + + core::rect<s32> m_current_text_rect = core::rect<s32>(0, 0, 1, 1); + + bool m_writable; + + bool m_mouse_marking = false; + + s32 m_mark_begin = 0; + s32 m_mark_end = 0; + + gui::IGUIFont *m_last_break_font = nullptr; + IOSOperator *m_operator = nullptr; + + core::rect<s32> m_frame_rect; // temporary values + + u32 m_scrollbar_width = 0; + GUIScrollBar *m_vscrollbar = nullptr; + +private: + bool processMouse(const SEvent &event); + + bool onKeyUp(const SEvent &event, s32 &mark_begin, s32 &mark_end); + bool onKeyDown(const SEvent &event, s32 &mark_begin, s32 &mark_end); + void onKeyControlC(const SEvent &event); + bool onKeyControlX(const SEvent &event, s32 &mark_begin, s32 &mark_end); + bool onKeyControlV(const SEvent &event, s32 &mark_begin, s32 &mark_end); + bool onKeyBack(const SEvent &event, s32 &mark_begin, s32 &mark_end); + bool onKeyDelete(const SEvent &event, s32 &mark_begin, s32 &mark_end); +}; diff --git a/src/gui/guiEditBoxWithScrollbar.cpp b/src/gui/guiEditBoxWithScrollbar.cpp index 442406688..c72070787 100644 --- a/src/gui/guiEditBoxWithScrollbar.cpp +++ b/src/gui/guiEditBoxWithScrollbar.cpp @@ -22,20 +22,12 @@ optional? dragging selected text numerical */ - //! constructor GUIEditBoxWithScrollBar::GUIEditBoxWithScrollBar(const wchar_t* text, bool border, IGUIEnvironment* environment, IGUIElement* parent, s32 id, const core::rect<s32>& rectangle, bool writable, bool has_vscrollbar) - : IGUIEditBox(environment, parent, id, rectangle), m_mouse_marking(false), - m_border(border), m_background(true), m_override_color_enabled(false), m_mark_begin(0), m_mark_end(0), - m_override_color(video::SColor(101, 255, 255, 255)), m_override_font(0), m_last_break_font(0), - m_operator(0), m_blink_start_time(0), m_cursor_pos(0), m_hscroll_pos(0), m_vscroll_pos(0), m_max(0), - m_word_wrap(false), m_multiline(false), m_autoscroll(true), m_passwordbox(false), - m_passwordchar(L'*'), m_halign(EGUIA_UPPERLEFT), m_valign(EGUIA_CENTER), - m_current_text_rect(0, 0, 1, 1), m_frame_rect(rectangle), - m_scrollbar_width(0), m_vscrollbar(NULL), m_writable(writable), - m_bg_color_used(false) + : GUIEditBox(environment, parent, id, rectangle, border, writable), + m_background(true), m_bg_color_used(false) { #ifdef _DEBUG setDebugName("GUIEditBoxWithScrollBar"); @@ -65,99 +57,12 @@ GUIEditBoxWithScrollBar::GUIEditBoxWithScrollBar(const wchar_t* text, bool borde setWritable(writable); } - -//! destructor -GUIEditBoxWithScrollBar::~GUIEditBoxWithScrollBar() -{ - if (m_override_font) - m_override_font->drop(); - - if (m_operator) - m_operator->drop(); - - if (m_vscrollbar) - m_vscrollbar->drop(); -} - - -//! Sets another skin independent font. -void GUIEditBoxWithScrollBar::setOverrideFont(IGUIFont* font) -{ - if (m_override_font == font) - return; - - if (m_override_font) - m_override_font->drop(); - - m_override_font = font; - - if (m_override_font) - m_override_font->grab(); - - breakText(); -} - -//! Gets the override font (if any) -IGUIFont * GUIEditBoxWithScrollBar::getOverrideFont() const -{ - return m_override_font; -} - -//! Get the font which is used right now for drawing -IGUIFont* GUIEditBoxWithScrollBar::getActiveFont() const -{ - if (m_override_font) - return m_override_font; - IGUISkin* skin = Environment->getSkin(); - if (skin) - return skin->getFont(); - return 0; -} - -//! Sets another color for the text. -void GUIEditBoxWithScrollBar::setOverrideColor(video::SColor color) -{ - m_override_color = color; - m_override_color_enabled = true; -} - - -video::SColor GUIEditBoxWithScrollBar::getOverrideColor() const -{ - return m_override_color; -} - - -//! Turns the border on or off -void GUIEditBoxWithScrollBar::setDrawBorder(bool border) -{ - m_border = border; -} - //! Sets whether to draw the background void GUIEditBoxWithScrollBar::setDrawBackground(bool draw) { m_background = draw; } -//! Sets if the text should use the overide color or the color in the gui skin. -void GUIEditBoxWithScrollBar::enableOverrideColor(bool enable) -{ - m_override_color_enabled = enable; -} - -bool GUIEditBoxWithScrollBar::isOverrideColorEnabled() const -{ - return m_override_color_enabled; -} - -//! Enables or disables word wrap -void GUIEditBoxWithScrollBar::setWordWrap(bool enable) -{ - m_word_wrap = enable; - breakText(); -} - void GUIEditBoxWithScrollBar::updateAbsolutePosition() { @@ -170,486 +75,6 @@ void GUIEditBoxWithScrollBar::updateAbsolutePosition() } } -//! Checks if word wrap is enabled -bool GUIEditBoxWithScrollBar::isWordWrapEnabled() const -{ - return m_word_wrap; -} - - -//! Enables or disables newlines. -void GUIEditBoxWithScrollBar::setMultiLine(bool enable) -{ - m_multiline = enable; -} - - -//! Checks if multi line editing is enabled -bool GUIEditBoxWithScrollBar::isMultiLineEnabled() const -{ - return m_multiline; -} - - -void GUIEditBoxWithScrollBar::setPasswordBox(bool password_box, wchar_t password_char) -{ - m_passwordbox = password_box; - if (m_passwordbox) { - m_passwordchar = password_char; - setMultiLine(false); - setWordWrap(false); - m_broken_text.clear(); - } -} - - -bool GUIEditBoxWithScrollBar::isPasswordBox() const -{ - return m_passwordbox; -} - - -//! Sets text justification -void GUIEditBoxWithScrollBar::setTextAlignment(EGUI_ALIGNMENT horizontal, EGUI_ALIGNMENT vertical) -{ - m_halign = horizontal; - m_valign = vertical; -} - - -//! called if an event happened. -bool GUIEditBoxWithScrollBar::OnEvent(const SEvent& event) -{ - if (isEnabled()) { - switch (event.EventType) - { - case EET_GUI_EVENT: - if (event.GUIEvent.EventType == EGET_ELEMENT_FOCUS_LOST) { - if (event.GUIEvent.Caller == this) { - m_mouse_marking = false; - setTextMarkers(0, 0); - } - } - break; - case EET_KEY_INPUT_EVENT: - if (processKey(event)) - return true; - break; - case EET_MOUSE_INPUT_EVENT: - if (processMouse(event)) - return true; - break; - default: - break; - } - } - - return IGUIElement::OnEvent(event); -} - - -bool GUIEditBoxWithScrollBar::processKey(const SEvent& event) -{ - if (!m_writable) { - return false; - } - - if (!event.KeyInput.PressedDown) - return false; - - bool text_changed = false; - s32 new_mark_begin = m_mark_begin; - s32 new_mark_end = m_mark_end; - - // control shortcut handling - - if (event.KeyInput.Control) { - - // german backlash '\' entered with control + '?' - if (event.KeyInput.Char == '\\') { - inputChar(event.KeyInput.Char); - return true; - } - - switch (event.KeyInput.Key) { - case KEY_KEY_A: - // select all - new_mark_begin = 0; - new_mark_end = Text.size(); - break; - case KEY_KEY_C: - // copy to clipboard - if (!m_passwordbox && m_operator && m_mark_begin != m_mark_end) - { - const s32 realmbgn = m_mark_begin < m_mark_end ? m_mark_begin : m_mark_end; - const s32 realmend = m_mark_begin < m_mark_end ? m_mark_end : m_mark_begin; - - core::stringc s; - s = Text.subString(realmbgn, realmend - realmbgn).c_str(); - m_operator->copyToClipboard(s.c_str()); - } - break; - case KEY_KEY_X: - // cut to the clipboard - if (!m_passwordbox && m_operator && m_mark_begin != m_mark_end) { - const s32 realmbgn = m_mark_begin < m_mark_end ? m_mark_begin : m_mark_end; - const s32 realmend = m_mark_begin < m_mark_end ? m_mark_end : m_mark_begin; - - // copy - core::stringc sc; - sc = Text.subString(realmbgn, realmend - realmbgn).c_str(); - m_operator->copyToClipboard(sc.c_str()); - - if (isEnabled()) - { - // delete - core::stringw s; - s = Text.subString(0, realmbgn); - s.append(Text.subString(realmend, Text.size() - realmend)); - Text = s; - - m_cursor_pos = realmbgn; - new_mark_begin = 0; - new_mark_end = 0; - text_changed = true; - } - } - break; - case KEY_KEY_V: - if (!isEnabled()) - break; - - // paste from the clipboard - if (m_operator) { - const s32 realmbgn = m_mark_begin < m_mark_end ? m_mark_begin : m_mark_end; - const s32 realmend = m_mark_begin < m_mark_end ? m_mark_end : m_mark_begin; - - // add new character - const c8* p = m_operator->getTextFromClipboard(); - if (p) { - if (m_mark_begin == m_mark_end) { - // insert text - core::stringw s = Text.subString(0, m_cursor_pos); - s.append(p); - s.append(Text.subString(m_cursor_pos, Text.size() - m_cursor_pos)); - - if (!m_max || s.size() <= m_max) // thx to Fish FH for fix - { - Text = s; - s = p; - m_cursor_pos += s.size(); - } - } else { - // replace text - - core::stringw s = Text.subString(0, realmbgn); - s.append(p); - s.append(Text.subString(realmend, Text.size() - realmend)); - - if (!m_max || s.size() <= m_max) // thx to Fish FH for fix - { - Text = s; - s = p; - m_cursor_pos = realmbgn + s.size(); - } - } - } - - new_mark_begin = 0; - new_mark_end = 0; - text_changed = true; - } - break; - case KEY_HOME: - // move/highlight to start of text - if (event.KeyInput.Shift) { - new_mark_end = m_cursor_pos; - new_mark_begin = 0; - m_cursor_pos = 0; - } else { - m_cursor_pos = 0; - new_mark_begin = 0; - new_mark_end = 0; - } - break; - case KEY_END: - // move/highlight to end of text - if (event.KeyInput.Shift) { - new_mark_begin = m_cursor_pos; - new_mark_end = Text.size(); - m_cursor_pos = 0; - } else { - m_cursor_pos = Text.size(); - new_mark_begin = 0; - new_mark_end = 0; - } - break; - default: - return false; - } - } - // default keyboard handling - else - switch (event.KeyInput.Key) { - case KEY_END: - { - s32 p = Text.size(); - if (m_word_wrap || m_multiline) { - p = getLineFromPos(m_cursor_pos); - p = m_broken_text_positions[p] + (s32)m_broken_text[p].size(); - if (p > 0 && (Text[p - 1] == L'\r' || Text[p - 1] == L'\n')) - p -= 1; - } - - if (event.KeyInput.Shift) { - if (m_mark_begin == m_mark_end) - new_mark_begin = m_cursor_pos; - - new_mark_end = p; - } else { - new_mark_begin = 0; - new_mark_end = 0; - } - m_cursor_pos = p; - m_blink_start_time = porting::getTimeMs(); - } - break; - case KEY_HOME: - { - - s32 p = 0; - if (m_word_wrap || m_multiline) { - p = getLineFromPos(m_cursor_pos); - p = m_broken_text_positions[p]; - } - - if (event.KeyInput.Shift) { - if (m_mark_begin == m_mark_end) - new_mark_begin = m_cursor_pos; - new_mark_end = p; - } else { - new_mark_begin = 0; - new_mark_end = 0; - } - m_cursor_pos = p; - m_blink_start_time = porting::getTimeMs(); - } - break; - case KEY_RETURN: - if (m_multiline) { - inputChar(L'\n'); - } else { - calculateScrollPos(); - sendGuiEvent(EGET_EDITBOX_ENTER); - } - return true; - case KEY_LEFT: - - if (event.KeyInput.Shift) { - if (m_cursor_pos > 0) { - if (m_mark_begin == m_mark_end) - new_mark_begin = m_cursor_pos; - - new_mark_end = m_cursor_pos - 1; - } - } else { - new_mark_begin = 0; - new_mark_end = 0; - } - - if (m_cursor_pos > 0) - m_cursor_pos--; - m_blink_start_time = porting::getTimeMs(); - break; - - case KEY_RIGHT: - if (event.KeyInput.Shift) { - if (Text.size() > (u32)m_cursor_pos) { - if (m_mark_begin == m_mark_end) - new_mark_begin = m_cursor_pos; - - new_mark_end = m_cursor_pos + 1; - } - } else { - new_mark_begin = 0; - new_mark_end = 0; - } - - if (Text.size() > (u32)m_cursor_pos) - m_cursor_pos++; - m_blink_start_time = porting::getTimeMs(); - break; - case KEY_UP: - if (m_multiline || (m_word_wrap && m_broken_text.size() > 1)) { - s32 lineNo = getLineFromPos(m_cursor_pos); - s32 mb = (m_mark_begin == m_mark_end) ? m_cursor_pos : (m_mark_begin > m_mark_end ? m_mark_begin : m_mark_end); - if (lineNo > 0) { - s32 cp = m_cursor_pos - m_broken_text_positions[lineNo]; - if ((s32)m_broken_text[lineNo - 1].size() < cp) - m_cursor_pos = m_broken_text_positions[lineNo - 1] + core::max_((u32)1, m_broken_text[lineNo - 1].size()) - 1; - else - m_cursor_pos = m_broken_text_positions[lineNo - 1] + cp; - } - - if (event.KeyInput.Shift) { - new_mark_begin = mb; - new_mark_end = m_cursor_pos; - } else { - new_mark_begin = 0; - new_mark_end = 0; - } - } else { - return false; - } - break; - case KEY_DOWN: - if (m_multiline || (m_word_wrap && m_broken_text.size() > 1)) { - s32 lineNo = getLineFromPos(m_cursor_pos); - s32 mb = (m_mark_begin == m_mark_end) ? m_cursor_pos : (m_mark_begin < m_mark_end ? m_mark_begin : m_mark_end); - if (lineNo < (s32)m_broken_text.size() - 1) - { - s32 cp = m_cursor_pos - m_broken_text_positions[lineNo]; - if ((s32)m_broken_text[lineNo + 1].size() < cp) - m_cursor_pos = m_broken_text_positions[lineNo + 1] + core::max_((u32)1, m_broken_text[lineNo + 1].size()) - 1; - else - m_cursor_pos = m_broken_text_positions[lineNo + 1] + cp; - } - - if (event.KeyInput.Shift) { - new_mark_begin = mb; - new_mark_end = m_cursor_pos; - } else { - new_mark_begin = 0; - new_mark_end = 0; - } - - } else { - return false; - } - break; - - case KEY_BACK: - if (!isEnabled()) - break; - - if (Text.size()) { - core::stringw s; - - if (m_mark_begin != m_mark_end) { - // delete marked text - const s32 realmbgn = m_mark_begin < m_mark_end ? m_mark_begin : m_mark_end; - const s32 realmend = m_mark_begin < m_mark_end ? m_mark_end : m_mark_begin; - - s = Text.subString(0, realmbgn); - s.append(Text.subString(realmend, Text.size() - realmend)); - Text = s; - - m_cursor_pos = realmbgn; - } else { - // delete text behind cursor - if (m_cursor_pos > 0) - s = Text.subString(0, m_cursor_pos - 1); - else - s = L""; - s.append(Text.subString(m_cursor_pos, Text.size() - m_cursor_pos)); - Text = s; - --m_cursor_pos; - } - - if (m_cursor_pos < 0) - m_cursor_pos = 0; - m_blink_start_time = porting::getTimeMs(); // os::Timer::getTime(); - new_mark_begin = 0; - new_mark_end = 0; - text_changed = true; - } - break; - case KEY_DELETE: - if (!isEnabled()) - break; - - if (Text.size() != 0) { - core::stringw s; - - if (m_mark_begin != m_mark_end) { - // delete marked text - const s32 realmbgn = m_mark_begin < m_mark_end ? m_mark_begin : m_mark_end; - const s32 realmend = m_mark_begin < m_mark_end ? m_mark_end : m_mark_begin; - - s = Text.subString(0, realmbgn); - s.append(Text.subString(realmend, Text.size() - realmend)); - Text = s; - - m_cursor_pos = realmbgn; - } else { - // delete text before cursor - s = Text.subString(0, m_cursor_pos); - s.append(Text.subString(m_cursor_pos + 1, Text.size() - m_cursor_pos - 1)); - Text = s; - } - - if (m_cursor_pos > (s32)Text.size()) - m_cursor_pos = (s32)Text.size(); - - m_blink_start_time = porting::getTimeMs(); // os::Timer::getTime(); - new_mark_begin = 0; - new_mark_end = 0; - text_changed = true; - } - break; - - case KEY_ESCAPE: - case KEY_TAB: - case KEY_SHIFT: - case KEY_F1: - case KEY_F2: - case KEY_F3: - case KEY_F4: - case KEY_F5: - case KEY_F6: - case KEY_F7: - case KEY_F8: - case KEY_F9: - case KEY_F10: - case KEY_F11: - case KEY_F12: - case KEY_F13: - case KEY_F14: - case KEY_F15: - case KEY_F16: - case KEY_F17: - case KEY_F18: - case KEY_F19: - case KEY_F20: - case KEY_F21: - case KEY_F22: - case KEY_F23: - case KEY_F24: - // ignore these keys - return false; - - default: - inputChar(event.KeyInput.Char); - return true; - } - - // Set new text markers - setTextMarkers(new_mark_begin, new_mark_end); - - // break the text if it has changed - if (text_changed) { - breakText(); - calculateScrollPos(); - sendGuiEvent(EGET_EDITBOX_CHANGED); - } - else - { - calculateScrollPos(); - } - - return true; -} - //! draws the element and its children void GUIEditBoxWithScrollBar::draw() @@ -839,131 +264,6 @@ void GUIEditBoxWithScrollBar::draw() } -//! Sets the new caption of this element. -void GUIEditBoxWithScrollBar::setText(const wchar_t* text) -{ - Text = text; - if (u32(m_cursor_pos) > Text.size()) - m_cursor_pos = Text.size(); - m_hscroll_pos = 0; - breakText(); -} - - -//! Enables or disables automatic scrolling with cursor position -//! \param enable: If set to true, the text will move around with the cursor position -void GUIEditBoxWithScrollBar::setAutoScroll(bool enable) -{ - m_autoscroll = enable; -} - - -//! Checks to see if automatic scrolling is enabled -//! \return true if automatic scrolling is enabled, false if not -bool GUIEditBoxWithScrollBar::isAutoScrollEnabled() const -{ - return m_autoscroll; -} - - -//! Gets the area of the text in the edit box -//! \return Returns the size in pixels of the text -core::dimension2du GUIEditBoxWithScrollBar::getTextDimension() -{ - core::rect<s32> ret; - - setTextRect(0); - ret = m_current_text_rect; - - for (u32 i = 1; i < m_broken_text.size(); ++i) { - setTextRect(i); - ret.addInternalPoint(m_current_text_rect.UpperLeftCorner); - ret.addInternalPoint(m_current_text_rect.LowerRightCorner); - } - - return core::dimension2du(ret.getSize()); -} - - -//! Sets the maximum amount of characters which may be entered in the box. -//! \param max: Maximum amount of characters. If 0, the character amount is -//! infinity. -void GUIEditBoxWithScrollBar::setMax(u32 max) -{ - m_max = max; - - if (Text.size() > m_max && m_max != 0) - Text = Text.subString(0, m_max); -} - - -//! Returns maximum amount of characters, previously set by setMax(); -u32 GUIEditBoxWithScrollBar::getMax() const -{ - return m_max; -} - - -bool GUIEditBoxWithScrollBar::processMouse(const SEvent& event) -{ - switch (event.MouseInput.Event) - { - case irr::EMIE_LMOUSE_LEFT_UP: - if (Environment->hasFocus(this)) { - m_cursor_pos = getCursorPos(event.MouseInput.X, event.MouseInput.Y); - if (m_mouse_marking) { - setTextMarkers(m_mark_begin, m_cursor_pos); - } - m_mouse_marking = false; - calculateScrollPos(); - return true; - } - break; - case irr::EMIE_MOUSE_MOVED: - { - if (m_mouse_marking) { - m_cursor_pos = getCursorPos(event.MouseInput.X, event.MouseInput.Y); - setTextMarkers(m_mark_begin, m_cursor_pos); - calculateScrollPos(); - return true; - } - } - break; - case EMIE_LMOUSE_PRESSED_DOWN: - - if (!Environment->hasFocus(this)) { - m_blink_start_time = porting::getTimeMs(); - m_mouse_marking = true; - m_cursor_pos = getCursorPos(event.MouseInput.X, event.MouseInput.Y); - setTextMarkers(m_cursor_pos, m_cursor_pos); - calculateScrollPos(); - return true; - } else { - if (!AbsoluteClippingRect.isPointInside( - core::position2d<s32>(event.MouseInput.X, event.MouseInput.Y))) { - return false; - } else { - // move cursor - m_cursor_pos = getCursorPos(event.MouseInput.X, event.MouseInput.Y); - - s32 newMarkBegin = m_mark_begin; - if (!m_mouse_marking) - newMarkBegin = m_cursor_pos; - - m_mouse_marking = true; - setTextMarkers(newMarkBegin, m_cursor_pos); - calculateScrollPos(); - return true; - } - } - default: - break; - } - - return false; -} - - s32 GUIEditBoxWithScrollBar::getCursorPos(s32 x, s32 y) { IGUIFont* font = getActiveFont(); @@ -1028,7 +328,7 @@ void GUIEditBoxWithScrollBar::breakText() s32 last_line_start = 0; s32 size = Text.size(); s32 length = 0; - s32 el_width = RelativeRect.getWidth() - 6; + s32 el_width = RelativeRect.getWidth() - m_scrollbar_width - 10; wchar_t c; for (s32 i = 0; i < size; ++i) { @@ -1181,59 +481,6 @@ void GUIEditBoxWithScrollBar::setTextRect(s32 line) m_current_text_rect += m_frame_rect.UpperLeftCorner; } - -s32 GUIEditBoxWithScrollBar::getLineFromPos(s32 pos) -{ - if (!m_word_wrap && !m_multiline) - return 0; - - s32 i = 0; - while (i < (s32)m_broken_text_positions.size()) { - if (m_broken_text_positions[i] > pos) - return i - 1; - ++i; - } - return (s32)m_broken_text_positions.size() - 1; -} - - -void GUIEditBoxWithScrollBar::inputChar(wchar_t c) -{ - if (!isEnabled()) - return; - - if (c != 0) { - if (Text.size() < m_max || m_max == 0) { - core::stringw s; - - if (m_mark_begin != m_mark_end) { - // replace marked text - const s32 realmbgn = m_mark_begin < m_mark_end ? m_mark_begin : m_mark_end; - const s32 realmend = m_mark_begin < m_mark_end ? m_mark_end : m_mark_begin; - - s = Text.subString(0, realmbgn); - s.append(c); - s.append(Text.subString(realmend, Text.size() - realmend)); - Text = s; - m_cursor_pos = realmbgn + 1; - } else { - // add new character - s = Text.subString(0, m_cursor_pos); - s.append(c); - s.append(Text.subString(m_cursor_pos, Text.size() - m_cursor_pos)); - Text = s; - ++m_cursor_pos; - } - - m_blink_start_time = porting::getTimeMs(); - setTextMarkers(0, 0); - } - } - breakText(); - calculateScrollPos(); - sendGuiEvent(EGET_EDITBOX_CHANGED); -} - // calculate autoscroll void GUIEditBoxWithScrollBar::calculateScrollPos() { @@ -1366,30 +613,6 @@ void GUIEditBoxWithScrollBar::calculateFrameRect() updateVScrollBar(); } -//! set text markers -void GUIEditBoxWithScrollBar::setTextMarkers(s32 begin, s32 end) -{ - if (begin != m_mark_begin || end != m_mark_end) { - m_mark_begin = begin; - m_mark_end = end; - sendGuiEvent(EGET_EDITBOX_MARKING_CHANGED); - } -} - -//! send some gui event to parent -void GUIEditBoxWithScrollBar::sendGuiEvent(EGUI_EVENT_TYPE type) -{ - if (Parent) { - SEvent e; - e.EventType = EET_GUI_EVENT; - e.GUIEvent.Caller = this; - e.GUIEvent.Element = 0; - e.GUIEvent.EventType = type; - - Parent->OnEvent(e); - } -} - //! create a vertical scroll bar void GUIEditBoxWithScrollBar::createVScrollBar() { @@ -1399,8 +622,6 @@ void GUIEditBoxWithScrollBar::createVScrollBar() m_scrollbar_width = skin ? skin->getSize(gui::EGDS_SCROLLBAR_SIZE) : 16; - RelativeRect.LowerRightCorner.X -= m_scrollbar_width + 4; - irr::core::rect<s32> scrollbarrect = m_frame_rect; scrollbarrect.UpperLeftCorner.X += m_frame_rect.getWidth() - m_scrollbar_width; m_vscrollbar = new GUIScrollBar(Environment, getParent(), -1, @@ -1411,61 +632,7 @@ void GUIEditBoxWithScrollBar::createVScrollBar() m_vscrollbar->setLargeStep(1); } -void GUIEditBoxWithScrollBar::updateVScrollBar() -{ - if (!m_vscrollbar) { - return; - } - // OnScrollBarChanged(...) - if (m_vscrollbar->getPos() != m_vscroll_pos) { - s32 deltaScrollY = m_vscrollbar->getPos() - m_vscroll_pos; - m_current_text_rect.UpperLeftCorner.Y -= deltaScrollY; - m_current_text_rect.LowerRightCorner.Y -= deltaScrollY; - - s32 scrollymax = getTextDimension().Height - m_frame_rect.getHeight(); - if (scrollymax != m_vscrollbar->getMax()) { - // manage a newline or a deleted line - m_vscrollbar->setMax(scrollymax); - m_vscrollbar->setPageSize(s32(getTextDimension().Height)); - calculateScrollPos(); - } else { - // manage a newline or a deleted line - m_vscroll_pos = m_vscrollbar->getPos(); - } - } - - // check if a vertical scrollbar is needed ? - if (getTextDimension().Height > (u32) m_frame_rect.getHeight()) { - m_frame_rect.LowerRightCorner.X -= m_scrollbar_width; - - s32 scrollymax = getTextDimension().Height - m_frame_rect.getHeight(); - if (scrollymax != m_vscrollbar->getMax()) { - m_vscrollbar->setMax(scrollymax); - m_vscrollbar->setPageSize(s32(getTextDimension().Height)); - } - - if (!m_vscrollbar->isVisible()) { - m_vscrollbar->setVisible(true); - } - } else { - if (m_vscrollbar->isVisible()) - { - m_vscrollbar->setVisible(false); - m_vscroll_pos = 0; - m_vscrollbar->setPos(0); - m_vscrollbar->setMax(1); - m_vscrollbar->setPageSize(s32(getTextDimension().Height)); - } - } - -} - -//! set true if this editbox is writable -void GUIEditBoxWithScrollBar::setWritable(bool writable) -{ - m_writable = writable; -} //! Change the background color void GUIEditBoxWithScrollBar::setBackgroundColor(const video::SColor &bg_color) @@ -1477,54 +644,21 @@ void GUIEditBoxWithScrollBar::setBackgroundColor(const video::SColor &bg_color) //! Writes attributes of the element. void GUIEditBoxWithScrollBar::serializeAttributes(io::IAttributes* out, io::SAttributeReadWriteOptions* options = 0) const { - // IGUIEditBox::serializeAttributes(out,options); - out->addBool("Border", m_border); out->addBool("Background", m_background); - out->addBool("OverrideColorEnabled", m_override_color_enabled); - out->addColor("OverrideColor", m_override_color); // out->addFont("OverrideFont", OverrideFont); - out->addInt("MaxChars", m_max); - out->addBool("WordWrap", m_word_wrap); - out->addBool("MultiLine", m_multiline); - out->addBool("AutoScroll", m_autoscroll); - out->addBool("PasswordBox", m_passwordbox); - core::stringw ch = L" "; - ch[0] = m_passwordchar; - out->addString("PasswordChar", ch.c_str()); - out->addEnum("HTextAlign", m_halign, GUIAlignmentNames); - out->addEnum("VTextAlign", m_valign, GUIAlignmentNames); - out->addBool("Writable", m_writable); - - IGUIEditBox::serializeAttributes(out, options); + + GUIEditBox::serializeAttributes(out, options); } //! Reads attributes of the element void GUIEditBoxWithScrollBar::deserializeAttributes(io::IAttributes* in, io::SAttributeReadWriteOptions* options = 0) { - IGUIEditBox::deserializeAttributes(in, options); + GUIEditBox::deserializeAttributes(in, options); setDrawBorder(in->getAttributeAsBool("Border")); setDrawBackground(in->getAttributeAsBool("Background")); - setOverrideColor(in->getAttributeAsColor("OverrideColor")); - enableOverrideColor(in->getAttributeAsBool("OverrideColorEnabled")); - setMax(in->getAttributeAsInt("MaxChars")); - setWordWrap(in->getAttributeAsBool("WordWrap")); - setMultiLine(in->getAttributeAsBool("MultiLine")); - setAutoScroll(in->getAttributeAsBool("AutoScroll")); - core::stringw ch = in->getAttributeAsStringW("PasswordChar"); - - if (!ch.size()) - setPasswordBox(in->getAttributeAsBool("PasswordBox")); - else - setPasswordBox(in->getAttributeAsBool("PasswordBox"), ch[0]); - - setTextAlignment((EGUI_ALIGNMENT)in->getAttributeAsEnumeration("HTextAlign", GUIAlignmentNames), - (EGUI_ALIGNMENT)in->getAttributeAsEnumeration("VTextAlign", GUIAlignmentNames)); - - // setOverrideFont(in->getAttributeAsFont("OverrideFont")); - setWritable(in->getAttributeAsBool("Writable")); } bool GUIEditBoxWithScrollBar::isDrawBackgroundEnabled() const { return false; } diff --git a/src/gui/guiEditBoxWithScrollbar.h b/src/gui/guiEditBoxWithScrollbar.h index 77538e2f7..3f7450dcb 100644 --- a/src/gui/guiEditBoxWithScrollbar.h +++ b/src/gui/guiEditBoxWithScrollbar.h @@ -5,15 +5,9 @@ #ifndef GUIEDITBOXWITHSCROLLBAR_HEADER #define GUIEDITBOXWITHSCROLLBAR_HEADER -#include "IGUIEditBox.h" -#include "IOSOperator.h" -#include "guiScrollBar.h" -#include <vector> +#include "guiEditBox.h" -using namespace irr; -using namespace irr::gui; - -class GUIEditBoxWithScrollBar : public IGUIEditBox +class GUIEditBoxWithScrollBar : public GUIEditBox { public: @@ -23,102 +17,17 @@ public: bool writable = true, bool has_vscrollbar = true); //! destructor - virtual ~GUIEditBoxWithScrollBar(); - - //! Sets another skin independent font. - virtual void setOverrideFont(IGUIFont* font = 0); - - //! Gets the override font (if any) - /** \return The override font (may be 0) */ - virtual IGUIFont* getOverrideFont() const; - - //! Get the font which is used right now for drawing - /** Currently this is the override font when one is set and the - font of the active skin otherwise */ - virtual IGUIFont* getActiveFont() const; - - //! Sets another color for the text. - virtual void setOverrideColor(video::SColor color); - - //! Gets the override color - virtual video::SColor getOverrideColor() const; - - //! Sets if the text should use the overide color or the - //! color in the gui skin. - virtual void enableOverrideColor(bool enable); - - //! Checks if an override color is enabled - /** \return true if the override color is enabled, false otherwise */ - virtual bool isOverrideColorEnabled(void) const; + virtual ~GUIEditBoxWithScrollBar() {} //! Sets whether to draw the background virtual void setDrawBackground(bool draw); - //! Turns the border on or off - virtual void setDrawBorder(bool border); - - //! Enables or disables word wrap for using the edit box as multiline text editor. - virtual void setWordWrap(bool enable); - - //! Checks if word wrap is enabled - //! \return true if word wrap is enabled, false otherwise - virtual bool isWordWrapEnabled() const; - - //! Enables or disables newlines. - /** \param enable: If set to true, the EGET_EDITBOX_ENTER event will not be fired, - instead a newline character will be inserted. */ - virtual void setMultiLine(bool enable); - - //! Checks if multi line editing is enabled - //! \return true if mult-line is enabled, false otherwise - virtual bool isMultiLineEnabled() const; - - //! Enables or disables automatic scrolling with cursor position - //! \param enable: If set to true, the text will move around with the cursor position - virtual void setAutoScroll(bool enable); - - //! Checks to see if automatic scrolling is enabled - //! \return true if automatic scrolling is enabled, false if not - virtual bool isAutoScrollEnabled() const; - - //! Gets the size area of the text in the edit box - //! \return Returns the size in pixels of the text - virtual core::dimension2du getTextDimension(); - - //! Sets text justification - virtual void setTextAlignment(EGUI_ALIGNMENT horizontal, EGUI_ALIGNMENT vertical); - - //! called if an event happened. - virtual bool OnEvent(const SEvent& event); - //! draws the element and its children virtual void draw(); - //! Sets the new caption of this element. - virtual void setText(const wchar_t* text); - - //! Sets the maximum amount of characters which may be entered in the box. - //! \param max: Maximum amount of characters. If 0, the character amount is - //! infinity. - virtual void setMax(u32 max); - - //! Returns maximum amount of characters, previously set by setMax(); - virtual u32 getMax() const; - - //! Sets whether the edit box is a password box. Setting this to true will - /** disable MultiLine, WordWrap and the ability to copy with ctrl+c or ctrl+x - \param passwordBox: true to enable password, false to disable - \param passwordChar: the character that is displayed instead of letters */ - virtual void setPasswordBox(bool passwordBox, wchar_t passwordChar = L'*'); - - //! Returns true if the edit box is currently a password box. - virtual bool isPasswordBox() const; - //! Updates the absolute position, splits text if required virtual void updateAbsolutePosition(); - virtual void setWritable(bool writable); - //! Change the background color virtual void setBackgroundColor(const video::SColor &bg_color); @@ -137,58 +46,19 @@ public: protected: //! Breaks the single text line. - void breakText(); + virtual void breakText(); //! sets the area of the given line - void setTextRect(s32 line); - //! returns the line number that the cursor is on - s32 getLineFromPos(s32 pos); - //! adds a letter to the edit box - void inputChar(wchar_t c); + virtual void setTextRect(s32 line); //! calculates the current scroll position void calculateScrollPos(); //! calculated the FrameRect void calculateFrameRect(); - //! send some gui event to parent - void sendGuiEvent(EGUI_EVENT_TYPE type); - //! set text markers - void setTextMarkers(s32 begin, s32 end); //! create a Vertical ScrollBar void createVScrollBar(); - //! update the vertical scrollBar (visibilty & position) - void updateVScrollBar(); - bool processKey(const SEvent& event); - bool processMouse(const SEvent& event); s32 getCursorPos(s32 x, s32 y); - bool m_mouse_marking; - bool m_border; bool m_background; - bool m_override_color_enabled; - s32 m_mark_begin; - s32 m_mark_end; - - video::SColor m_override_color; - gui::IGUIFont *m_override_font, *m_last_break_font; - IOSOperator* m_operator; - - u32 m_blink_start_time; - s32 m_cursor_pos; - s32 m_hscroll_pos, m_vscroll_pos; // scroll position in characters - u32 m_max; - - bool m_word_wrap, m_multiline, m_autoscroll, m_passwordbox; - wchar_t m_passwordchar; - EGUI_ALIGNMENT m_halign, m_valign; - - std::vector<core::stringw> m_broken_text; - std::vector<s32> m_broken_text_positions; - - core::rect<s32> m_current_text_rect, m_frame_rect; // temporary values - - u32 m_scrollbar_width; - GUIScrollBar *m_vscrollbar; - bool m_writable; bool m_bg_color_used; video::SColor m_bg_color; diff --git a/src/gui/guiEngine.cpp b/src/gui/guiEngine.cpp index b40707d01..93463ad70 100644 --- a/src/gui/guiEngine.cpp +++ b/src/gui/guiEngine.cpp @@ -75,8 +75,6 @@ video::ITexture *MenuTextureSource::getTexture(const std::string &name, u32 *id) if (name.empty()) return NULL; - m_to_delete.insert(name); - #if ENABLE_GLES video::ITexture *retval = m_driver->findTexture(name.c_str()); if (retval) @@ -88,6 +86,7 @@ video::ITexture *MenuTextureSource::getTexture(const std::string &name, u32 *id) image = Align2Npot2(image, m_driver); retval = m_driver->addTexture(name.c_str(), image); + m_to_delete.insert(name); image->drop(); return retval; #else @@ -170,6 +169,7 @@ GUIEngine::GUIEngine(JoystickController *joystick, m_menumanager, NULL /* &client */, m_texture_source, + m_sound_manager, m_formspecgui, m_buttonhandler, "", @@ -297,10 +297,14 @@ void GUIEngine::run() driver->endScene(); + IrrlichtDevice *device = RenderingEngine::get_raw_device(); + u32 frametime_min = 1000 / (device->isWindowFocused() + ? g_settings->getFloat("fps_max") + : g_settings->getFloat("fps_max_unfocused")); if (m_clouds_enabled) - cloudPostProcess(); + cloudPostProcess(frametime_min, device); else - sleep_ms(25); + sleep_ms(frametime_min); m_script->step(); @@ -367,9 +371,8 @@ void GUIEngine::cloudPreProcess() } /******************************************************************************/ -void GUIEngine::cloudPostProcess() +void GUIEngine::cloudPostProcess(u32 frametime_min, IrrlichtDevice *device) { - float fps_max = g_settings->getFloat("pause_fps_max"); // Time of frame without fps limit u32 busytime_u32; @@ -380,12 +383,10 @@ void GUIEngine::cloudPostProcess() else busytime_u32 = 0; - // FPS limiter - u32 frametime_min = 1000./fps_max; - + // FPS limit if (busytime_u32 < frametime_min) { u32 sleeptime = frametime_min - busytime_u32; - RenderingEngine::get_raw_device()->sleep(sleeptime); + device->sleep(sleeptime); } } @@ -484,8 +485,6 @@ void GUIEngine::drawHeader(video::IVideoDriver *driver) splashrect += v2s32((screensize.Width/2)-(splashsize.X/2), ((free_space/2)-splashsize.Y/2)+10); - video::SColor bgcolor(255,50,50,50); - draw2DImageFilterScaled(driver, texture, splashrect, core::rect<s32>(core::position2d<s32>(0,0), core::dimension2di(texture->getOriginalSize())), diff --git a/src/gui/guiEngine.h b/src/gui/guiEngine.h index e55531bbc..eef1ad8aa 100644 --- a/src/gui/guiEngine.h +++ b/src/gui/guiEngine.h @@ -23,29 +23,28 @@ with this program; if not, write to the Free Software Foundation, Inc., /* Includes */ /******************************************************************************/ #include "irrlichttypes.h" -#include "modalMenu.h" #include "guiFormSpecMenu.h" #include "client/sound.h" #include "client/tile.h" #include "util/enriched_string.h" /******************************************************************************/ -/* Typedefs and macros */ +/* Structs and macros */ /******************************************************************************/ /** texture layer ids */ -typedef enum { +enum texture_layer { TEX_LAYER_BACKGROUND = 0, TEX_LAYER_OVERLAY, TEX_LAYER_HEADER, TEX_LAYER_FOOTER, TEX_LAYER_MAX -} texture_layer; +}; -typedef struct { +struct image_definition { video::ITexture *texture = nullptr; bool tile; unsigned int minsize; -} image_definition; +}; /******************************************************************************/ /* forward declarations */ @@ -278,7 +277,7 @@ private: /** do preprocessing for cloud subsystem */ void cloudPreProcess(); /** do postprocessing for cloud subsystem */ - void cloudPostProcess(); + void cloudPostProcess(u32 frametime_min, IrrlichtDevice *device); /** internam data required for drawing clouds */ struct clouddata { diff --git a/src/gui/guiFormSpecMenu.cpp b/src/gui/guiFormSpecMenu.cpp index 37edf3c4b..5aa6dc9ae 100644 --- a/src/gui/guiFormSpecMenu.cpp +++ b/src/gui/guiFormSpecMenu.cpp @@ -19,13 +19,12 @@ with this program; if not, write to the Free Software Foundation, Inc., #include <cstdlib> +#include <cmath> #include <algorithm> #include <iterator> #include <limits> #include <sstream> #include "guiFormSpecMenu.h" -#include "guiScrollBar.h" -#include "guiTable.h" #include "constants.h" #include "gamedef.h" #include "client/keycode.h" @@ -50,6 +49,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "settings.h" #include "client/client.h" #include "client/fontengine.h" +#include "client/sound.h" #include "util/hex.h" #include "util/numeric.h" #include "util/string.h" // for parseColorString() @@ -64,15 +64,14 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "guiEditBoxWithScrollbar.h" #include "guiInventoryList.h" #include "guiItemImage.h" -#include "guiScrollBar.h" #include "guiScrollContainer.h" -#include "guiTable.h" #include "intlGUIEditBox.h" #include "guiHyperText.h" +#include "guiScene.h" #define MY_CHECKPOS(a,b) \ if (v_pos.size() != 2) { \ - errorstream<< "Invalid pos for element " << a << "specified: \"" \ + errorstream<< "Invalid pos for element " << a << " specified: \"" \ << parts[b] << "\"" << std::endl; \ return; \ } @@ -80,7 +79,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #define MY_CHECKGEOM(a,b) \ if (v_geom.size() != 2) { \ errorstream<< "Invalid geometry for element " << a << \ - "specified: \"" << parts[b] << "\"" << std::endl; \ + " specified: \"" << parts[b] << "\"" << std::endl; \ return; \ } /* @@ -98,30 +97,24 @@ inline u32 clamp_u8(s32 value) GUIFormSpecMenu::GUIFormSpecMenu(JoystickController *joystick, gui::IGUIElement *parent, s32 id, IMenuManager *menumgr, - Client *client, ISimpleTextureSource *tsrc, IFormSource *fsrc, TextDest *tdst, - const std::string &formspecPrepend, - bool remap_dbl_click): - GUIModalMenu(RenderingEngine::get_gui_env(), parent, id, menumgr), + Client *client, ISimpleTextureSource *tsrc, ISoundManager *sound_manager, + IFormSource *fsrc, TextDest *tdst, + const std::string &formspecPrepend, bool remap_dbl_click): + GUIModalMenu(RenderingEngine::get_gui_env(), parent, id, menumgr, remap_dbl_click), m_invmgr(client), m_tsrc(tsrc), + m_sound_manager(sound_manager), m_client(client), m_formspec_prepend(formspecPrepend), m_form_src(fsrc), m_text_dst(tdst), - m_joystick(joystick), - m_remap_dbl_click(remap_dbl_click) + m_joystick(joystick) { current_keys_pending.key_down = false; current_keys_pending.key_up = false; current_keys_pending.key_enter = false; current_keys_pending.key_escape = false; - m_doubleclickdetect[0].time = 0; - m_doubleclickdetect[1].time = 0; - - m_doubleclickdetect[0].pos = v2s32(0, 0); - m_doubleclickdetect[1].pos = v2s32(0, 0); - m_tooltip_show_delay = (u32)g_settings->getS32("tooltip_show_delay"); m_tooltip_append_itemname = g_settings->getBool("tooltip_append_itemname"); } @@ -154,11 +147,12 @@ GUIFormSpecMenu::~GUIFormSpecMenu() void GUIFormSpecMenu::create(GUIFormSpecMenu *&cur_formspec, Client *client, JoystickController *joystick, IFormSource *fs_src, TextDest *txt_dest, - const std::string &formspecPrepend) + const std::string &formspecPrepend, ISoundManager *sound_manager) { if (cur_formspec == nullptr) { cur_formspec = new GUIFormSpecMenu(joystick, guiroot, -1, &g_menumgr, - client, client->getTextureSource(), fs_src, txt_dest, formspecPrepend); + client, client->getTextureSource(), sound_manager, fs_src, + txt_dest, formspecPrepend); cur_formspec->doPause = false; /* @@ -504,19 +498,39 @@ void GUIFormSpecMenu::parseList(parserData *data, const std::string &element) 3 ); - v2f32 slot_spacing = data->real_coordinates ? - v2f32(imgsize.X * 1.25f, imgsize.Y * 1.25f) : spacing; + auto style = getDefaultStyleForElement("list", spec.fname); + + v2f32 slot_scale = style.getVector2f(StyleSpec::SIZE, v2f32(0, 0)); + v2f32 slot_size( + slot_scale.X <= 0 ? imgsize.X : std::max<f32>(slot_scale.X * imgsize.X, 1), + slot_scale.Y <= 0 ? imgsize.Y : std::max<f32>(slot_scale.Y * imgsize.Y, 1) + ); + + v2f32 slot_spacing = style.getVector2f(StyleSpec::SPACING, v2f32(-1, -1)); + v2f32 default_spacing = data->real_coordinates ? + v2f32(imgsize.X * 0.25f, imgsize.Y * 0.25f) : + v2f32(spacing.X - imgsize.X, spacing.Y - imgsize.Y); + + slot_spacing.X = slot_spacing.X < 0 ? default_spacing.X : + imgsize.X * slot_spacing.X; + slot_spacing.Y = slot_spacing.Y < 0 ? default_spacing.Y : + imgsize.Y * slot_spacing.Y; + + slot_spacing += slot_size; - v2s32 pos = data->real_coordinates ? getRealCoordinateBasePos(v_pos) - : getElementBasePos(&v_pos); + 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); + pos.X + (geom.X - 1) * slot_spacing.X + slot_size.X, + pos.Y + (geom.Y - 1) * slot_spacing.Y + slot_size.Y); GUIInventoryList *e = new GUIInventoryList(Environment, data->current_parent, - spec.fid, rect, m_invmgr, loc, listname, geom, start_i, imgsize, - slot_spacing, this, data->inventorylist_options, m_font); + spec.fid, rect, m_invmgr, loc, listname, geom, start_i, + v2s32(slot_size.X, slot_size.Y), slot_spacing, this, + data->inventorylist_options, m_font); + + e->setNotClipped(style.getBool(StyleSpec::NOCLIP, false)); m_inventorylists.push_back(e); m_fields.push_back(spec); @@ -625,9 +639,12 @@ void GUIFormSpecMenu::parseCheckbox(parserData* data, const std::string &element data->current_parent, spec.fid, spec.flabel.c_str()); auto style = getDefaultStyleForElement("checkbox", name); + + spec.sound = style.get(StyleSpec::Property::SOUND, ""); + e->setNotClipped(style.getBool(StyleSpec::NOCLIP, false)); - if (spec.fname == data->focused_fieldname) { + if (spec.fname == m_focused_element) { Environment->setFocus(e); } @@ -703,6 +720,10 @@ void GUIFormSpecMenu::parseScrollBar(parserData* data, const std::string &elemen e->setPageSize(scrollbar_size * (max - min + 1) / data->scrollbar_options.thumb_size); + if (spec.fname == m_focused_element) { + Environment->setFocus(e); + } + m_scrollbars.emplace_back(spec,e); m_fields.push_back(spec); return; @@ -907,7 +928,7 @@ void GUIFormSpecMenu::parseAnimatedImage(parserData *data, const std::string &el core::rect<s32> rect = core::rect<s32>(pos, pos + geom); - GUIAnimatedImage *e = new GUIAnimatedImage(Environment, this, spec.fid, + GUIAnimatedImage *e = new GUIAnimatedImage(Environment, data->current_parent, spec.fid, rect, texture_name, frame_count, frame_duration, m_tsrc); if (parts.size() >= 7) @@ -1027,9 +1048,12 @@ void GUIFormSpecMenu::parseButton(parserData* data, const std::string &element, data->current_parent, spec.fid, spec.flabel.c_str()); auto style = getStyleForElement(type, name, (type != "button") ? "button" : ""); + + spec.sound = style[StyleSpec::STATE_DEFAULT].get(StyleSpec::Property::SOUND, ""); + e->setStyles(style); - if (spec.fname == data->focused_fieldname) { + if (spec.fname == m_focused_element) { Environment->setFocus(e); } @@ -1218,7 +1242,7 @@ void GUIFormSpecMenu::parseTable(parserData* data, const std::string &element) GUITable *e = new GUITable(Environment, data->current_parent, spec.fid, rect, m_tsrc); - if (spec.fname == data->focused_fieldname) { + if (spec.fname == m_focused_element) { Environment->setFocus(e); } @@ -1233,6 +1257,7 @@ void GUIFormSpecMenu::parseTable(parserData* data, const std::string &element) auto style = getDefaultStyleForElement("table", name); e->setNotClipped(style.getBool(StyleSpec::NOCLIP, false)); + e->setOverrideFont(style.getFont()); m_tables.emplace_back(spec, e); m_fields.push_back(spec); @@ -1295,7 +1320,7 @@ void GUIFormSpecMenu::parseTextList(parserData* data, const std::string &element GUITable *e = new GUITable(Environment, data->current_parent, spec.fid, rect, m_tsrc); - if (spec.fname == data->focused_fieldname) { + if (spec.fname == m_focused_element) { Environment->setFocus(e); } @@ -1310,6 +1335,7 @@ void GUIFormSpecMenu::parseTextList(parserData* data, const std::string &element auto style = getDefaultStyleForElement("textlist", name); e->setNotClipped(style.getBool(StyleSpec::NOCLIP, false)); + e->setOverrideFont(style.getFont()); m_tables.emplace_back(spec, e); m_fields.push_back(spec); @@ -1318,19 +1344,20 @@ void GUIFormSpecMenu::parseTextList(parserData* data, const std::string &element errorstream<< "Invalid textlist element(" << parts.size() << "): '" << element << "'" << std::endl; } - void GUIFormSpecMenu::parseDropDown(parserData* data, const std::string &element) { - std::vector<std::string> parts = split(element,';'); + std::vector<std::string> parts = split(element, ';'); - if ((parts.size() == 5) || - ((parts.size() > 5) && (m_formspec_version > FORMSPEC_API_VERSION))) + if (parts.size() == 5 || parts.size() == 6 || + (parts.size() > 6 && m_formspec_version > FORMSPEC_API_VERSION)) { - std::vector<std::string> v_pos = split(parts[0],','); + std::vector<std::string> v_pos = split(parts[0], ','); std::string name = parts[2]; - std::vector<std::string> items = split(parts[3],','); - std::string str_initial_selection; - str_initial_selection = parts[4]; + std::vector<std::string> items = split(parts[3], ','); + std::string str_initial_selection = parts[4]; + + if (parts.size() >= 6 && is_yes(parts[5])) + m_dropdown_index_event[name] = true; MY_CHECKPOS("dropdown",0); @@ -1372,7 +1399,7 @@ void GUIFormSpecMenu::parseDropDown(parserData* data, const std::string &element gui::IGUIComboBox *e = Environment->addComboBox(rect, data->current_parent, spec.fid); - if (spec.fname == data->focused_fieldname) { + if (spec.fname == m_focused_element) { Environment->setFocus(e); } @@ -1385,6 +1412,9 @@ void GUIFormSpecMenu::parseDropDown(parserData* data, const std::string &element e->setSelected(stoi(str_initial_selection)-1); auto style = getDefaultStyleForElement("dropdown", name); + + spec.sound = style.get(StyleSpec::Property::SOUND, ""); + e->setNotClipped(style.getBool(StyleSpec::NOCLIP, false)); m_fields.push_back(spec); @@ -1397,8 +1427,8 @@ void GUIFormSpecMenu::parseDropDown(parserData* data, const std::string &element return; } - errorstream << "Invalid dropdown element(" << parts.size() << "): '" - << element << "'" << std::endl; + errorstream << "Invalid dropdown element(" << parts.size() << "): '" << element + << "'" << std::endl; } void GUIFormSpecMenu::parseFieldCloseOnEnter(parserData *data, const std::string &element) @@ -1414,8 +1444,8 @@ void GUIFormSpecMenu::parsePwdField(parserData* data, const std::string &element { std::vector<std::string> parts = split(element,';'); - if ((parts.size() == 4) || - ((parts.size() > 4) && (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],','); @@ -1459,7 +1489,7 @@ void GUIFormSpecMenu::parsePwdField(parserData* data, const std::string &element gui::IGUIEditBox *e = Environment->addEditBox(0, rect, true, data->current_parent, spec.fid); - if (spec.fname == data->focused_fieldname) { + if (spec.fname == m_focused_element) { Environment->setFocus(e); } @@ -1477,6 +1507,7 @@ void GUIFormSpecMenu::parsePwdField(parserData* data, const std::string &element e->setNotClipped(style.getBool(StyleSpec::NOCLIP, false)); e->setDrawBorder(style.getBool(StyleSpec::BORDER, true)); e->setOverrideColor(style.getColor(StyleSpec::TEXTCOLOR, video::SColor(0xFFFFFFFF))); + e->setOverrideFont(style.getFont()); irr::SEvent evt; evt.EventType = EET_KEY_INPUT_EVENT; @@ -1536,7 +1567,7 @@ void GUIFormSpecMenu::createTextField(parserData *data, FieldSpec &spec, auto style = getDefaultStyleForElement(is_multiline ? "textarea" : "field", spec.fname); if (e) { - if (is_editable && spec.fname == data->focused_fieldname) + if (is_editable && spec.fname == m_focused_element) Environment->setFocus(e); if (is_multiline) { @@ -1560,6 +1591,7 @@ void GUIFormSpecMenu::createTextField(parserData *data, FieldSpec &spec, if (style.get(StyleSpec::BGCOLOR, "") == "transparent") { e->setDrawBackground(false); } + e->setOverrideFont(style.getFont()); e->drop(); } @@ -1743,12 +1775,16 @@ void GUIFormSpecMenu::parseHyperText(parserData *data, const std::string &elemen FieldSpec spec( name, - utf8_to_wide(unescape_string(text)), + translate_string(utf8_to_wide(unescape_string(text))), L"", 258 + m_fields.size() ); spec.ftype = f_HyperText; + + auto style = getDefaultStyleForElement("hypertext", spec.fname); + spec.sound = style.get(StyleSpec::Property::SOUND, ""); + GUIHyperText *e = new GUIHyperText(spec.flabel.c_str(), Environment, data->current_parent, spec.fid, rect, m_client, m_tsrc); e->drop(); @@ -1773,6 +1809,11 @@ void GUIFormSpecMenu::parseLabel(parserData* data, const std::string &element) std::vector<std::string> lines = split(text, '\n'); + auto style = getDefaultStyleForElement("label", ""); + gui::IGUIFont *font = style.getFont(); + if (!font) + font = m_font; + for (unsigned int i = 0; i != lines.size(); i++) { std::wstring wlabel_colors = translate_string( utf8_to_wide(unescape_string(lines[i]))); @@ -1794,7 +1835,7 @@ void GUIFormSpecMenu::parseLabel(parserData* data, const std::string &element) rect = core::rect<s32>( pos.X, pos.Y, - pos.X + m_font->getDimension(wlabel_plain.c_str()).Width, + pos.X + font->getDimension(wlabel_plain.c_str()).Width, pos.Y + imgsize.Y); } else { @@ -1816,7 +1857,7 @@ void GUIFormSpecMenu::parseLabel(parserData* data, const std::string &element) rect = core::rect<s32>( pos.X, pos.Y - m_btn_height, - pos.X + m_font->getDimension(wlabel_plain.c_str()).Width, + pos.X + font->getDimension(wlabel_plain.c_str()).Width, pos.Y + m_btn_height); } @@ -1832,9 +1873,9 @@ void GUIFormSpecMenu::parseLabel(parserData* data, const std::string &element) spec.fid); e->setTextAlignment(gui::EGUIA_UPPERLEFT, gui::EGUIA_CENTER); - auto style = getDefaultStyleForElement("label", spec.fname); e->setNotClipped(style.getBool(StyleSpec::NOCLIP, false)); e->setOverrideColor(style.getColor(StyleSpec::TEXTCOLOR, video::SColor(0xFFFFFFFF))); + e->setOverrideFont(font); m_fields.push_back(spec); @@ -1862,6 +1903,11 @@ void GUIFormSpecMenu::parseVertLabel(parserData* data, const std::string &elemen MY_CHECKPOS("vertlabel",1); + auto style = getDefaultStyleForElement("vertlabel", "", "label"); + gui::IGUIFont *font = style.getFont(); + if (!font) + font = m_font; + v2s32 pos; core::rect<s32> rect; @@ -1875,7 +1921,7 @@ void GUIFormSpecMenu::parseVertLabel(parserData* data, const std::string &elemen // isn't quite tall enough and cuts off the text. rect = core::rect<s32>(pos.X, pos.Y, pos.X + imgsize.X, - pos.Y + font_line_height(m_font) * + pos.Y + font_line_height(font) * (text.length() + 1)); } else { @@ -1887,7 +1933,7 @@ void GUIFormSpecMenu::parseVertLabel(parserData* data, const std::string &elemen rect = core::rect<s32>( pos.X, pos.Y+((imgsize.Y/2) - m_btn_height), pos.X+15, pos.Y + - font_line_height(m_font) * + font_line_height(font) * (text.length() + 1) + ((imgsize.Y/2) - m_btn_height)); } @@ -1912,9 +1958,9 @@ void GUIFormSpecMenu::parseVertLabel(parserData* data, const std::string &elemen rect, false, false, data->current_parent, spec.fid); e->setTextAlignment(gui::EGUIA_CENTER, gui::EGUIA_CENTER); - auto style = getDefaultStyleForElement("vertlabel", spec.fname, "label"); e->setNotClipped(style.getBool(StyleSpec::NOCLIP, false)); e->setOverrideColor(style.getColor(StyleSpec::TEXTCOLOR, video::SColor(0xFFFFFFFF))); + e->setOverrideFont(font); m_fields.push_back(spec); @@ -1985,12 +2031,14 @@ void GUIFormSpecMenu::parseImageButton(parserData* data, const std::string &elem GUIButtonImage *e = GUIButtonImage::addButton(Environment, rect, m_tsrc, data->current_parent, spec.fid, spec.flabel.c_str()); - if (spec.fname == data->focused_fieldname) { + if (spec.fname == m_focused_element) { Environment->setFocus(e); } auto style = getStyleForElement("image_button", spec.fname); + spec.sound = style[StyleSpec::STATE_DEFAULT].get(StyleSpec::Property::SOUND, ""); + // Override style properties with values specified directly in the element if (!image_name.empty()) style[StyleSpec::STATE_DEFAULT].set(StyleSpec::FGIMG, image_name); @@ -2098,11 +2146,10 @@ void GUIFormSpecMenu::parseTabHeader(parserData* data, const std::string &elemen irr::gui::EGUIA_UPPERLEFT, irr::gui::EGUIA_LOWERRIGHT); e->setTabHeight(geom.Y); - if (spec.fname == data->focused_fieldname) { - Environment->setFocus(e); - } - auto style = getDefaultStyleForElement("tabheader", name); + + spec.sound = style.get(StyleSpec::Property::SOUND, ""); + e->setNotClipped(style.getBool(StyleSpec::NOCLIP, true)); for (const std::string &button : buttons) { @@ -2191,9 +2238,12 @@ void GUIFormSpecMenu::parseItemImageButton(parserData* data, const std::string & item_name, m_client); auto style = getStyleForElement("item_image_button", spec_btn.fname, "image_button"); + + spec_btn.sound = style[StyleSpec::STATE_DEFAULT].get(StyleSpec::Property::SOUND, ""); + e_btn->setStyles(style); - if (spec_btn.fname == data->focused_fieldname) { + if (spec_btn.fname == m_focused_element) { Environment->setFocus(e_btn); } @@ -2208,16 +2258,16 @@ void GUIFormSpecMenu::parseItemImageButton(parserData* data, const std::string & void GUIFormSpecMenu::parseBox(parserData* data, const std::string &element) { - std::vector<std::string> parts = split(element,';'); + std::vector<std::string> parts = split(element, ';'); if ((parts.size() == 3) || ((parts.size() > 3) && (m_formspec_version > FORMSPEC_API_VERSION))) { - std::vector<std::string> v_pos = split(parts[0],','); - std::vector<std::string> v_geom = split(parts[1],','); + std::vector<std::string> v_pos = split(parts[0], ','); + std::vector<std::string> v_geom = split(parts[1], ','); - MY_CHECKPOS("box",0); - MY_CHECKGEOM("box",1); + MY_CHECKPOS("box", 0); + MY_CHECKGEOM("box", 1); v2s32 pos; v2s32 geom; @@ -2231,36 +2281,43 @@ void GUIFormSpecMenu::parseBox(parserData* data, const std::string &element) geom.Y = stof(v_geom[1]) * spacing.Y; } - video::SColor tmp_color; - - if (parseColorString(parts[2], tmp_color, false, 0x8C)) { - FieldSpec spec( - "", - L"", - L"", - 258 + m_fields.size(), - -2 - ); - spec.ftype = f_Box; + FieldSpec spec( + "", + L"", + L"", + 258 + m_fields.size(), + -2 + ); + spec.ftype = f_Box; - core::rect<s32> rect(pos, pos + geom); + auto style = getDefaultStyleForElement("box", spec.fname); - GUIBox *e = new GUIBox(Environment, data->current_parent, spec.fid, - rect, tmp_color); + video::SColor tmp_color; + std::array<video::SColor, 4> colors; + std::array<video::SColor, 4> bordercolors = {0x0, 0x0, 0x0, 0x0}; + std::array<s32, 4> borderwidths = {0, 0, 0, 0}; - auto style = getDefaultStyleForElement("box", spec.fname); - e->setNotClipped(style.getBool(StyleSpec::NOCLIP, m_formspec_version < 3)); + if (parseColorString(parts[2], tmp_color, true, 0x8C)) { + colors = {tmp_color, tmp_color, tmp_color, tmp_color}; + } else { + colors = style.getColorArray(StyleSpec::COLORS, {0x0, 0x0, 0x0, 0x0}); + bordercolors = style.getColorArray(StyleSpec::BORDERCOLORS, + {0x0, 0x0, 0x0, 0x0}); + borderwidths = style.getIntArray(StyleSpec::BORDERWIDTHS, {0, 0, 0, 0}); + } - e->drop(); + core::rect<s32> rect(pos, pos + geom); - m_fields.push_back(spec); + GUIBox *e = new GUIBox(Environment, data->current_parent, spec.fid, rect, + colors, bordercolors, borderwidths); + e->setNotClipped(style.getBool(StyleSpec::NOCLIP, m_formspec_version < 3)); + e->drop(); - } else { - errorstream<< "Invalid Box element(" << parts.size() << "): '" << element << "' INVALID COLOR" << std::endl; - } + m_fields.push_back(spec); return; } - errorstream<< "Invalid Box element(" << parts.size() << "): '" << element << "'" << std::endl; + errorstream << "Invalid Box element(" << parts.size() << "): '" << element + << "'" << std::endl; } void GUIFormSpecMenu::parseBackgroundColor(parserData* data, const std::string &element) @@ -2664,6 +2721,118 @@ bool GUIFormSpecMenu::parseStyle(parserData *data, const std::string &element, b return true; } +void GUIFormSpecMenu::parseSetFocus(const std::string &element) +{ + std::vector<std::string> parts = split(element, ';'); + + if (parts.size() <= 2 || + (parts.size() > 2 && m_formspec_version > FORMSPEC_API_VERSION)) + { + if (m_is_form_regenerated) + return; // Never focus on resizing + + bool force_focus = parts.size() >= 2 && is_yes(parts[1]); + if (force_focus || m_text_dst->m_formname != m_last_formname) + setFocus(parts[0]); + + return; + } + + errorstream << "Invalid set_focus element (" << parts.size() << "): '" << element + << "'" << std::endl; +} + +void GUIFormSpecMenu::parseModel(parserData *data, const std::string &element) +{ + std::vector<std::string> parts = split(element, ';'); + + if (parts.size() < 5 || (parts.size() > 9 && + m_formspec_version <= FORMSPEC_API_VERSION)) { + errorstream << "Invalid model element (" << parts.size() << "): '" << element + << "'" << std::endl; + return; + } + + // Avoid length checks by resizing + if (parts.size() < 9) + parts.resize(9); + + std::vector<std::string> v_pos = split(parts[0], ','); + std::vector<std::string> v_geom = split(parts[1], ','); + std::string name = unescape_string(parts[2]); + std::string meshstr = unescape_string(parts[3]); + std::vector<std::string> textures = split(parts[4], ','); + std::vector<std::string> vec_rot = split(parts[5], ','); + bool inf_rotation = is_yes(parts[6]); + bool mousectrl = is_yes(parts[7]) || parts[7].empty(); // default true + std::vector<std::string> frame_loop = split(parts[8], ','); + + MY_CHECKPOS("model", 0); + MY_CHECKGEOM("model", 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 model without a size[] element" << std::endl; + + scene::IAnimatedMesh *mesh = m_client->getMesh(meshstr); + + if (!mesh) { + errorstream << "Invalid model element: Unable to load mesh:" + << std::endl << "\t" << meshstr << std::endl; + return; + } + + FieldSpec spec( + name, + L"", + L"", + 258 + m_fields.size() + ); + + core::rect<s32> rect(pos, pos + geom); + + GUIScene *e = new GUIScene(Environment, RenderingEngine::get_scene_manager(), + data->current_parent, rect, spec.fid); + + auto meshnode = e->setMesh(mesh); + + for (u32 i = 0; i < textures.size() && i < meshnode->getMaterialCount(); ++i) + e->setTexture(i, m_tsrc->getTexture(unescape_string(textures[i]))); + + if (vec_rot.size() >= 2) + e->setRotation(v2f(stof(vec_rot[0]), stof(vec_rot[1]))); + + e->enableContinuousRotation(inf_rotation); + e->enableMouseControl(mousectrl); + + s32 frame_loop_begin = 0; + s32 frame_loop_end = 0x7FFFFFFF; + + if (frame_loop.size() == 2) { + frame_loop_begin = stoi(frame_loop[0]); + frame_loop_end = stoi(frame_loop[1]); + } + + e->setFrameLoop(frame_loop_begin, frame_loop_end); + + auto style = getStyleForElement("model", spec.fname); + e->setStyles(style); + e->drop(); + + m_fields.push_back(spec); +} + void GUIFormSpecMenu::parseElement(parserData* data, const std::string &element) { //some prechecks @@ -2855,6 +3024,16 @@ void GUIFormSpecMenu::parseElement(parserData* data, const std::string &element) return; } + if (type == "set_focus") { + parseSetFocus(description); + return; + } + + if (type == "model") { + parseModel(data, description); + return; + } + // Ignore others infostream << "Unknown DrawSpec: type=" << type << ", data=\"" << description << "\"" << std::endl; @@ -2862,36 +3041,38 @@ void GUIFormSpecMenu::parseElement(parserData* data, const std::string &element) void GUIFormSpecMenu::regenerateGui(v2u32 screensize) { - /* useless to regenerate without a screensize */ + // Useless to regenerate without a screensize if ((screensize.X <= 0) || (screensize.Y <= 0)) { return; } parserData mydata; - //preserve tables - for (auto &m_table : m_tables) { - std::string tablename = m_table.first.fname; - GUITable *table = m_table.second; - mydata.table_dyndata[tablename] = table->getDynamicData(); - } - - //set focus - if (!m_focused_element.empty()) - mydata.focused_fieldname = m_focused_element; + // Preserve stuff only on same form, not on a new form. + if (m_text_dst->m_formname == m_last_formname) { + // Preserve tables/textlists + for (auto &m_table : m_tables) { + std::string tablename = m_table.first.fname; + GUITable *table = m_table.second; + mydata.table_dyndata[tablename] = table->getDynamicData(); + } - //preserve focus - gui::IGUIElement *focused_element = Environment->getFocus(); - if (focused_element && focused_element->getParent() == this) { - s32 focused_id = focused_element->getID(); - if (focused_id > 257) { - for (const GUIFormSpecMenu::FieldSpec &field : m_fields) { - if (field.fid == focused_id) { - mydata.focused_fieldname = field.fname; - break; + // Preserve focus + gui::IGUIElement *focused_element = Environment->getFocus(); + if (focused_element && focused_element->getParent() == this) { + s32 focused_id = focused_element->getID(); + if (focused_id > 257) { + for (const GUIFormSpecMenu::FieldSpec &field : m_fields) { + if (field.fid == focused_id) { + m_focused_element = field.fname; + break; + } } } } + } else { + // Don't keep old focus value + m_focused_element = ""; } // Remove children @@ -2940,6 +3121,8 @@ void GUIFormSpecMenu::regenerateGui(v2u32 screensize) theme_by_name.clear(); theme_by_type.clear(); m_clickthrough_elements.clear(); + field_close_on_enter.clear(); + m_dropdown_index_event.clear(); m_bgnonfullscreen = true; m_bgfullscreen = false; @@ -3079,42 +3262,42 @@ void GUIFormSpecMenu::regenerateGui(v2u32 screensize) // and default scaling (1.00). use_imgsize = 0.5555 * screen_dpi * gui_scaling; } else { - // In variable-size mode, we prefer to make the - // inventory image size 1/15 of screen height, - // multiplied by the gui_scaling config parameter. - // If the preferred size won't fit the whole - // form on the screen, either horizontally or - // vertically, then we scale it down to fit. - // (The magic numbers in the computation of what - // fits arise from the scaling factors in the - // following stanza, including the form border, - // help text space, and 0.1 inventory slot spare.) - // However, a minimum size is also set, that - // the image size can't be less than 0.3 inch - // multiplied by gui_scaling, even if this means - // the form doesn't fit the screen. + // Variables for the maximum imgsize that can fit in the screen. + double fitx_imgsize; + double fity_imgsize; + + // Pad the screensize with 5% of the screensize on all sides to ensure + // that even the largest formspecs don't touch the screen borders. + v2f padded_screensize( + mydata.screensize.X * 0.9f, + mydata.screensize.Y * 0.9f + ); + + if (mydata.real_coordinates) { + fitx_imgsize = padded_screensize.X / mydata.invsize.X; + fity_imgsize = padded_screensize.Y / mydata.invsize.Y; + } else { + // The maximum imgsize in the old coordinate system also needs to + // factor in padding and spacing along with 0.1 inventory slot spare + // and help text space, hence the magic numbers. + fitx_imgsize = padded_screensize.X / + ((5.0 / 4.0) * (0.5 + mydata.invsize.X)); + fity_imgsize = padded_screensize.Y / + ((15.0 / 13.0) * (0.85 + mydata.invsize.Y)); + } + #ifdef __ANDROID__ - // For mobile devices these magic numbers are - // different and forms should always use the - // maximum screen space available. - double prefer_imgsize = mydata.screensize.Y / 10 * gui_scaling; - double fitx_imgsize = mydata.screensize.X / - ((12.0 / 8.0) * (0.5 + mydata.invsize.X)); - double fity_imgsize = mydata.screensize.Y / - ((15.0 / 11.0) * (0.85 + mydata.invsize.Y)); - use_imgsize = MYMIN(prefer_imgsize, - MYMIN(fitx_imgsize, fity_imgsize)); + // In Android, the preferred imgsize should be larger to accommodate the + // smaller screensize. + double prefer_imgsize = padded_screensize.Y / 10 * gui_scaling; #else - double prefer_imgsize = mydata.screensize.Y / 15 * gui_scaling; - double fitx_imgsize = mydata.screensize.X / - ((5.0 / 4.0) * (0.5 + mydata.invsize.X)); - double fity_imgsize = mydata.screensize.Y / - ((15.0 / 13.0) * (0.85 * mydata.invsize.Y)); - double screen_dpi = RenderingEngine::getDisplayDensity() * 96; - double min_imgsize = 0.3 * screen_dpi * gui_scaling; - use_imgsize = MYMAX(min_imgsize, MYMIN(prefer_imgsize, - MYMIN(fitx_imgsize, fity_imgsize))); + // Desktop computers have more space, so try to fit 15 coordinates. + double prefer_imgsize = padded_screensize.Y / 15 * gui_scaling; #endif + // Try to use the preferred imgsize, but if that's bigger than the maximum + // size, use the maximum size. + use_imgsize = std::min(prefer_imgsize, + std::min(fitx_imgsize, fity_imgsize)); } // Everything else is scaled in proportion to the @@ -3250,8 +3433,8 @@ void GUIFormSpecMenu::regenerateGui(v2u32 screensize) } } - //set initial focus if parser didn't set it - focused_element = Environment->getFocus(); + // Set initial focus if parser didn't set it + gui::IGUIElement *focused_element = Environment->getFocus(); if (!focused_element || !isMyChild(focused_element) || focused_element->getType() == gui::EGUIET_TAB_CONTROL) @@ -3262,6 +3445,13 @@ void GUIFormSpecMenu::regenerateGui(v2u32 screensize) // legacy sorting if (m_formspec_version < 3) legacySortElements(legacy_sort_start); + + // Formname and regeneration setting + if (!m_is_form_regenerated) { + // Only set previous form name if we purposefully showed a new formspec + m_last_formname = m_text_dst->m_formname; + m_is_form_regenerated = true; + } } void GUIFormSpecMenu::legacySortElements(core::list<IGUIElement *>::Iterator from) @@ -3336,8 +3526,6 @@ bool GUIFormSpecMenu::getAndroidUIInput() GUIInventoryList::ItemSpec GUIFormSpecMenu::getItemAtPos(v2s32 p) const { - core::rect<s32> imgrect(0, 0, imgsize.X, imgsize.Y); - for (const GUIInventoryList *e : m_inventorylists) { s32 item_index = e->getItemIndexAtPos(p); if (item_index != -1) @@ -3379,6 +3567,7 @@ void GUIFormSpecMenu::drawMenu() const std::string &newform = m_form_src->getForm(); if (newform != m_formspec_string) { m_formspec_string = newform; + m_is_form_regenerated = false; regenerateGui(m_screensize_old); } } @@ -3435,10 +3624,14 @@ void GUIFormSpecMenu::drawMenu() e->setVisible(true); /* - Call base class - (This is where all the drawing happens.) + This is where all the drawing happens. */ - gui::IGUIElement::draw(); + core::list<IGUIElement*>::Iterator it = Children.begin(); + for (; it != Children.end(); ++it) + if ((*it)->isNotClipped() || + AbsoluteClippingRect.isRectCollided( + (*it)->getAbsolutePosition())) + (*it)->draw(); for (gui::IGUIElement *e : m_clickthrough_elements) e->setVisible(false); @@ -3544,7 +3737,8 @@ void GUIFormSpecMenu::showTooltip(const std::wstring &text, { EnrichedString ntext(text); ntext.setDefaultColor(color); - ntext.setBackground(bgcolor); + if (!ntext.hasBackground()) + ntext.setBackground(bgcolor); setStaticText(m_tooltip_element, ntext); @@ -3662,7 +3856,7 @@ ItemStack GUIFormSpecMenu::verifySelectedItem() return ItemStack(); } -void GUIFormSpecMenu::acceptInput(FormspecQuitMode quitmode=quit_mode_no) +void GUIFormSpecMenu::acceptInput(FormspecQuitMode quitmode) { if(m_text_dst) { @@ -3727,10 +3921,14 @@ void GUIFormSpecMenu::acceptInput(FormspecQuitMode quitmode=quit_mode_no) } s32 selected = e->getSelected(); if (selected >= 0) { - std::vector<std::string> *dropdown_values = - getDropDownValues(s.fname); - if (dropdown_values && selected < (s32)dropdown_values->size()) { - fields[name] = (*dropdown_values)[selected]; + if (m_dropdown_index_event.find(s.fname) != + m_dropdown_index_event.end()) { + fields[name] = std::to_string(selected + 1); + } else { + std::vector<std::string> *dropdown_values = + getDropDownValues(s.fname); + if (dropdown_values && selected < (s32)dropdown_values->size()) + fields[name] = (*dropdown_values)[selected]; } } } else if (s.ftype == f_TabHeader) { @@ -3800,17 +3998,6 @@ void GUIFormSpecMenu::acceptInput(FormspecQuitMode quitmode=quit_mode_no) } } -static bool isChild(gui::IGUIElement *tocheck, gui::IGUIElement *parent) -{ - while (tocheck) { - if (tocheck == parent) { - return true; - } - tocheck = tocheck->getParent(); - } - return false; -} - bool GUIFormSpecMenu::preprocessEvent(const SEvent& event) { // The IGUITabControl renders visually using the skin's selected @@ -3871,22 +4058,6 @@ bool GUIFormSpecMenu::preprocessEvent(const SEvent& event) } } - if (event.EventType == EET_MOUSE_INPUT_EVENT) { - s32 x = event.MouseInput.X; - s32 y = event.MouseInput.Y; - gui::IGUIElement *hovered = - Environment->getRootGUIElement()->getElementFromPoint( - core::position2d<s32>(x, y)); - if (event.MouseInput.Event == EMIE_LMOUSE_PRESSED_DOWN) { - m_old_tooltip_id = -1; - } - if (!isChild(hovered, this)) { - if (DoubleClickDetection(event)) { - return true; - } - } - } - if (event.EventType == irr::EET_JOYSTICK_INPUT_EVENT) { /* TODO add a check like: if (event.JoystickEvent != joystick_we_listen_for) @@ -3909,64 +4080,6 @@ bool GUIFormSpecMenu::preprocessEvent(const SEvent& event) return GUIModalMenu::preprocessEvent(event); } -/******************************************************************************/ -bool GUIFormSpecMenu::DoubleClickDetection(const SEvent event) -{ - /* The following code is for capturing double-clicks of the mouse button - * and translating the double-click into an EET_KEY_INPUT_EVENT event - * -- which closes the form -- under some circumstances. - * - * There have been many github issues reporting this as a bug even though it - * was an intended feature. For this reason, remapping the double-click as - * an ESC must be explicitly set when creating this class via the - * /p remap_dbl_click parameter of the constructor. - */ - - if (!m_remap_dbl_click) - return false; - - if (event.MouseInput.Event == EMIE_LMOUSE_PRESSED_DOWN) { - m_doubleclickdetect[0].pos = m_doubleclickdetect[1].pos; - m_doubleclickdetect[0].time = m_doubleclickdetect[1].time; - - m_doubleclickdetect[1].pos = m_pointer; - m_doubleclickdetect[1].time = porting::getTimeMs(); - } - else if (event.MouseInput.Event == EMIE_LMOUSE_LEFT_UP) { - u64 delta = porting::getDeltaMs(m_doubleclickdetect[0].time, porting::getTimeMs()); - if (delta > 400) { - return false; - } - - double squaredistance = - m_doubleclickdetect[0].pos - .getDistanceFromSQ(m_doubleclickdetect[1].pos); - - if (squaredistance > (30*30)) { - return false; - } - - SEvent* translated = new SEvent(); - assert(translated != 0); - //translate doubleclick to escape - memset(translated, 0, sizeof(SEvent)); - translated->EventType = irr::EET_KEY_INPUT_EVENT; - translated->KeyInput.Key = KEY_ESCAPE; - translated->KeyInput.Control = false; - translated->KeyInput.Shift = false; - translated->KeyInput.PressedDown = true; - translated->KeyInput.Char = 0; - OnEvent(*translated); - - // no need to send the key up event as we're already deleted - // and no one else did notice this event - delete translated; - return true; - } - - return false; -} - void GUIFormSpecMenu::tryClose() { if (m_allowclose) { @@ -4429,6 +4542,8 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event) for (GUIFormSpecMenu::FieldSpec &s : m_fields) { if ((s.ftype == f_TabHeader) && (s.fid == event.GUIEvent.Caller->getID())) { + if (!s.sound.empty() && m_sound_manager) + m_sound_manager->playSound(s.sound, false, 1.0f); s.send = true; acceptInput(); s.send = false; @@ -4472,6 +4587,9 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event) continue; if (s.ftype == f_Button || s.ftype == f_CheckBox) { + if (!s.sound.empty() && m_sound_manager) + m_sound_manager->playSound(s.sound, false, 1.0f); + s.send = true; if (s.is_exit) { if (m_allowclose) { @@ -4494,6 +4612,8 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event) s2.send = false; } } + if (!s.sound.empty() && m_sound_manager) + m_sound_manager->playSound(s.sound, false, 1.0f); s.send = true; acceptInput(quit_mode_no); @@ -4510,6 +4630,8 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event) acceptInput(quit_mode_no); s.fdefault = L""; } else if (s.ftype == f_Unknown || s.ftype == f_HyperText) { + if (!s.sound.empty() && m_sound_manager) + m_sound_manager->playSound(s.sound, false, 1.0f); s.send = true; acceptInput(); s.send = false; diff --git a/src/gui/guiFormSpecMenu.h b/src/gui/guiFormSpecMenu.h index 28088416d..d658aba7b 100644 --- a/src/gui/guiFormSpecMenu.h +++ b/src/gui/guiFormSpecMenu.h @@ -27,6 +27,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "inventorymanager.h" #include "modalMenu.h" #include "guiInventoryList.h" +#include "guiScrollBar.h" #include "guiTable.h" #include "network/networkprotocol.h" #include "client/joystick_controller.h" @@ -37,11 +38,10 @@ with this program; if not, write to the Free Software Foundation, Inc., class InventoryManager; class ISimpleTextureSource; class Client; -class GUIScrollBar; -class TexturePool; class GUIScrollContainer; +class ISoundManager; -typedef enum { +enum FormspecFieldType { f_Button, f_Table, f_TabHeader, @@ -53,13 +53,13 @@ typedef enum { f_HyperText, f_AnimatedImage, f_Unknown -} FormspecFieldType; +}; -typedef enum { +enum FormspecQuitMode { quit_mode_no, quit_mode_accept, quit_mode_cancel -} FormspecQuitMode; +}; struct TextDest { @@ -128,6 +128,7 @@ class GUIFormSpecMenu : public GUIModalMenu int priority; core::rect<s32> rect; gui::ECURSOR_ICON fcursor_icon; + std::string sound; }; struct TooltipSpec @@ -152,6 +153,7 @@ public: IMenuManager *menumgr, Client *client, ISimpleTextureSource *tsrc, + ISoundManager *sound_manager, IFormSource* fs_src, TextDest* txt_dst, const std::string &formspecPrepend, @@ -161,13 +163,14 @@ public: static void create(GUIFormSpecMenu *&cur_formspec, Client *client, JoystickController *joystick, IFormSource *fs_src, TextDest *txt_dest, - const std::string &formspecPrepend); + const std::string &formspecPrepend, ISoundManager *sound_manager); void setFormSpec(const std::string &formspec_string, const InventoryLocation ¤t_inventory_location) { m_formspec_string = formspec_string; m_current_inventory_location = current_inventory_location; + m_is_form_regenerated = false; regenerateGui(m_screensize_old); } @@ -250,7 +253,7 @@ public: void updateSelectedItem(); ItemStack verifySelectedItem(); - void acceptInput(FormspecQuitMode quitmode); + void acceptInput(FormspecQuitMode quitmode=quit_mode_no); bool preprocessEvent(const SEvent& event); bool OnEvent(const SEvent& event); bool doPause; @@ -293,16 +296,22 @@ protected: InventoryManager *m_invmgr; ISimpleTextureSource *m_tsrc; + ISoundManager *m_sound_manager; Client *m_client; std::string m_formspec_string; std::string m_formspec_prepend; InventoryLocation m_current_inventory_location; + // Default true because we can't control regeneration on resizing, but + // we can control cases when the formspec is shown intentionally. + bool m_is_form_regenerated = true; + std::vector<GUIInventoryList *> m_inventorylists; std::vector<ListRingSpec> m_inventory_rings; std::vector<gui::IGUIElement *> m_backgrounds; std::unordered_map<std::string, bool> field_close_on_enter; + std::unordered_map<std::string, bool> m_dropdown_index_event; std::vector<FieldSpec> m_fields; std::vector<std::pair<FieldSpec, GUITable *>> m_tables; std::vector<std::pair<FieldSpec, gui::IGUICheckBox *>> m_checkboxes; @@ -338,16 +347,16 @@ protected: video::SColor m_default_tooltip_bgcolor; video::SColor m_default_tooltip_color; - private: IFormSource *m_form_src; TextDest *m_text_dst; + std::string m_last_formname; u16 m_formspec_version = 1; std::string m_focused_element = ""; JoystickController *m_joystick; bool m_show_debug = false; - typedef struct { + struct parserData { bool explicit_size; bool real_coordinates; u8 simple_field_count; @@ -358,7 +367,6 @@ private: core::rect<s32> rect; v2s32 basepos; v2u32 screensize; - std::string focused_fieldname; GUITable::TableOptions table_options; GUITable::TableColumns table_columns; gui::IGUIElement *current_parent = nullptr; @@ -376,16 +384,16 @@ private: // used to restore table selection/scroll/treeview state std::unordered_map<std::string, GUITable::DynamicData> table_dyndata; - } parserData; + }; - typedef struct { + struct fs_key_pending { bool key_up; bool key_down; bool key_enter; bool key_escape; - } fs_key_pendig; + }; - fs_key_pendig current_keys_pending; + fs_key_pending current_keys_pending; std::string current_field_enter_pending = ""; std::vector<std::string> m_hovered_item_tooltips; @@ -438,6 +446,8 @@ private: bool parseAnchorDirect(parserData *data, const std::string &element); void parseAnchor(parserData *data, const std::string &element); bool parseStyle(parserData *data, const std::string &element, bool style_type); + void parseSetFocus(const std::string &element); + void parseModel(parserData *data, const std::string &element); void tryClose(); @@ -451,30 +461,8 @@ private: */ 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 - */ - bool DoubleClickDetection(const SEvent event); - - struct clickpos - { - v2s32 pos; - s64 time; - }; - clickpos m_doubleclickdetect[2]; - int m_btn_height; gui::IGUIFont *m_font = nullptr; - - /* If true, remap a double-click (or double-tap) action to ESC. This is so - * that, for example, Android users can double-tap to close a formspec. - * - * This value can (currently) only be set by the class constructor - * and the default value for the setting is true. - */ - bool m_remap_dbl_click; }; class FormspecFormSource: public IFormSource diff --git a/src/gui/guiHyperText.cpp b/src/gui/guiHyperText.cpp index 88931cdf9..ccfdcb81d 100644 --- a/src/gui/guiHyperText.cpp +++ b/src/gui/guiHyperText.cpp @@ -1088,7 +1088,7 @@ bool GUIHyperText::OnEvent(const SEvent &event) if (event.MouseInput.Event == EMIE_MOUSE_MOVED) checkHover(event.MouseInput.X, event.MouseInput.Y); - if (event.MouseInput.Event == EMIE_MOUSE_WHEEL) { + if (event.MouseInput.Event == EMIE_MOUSE_WHEEL && m_vscrollbar->isVisible()) { m_vscrollbar->setPos(m_vscrollbar->getPos() - event.MouseInput.Wheel * m_vscrollbar->getSmallStep()); m_text_scrollpos.Y = -m_vscrollbar->getPos(); diff --git a/src/gui/guiInventoryList.cpp b/src/gui/guiInventoryList.cpp index dfdb60448..183d72165 100644 --- a/src/gui/guiInventoryList.cpp +++ b/src/gui/guiInventoryList.cpp @@ -104,8 +104,6 @@ void GUIInventoryList::draw() && 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); diff --git a/src/gui/guiKeyChangeMenu.cpp b/src/gui/guiKeyChangeMenu.cpp index da0e25c23..4dcb47779 100644 --- a/src/gui/guiKeyChangeMenu.cpp +++ b/src/gui/guiKeyChangeMenu.cpp @@ -248,7 +248,7 @@ bool GUIKeyChangeMenu::acceptInput() { for (key_setting *k : key_settings) { std::string default_key; - g_settings->getDefaultNoEx(k->setting_name, default_key); + Settings::getLayer(SL_DEFAULTS)->getNoEx(k->setting_name, default_key); if (k->key.sym() != default_key) g_settings->set(k->setting_name, k->key.sym()); @@ -360,7 +360,7 @@ bool GUIKeyChangeMenu::OnEvent(const SEvent& event) { if (!canTakeFocus(event.GUIEvent.Element)) { - dstream << "GUIMainMenu: Not allowing focus change." + infostream << "GUIKeyChangeMenu: Not allowing focus change." << std::endl; // Returning true disables focus change return true; diff --git a/src/gui/guiMainMenu.h b/src/gui/guiMainMenu.h index 43a3b1a33..1dca8bf2d 100644 --- a/src/gui/guiMainMenu.h +++ b/src/gui/guiMainMenu.h @@ -20,7 +20,6 @@ with this program; if not, write to the Free Software Foundation, Inc., #pragma once #include "irrlichttypes_extrabloated.h" -#include "modalMenu.h" #include <string> #include <list> diff --git a/src/gui/guiPasswordChange.cpp b/src/gui/guiPasswordChange.cpp index 965a2d6f7..74cd62f5b 100644 --- a/src/gui/guiPasswordChange.cpp +++ b/src/gui/guiPasswordChange.cpp @@ -79,7 +79,11 @@ void GUIPasswordChange::regenerateGui(v2u32 screensize) /* Calculate new sizes and positions */ +#ifdef __ANDROID__ + const float s = m_gui_scale * porting::getDisplayDensity() / 2; +#else const float s = m_gui_scale; +#endif DesiredRect = core::rect<s32>( screensize.X / 2 - 580 * s / 2, screensize.Y / 2 - 300 * s / 2, @@ -236,7 +240,7 @@ bool GUIPasswordChange::OnEvent(const SEvent &event) if (event.GUIEvent.EventType == gui::EGET_ELEMENT_FOCUS_LOST && isVisible()) { if (!canTakeFocus(event.GUIEvent.Element)) { - dstream << "GUIPasswordChange: Not allowing focus change." + infostream << "GUIPasswordChange: Not allowing focus change." << std::endl; // Returning true disables focus change return true; @@ -289,6 +293,10 @@ bool GUIPasswordChange::getAndroidUIInput() if (!hasAndroidUIInput()) return false; + // still waiting + if (porting::getInputDialogState() == -1) + return true; + gui::IGUIElement *e = nullptr; if (m_jni_field_name == "old_password") e = getElementFromId(ID_oldPassword); @@ -296,12 +304,13 @@ bool GUIPasswordChange::getAndroidUIInput() e = getElementFromId(ID_newPassword1); else if (m_jni_field_name == "new_password_2") e = getElementFromId(ID_newPassword2); - - if (e) { - std::string text = porting::getInputDialogValue(); - e->setText(utf8_to_wide(text).c_str()); - } m_jni_field_name.clear(); + + if (!e || e->getType() != irr::gui::EGUIET_EDIT_BOX) + return false; + + std::string text = porting::getInputDialogValue(); + e->setText(utf8_to_wide(text).c_str()); return false; } #endif diff --git a/src/gui/guiScene.cpp b/src/gui/guiScene.cpp new file mode 100644 index 000000000..5f4c50b91 --- /dev/null +++ b/src/gui/guiScene.cpp @@ -0,0 +1,266 @@ +/* +Minetest +Copyright (C) 2020 Jean-Patrick Guerrero <jeanpatrick.guerrero@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 "guiScene.h" + +#include <SViewFrustum.h> +#include <IAnimatedMeshSceneNode.h> +#include <ILightSceneNode.h> +#include "porting.h" + +GUIScene::GUIScene(gui::IGUIEnvironment *env, scene::ISceneManager *smgr, + gui::IGUIElement *parent, core::recti rect, s32 id) + : IGUIElement(gui::EGUIET_ELEMENT, env, parent, id, rect) +{ + m_driver = env->getVideoDriver(); + m_smgr = smgr->createNewSceneManager(false); + + m_cam = m_smgr->addCameraSceneNode(0, v3f(0.f, 0.f, -100.f), v3f(0.f)); + m_cam->setFOV(30.f * core::DEGTORAD); + + scene::ILightSceneNode *light = m_smgr->addLightSceneNode(m_cam); + light->setRadius(1000.f); + + m_smgr->getParameters()->setAttribute(scene::ALLOW_ZWRITE_ON_TRANSPARENT, true); +} + +GUIScene::~GUIScene() +{ + setMesh(nullptr); + + m_smgr->drop(); +} + +scene::IAnimatedMeshSceneNode *GUIScene::setMesh(scene::IAnimatedMesh *mesh) +{ + if (m_mesh) { + m_mesh->remove(); + m_mesh = nullptr; + } + + if (!mesh) + return nullptr; + + m_mesh = m_smgr->addAnimatedMeshSceneNode(mesh); + m_mesh->setPosition(-m_mesh->getBoundingBox().getCenter()); + m_mesh->animateJoints(); + return m_mesh; +} + +void GUIScene::setTexture(u32 idx, video::ITexture *texture) +{ + video::SMaterial &material = m_mesh->getMaterial(idx); + material.MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL; + material.MaterialTypeParam = 0.5f; + material.TextureLayer[0].Texture = texture; + material.setFlag(video::EMF_LIGHTING, false); + material.setFlag(video::EMF_FOG_ENABLE, true); + material.setFlag(video::EMF_BILINEAR_FILTER, false); + material.setFlag(video::EMF_BACK_FACE_CULLING, false); +} + +void GUIScene::draw() +{ + // Control rotation speed based on time + u64 new_time = porting::getTimeMs(); + u64 dtime_ms = 0; + if (m_last_time != 0) + dtime_ms = porting::getDeltaMs(m_last_time, new_time); + m_last_time = new_time; + + core::rect<s32> oldViewPort = m_driver->getViewPort(); + m_driver->setViewPort(getAbsoluteClippingRect()); + core::recti borderRect = Environment->getRootGUIElement()->getAbsoluteClippingRect(); + + if (m_bgcolor != 0) { + Environment->getSkin()->draw3DSunkenPane( + this, m_bgcolor, false, true, borderRect, 0); + } + + core::dimension2d<s32> size = getAbsoluteClippingRect().getSize(); + m_smgr->getActiveCamera()->setAspectRatio((f32)size.Width / (f32)size.Height); + + if (!m_target) { + updateCamera(m_smgr->addEmptySceneNode()); + rotateCamera(v3f(0.f)); + m_cam->bindTargetAndRotation(true); + } + + cameraLoop(); + + // Continuous rotation + if (m_inf_rot) + rotateCamera(v3f(0.f, -0.03f * (float)dtime_ms, 0.f)); + + m_smgr->drawAll(); + + if (m_initial_rotation && m_mesh) { + rotateCamera(v3f(m_custom_rot.X, m_custom_rot.Y, 0.f)); + calcOptimalDistance(); + + m_initial_rotation = false; + } + + m_driver->setViewPort(oldViewPort); +} + +bool GUIScene::OnEvent(const SEvent &event) +{ + if (m_mouse_ctrl && event.EventType == EET_MOUSE_INPUT_EVENT) { + if (event.MouseInput.Event == EMIE_LMOUSE_PRESSED_DOWN) { + m_last_pos = v2f((f32)event.MouseInput.X, (f32)event.MouseInput.Y); + return true; + } else if (event.MouseInput.Event == EMIE_MOUSE_MOVED) { + if (event.MouseInput.isLeftPressed()) { + m_curr_pos = v2f((f32)event.MouseInput.X, (f32)event.MouseInput.Y); + + rotateCamera(v3f( + m_last_pos.Y - m_curr_pos.Y, + m_curr_pos.X - m_last_pos.X, 0.f)); + + m_last_pos = m_curr_pos; + return true; + } + } + } + + return gui::IGUIElement::OnEvent(event); +} + +void GUIScene::setStyles(const std::array<StyleSpec, StyleSpec::NUM_STATES> &styles) +{ + StyleSpec::State state = StyleSpec::STATE_DEFAULT; + StyleSpec style = StyleSpec::getStyleFromStatePropagation(styles, state); + + setNotClipped(style.getBool(StyleSpec::NOCLIP, false)); + setBackgroundColor(style.getColor(StyleSpec::BGCOLOR, m_bgcolor)); +} + +/** + * Sets the frame loop range for the mesh + */ +void GUIScene::setFrameLoop(s32 begin, s32 end) +{ + if (m_mesh->getStartFrame() != begin || m_mesh->getEndFrame() != end) + m_mesh->setFrameLoop(begin, end); +} + +/* Camera control functions */ + +inline void GUIScene::calcOptimalDistance() +{ + core::aabbox3df box = m_mesh->getBoundingBox(); + f32 width = box.MaxEdge.X - box.MinEdge.X; + f32 height = box.MaxEdge.Y - box.MinEdge.Y; + f32 depth = box.MaxEdge.Z - box.MinEdge.Z; + f32 max_width = width > depth ? width : depth; + + const scene::SViewFrustum *f = m_cam->getViewFrustum(); + f32 cam_far = m_cam->getFarValue(); + f32 far_width = core::line3df(f->getFarLeftUp(), f->getFarRightUp()).getLength(); + f32 far_height = core::line3df(f->getFarLeftUp(), f->getFarLeftDown()).getLength(); + + core::recti rect = getAbsolutePosition(); + f32 zoomX = rect.getWidth() / max_width; + f32 zoomY = rect.getHeight() / height; + f32 dist; + + if (zoomX < zoomY) + dist = (max_width / (far_width / cam_far)) + (0.5f * max_width); + else + dist = (height / (far_height / cam_far)) + (0.5f * max_width); + + m_cam_distance = dist; + m_update_cam = true; +} + +void GUIScene::updateCamera(scene::ISceneNode *target) +{ + m_target = target; + updateTargetPos(); + + m_last_target_pos = m_target_pos; + updateCameraPos(); + + m_update_cam = true; +} + +void GUIScene::updateTargetPos() +{ + m_last_target_pos = m_target_pos; + m_target->updateAbsolutePosition(); + m_target_pos = m_target->getAbsolutePosition(); +} + +void GUIScene::setCameraRotation(v3f rot) +{ + correctBounds(rot); + + core::matrix4 mat; + mat.setRotationDegrees(rot); + + m_cam_pos = v3f(0.f, 0.f, m_cam_distance); + mat.rotateVect(m_cam_pos); + + m_cam_pos += m_target_pos; + m_cam->setPosition(m_cam_pos); + m_update_cam = false; +} + +bool GUIScene::correctBounds(v3f &rot) +{ + const float ROTATION_MAX_1 = 60.0f; + const float ROTATION_MAX_2 = 300.0f; + + // Limit and correct the rotation when needed + if (rot.X < 90.f) { + if (rot.X > ROTATION_MAX_1) { + rot.X = ROTATION_MAX_1; + return true; + } + } else if (rot.X < ROTATION_MAX_2) { + rot.X = ROTATION_MAX_2; + return true; + } + + // Not modified + return false; +} + +void GUIScene::cameraLoop() +{ + updateCameraPos(); + updateTargetPos(); + + if (m_target_pos != m_last_target_pos) + m_update_cam = true; + + if (m_update_cam) { + m_cam_pos = m_target_pos + (m_cam_pos - m_target_pos).normalize() * m_cam_distance; + + v3f rot = getCameraRotation(); + if (correctBounds(rot)) + setCameraRotation(rot); + + m_cam->setPosition(m_cam_pos); + m_cam->setTarget(m_target_pos); + + m_update_cam = false; + } +} diff --git a/src/gui/guiScene.h b/src/gui/guiScene.h new file mode 100644 index 000000000..08eb7f350 --- /dev/null +++ b/src/gui/guiScene.h @@ -0,0 +1,86 @@ +/* +Minetest +Copyright (C) 2020 Jean-Patrick Guerrero <jeanpatrick.guerrero@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 "ICameraSceneNode.h" +#include "StyleSpec.h" + +using namespace irr; + +class GUIScene : public gui::IGUIElement +{ +public: + GUIScene(gui::IGUIEnvironment *env, scene::ISceneManager *smgr, + gui::IGUIElement *parent, core::recti rect, s32 id = -1); + + ~GUIScene(); + + scene::IAnimatedMeshSceneNode *setMesh(scene::IAnimatedMesh *mesh = nullptr); + void setTexture(u32 idx, video::ITexture *texture); + void setBackgroundColor(const video::SColor &color) noexcept { m_bgcolor = color; }; + void setFrameLoop(s32 begin, s32 end); + void enableMouseControl(bool enable) noexcept { m_mouse_ctrl = enable; }; + void setRotation(v2f rot) noexcept { m_custom_rot = rot; }; + void enableContinuousRotation(bool enable) noexcept { m_inf_rot = enable; }; + void setStyles(const std::array<StyleSpec, StyleSpec::NUM_STATES> &styles); + + virtual void draw(); + virtual bool OnEvent(const SEvent &event); + +private: + void calcOptimalDistance(); + void updateTargetPos(); + void updateCamera(scene::ISceneNode *target); + void setCameraRotation(v3f rot); + /// @return true indicates that the rotation was corrected + bool correctBounds(v3f &rot); + void cameraLoop(); + + void updateCameraPos() { m_cam_pos = m_cam->getPosition(); }; + v3f getCameraRotation() const { return (m_cam_pos - m_target_pos).getHorizontalAngle(); }; + void rotateCamera(const v3f &delta) { setCameraRotation(getCameraRotation() + delta); }; + + scene::ISceneManager *m_smgr; + video::IVideoDriver *m_driver; + scene::ICameraSceneNode *m_cam; + scene::ISceneNode *m_target = nullptr; + scene::IAnimatedMeshSceneNode *m_mesh = nullptr; + + f32 m_cam_distance = 50.f; + + u64 m_last_time = 0; + + v3f m_cam_pos; + v3f m_target_pos; + v3f m_last_target_pos; + // Cursor positions + v2f m_curr_pos; + v2f m_last_pos; + // Initial rotation + v2f m_custom_rot; + + bool m_mouse_ctrl = true; + bool m_update_cam = false; + bool m_inf_rot = false; + bool m_initial_rotation = true; + + video::SColor m_bgcolor = 0; +}; diff --git a/src/gui/guiScrollBar.cpp b/src/gui/guiScrollBar.cpp index b04ccb9d5..c6a03f3e4 100644 --- a/src/gui/guiScrollBar.cpp +++ b/src/gui/guiScrollBar.cpp @@ -21,7 +21,7 @@ GUIScrollBar::GUIScrollBar(IGUIEnvironment *environment, IGUIElement *parent, s3 is_horizontal(horizontal), is_auto_scaling(auto_scale), dragged_by_slider(false), tray_clicked(false), scroll_pos(0), draw_center(0), thumb_size(0), min_pos(0), max_pos(100), small_step(10), - large_step(50), last_change(0), drag_offset(0), page_size(100), border_size(0) + large_step(50), drag_offset(0), page_size(100), border_size(0) { refreshControls(); setNotClipped(false); diff --git a/src/gui/guiScrollBar.h b/src/gui/guiScrollBar.h index 29493bb99..d18f8e875 100644 --- a/src/gui/guiScrollBar.h +++ b/src/gui/guiScrollBar.h @@ -68,7 +68,6 @@ private: s32 max_pos; s32 small_step; s32 large_step; - u32 last_change; s32 drag_offset; s32 page_size; s32 border_size; diff --git a/src/gui/guiScrollContainer.cpp b/src/gui/guiScrollContainer.cpp index 88cdc7057..0fe4c41bd 100644 --- a/src/gui/guiScrollContainer.cpp +++ b/src/gui/guiScrollContainer.cpp @@ -56,6 +56,18 @@ bool GUIScrollContainer::OnEvent(const SEvent &event) return IGUIElement::OnEvent(event); } +void GUIScrollContainer::draw() +{ + if (isVisible()) { + core::list<IGUIElement *>::Iterator it = Children.begin(); + for (; it != Children.end(); ++it) + if ((*it)->isNotClipped() || + AbsoluteClippingRect.isRectCollided( + (*it)->getAbsolutePosition())) + (*it)->draw(); + } +} + void GUIScrollContainer::updateScrolling() { s32 pos = m_scrollbar->getPos(); diff --git a/src/gui/guiScrollContainer.h b/src/gui/guiScrollContainer.h index a0306291e..9e3ec6e93 100644 --- a/src/gui/guiScrollContainer.h +++ b/src/gui/guiScrollContainer.h @@ -32,6 +32,8 @@ public: virtual bool OnEvent(const SEvent &event) override; + virtual void draw() override; + inline void onScrollEvent(gui::IGUIElement *caller) { if (caller == m_scrollbar) diff --git a/src/gui/guiSkin.cpp b/src/gui/guiSkin.cpp index 8892a00b4..e09209bd9 100644 --- a/src/gui/guiSkin.cpp +++ b/src/gui/guiSkin.cpp @@ -29,7 +29,7 @@ GUISkin::GUISkin(EGUI_SKIN_TYPE type, video::IVideoDriver* driver) {
Colors[EGDC_3D_DARK_SHADOW] = video::SColor(101,50,50,50);
Colors[EGDC_3D_SHADOW] = video::SColor(101,130,130,130);
- Colors[EGDC_3D_FACE] = video::SColor(101,210,210,210);
+ Colors[EGDC_3D_FACE] = video::SColor(220,100,100,100);
Colors[EGDC_3D_HIGH_LIGHT] = video::SColor(101,255,255,255);
Colors[EGDC_3D_LIGHT] = video::SColor(101,210,210,210);
Colors[EGDC_ACTIVE_BORDER] = video::SColor(101,16,14,115);
diff --git a/src/gui/guiTable.cpp b/src/gui/guiTable.cpp index c705e17fb..cab2e19fd 100644 --- a/src/gui/guiTable.cpp +++ b/src/gui/guiTable.cpp @@ -56,7 +56,7 @@ GUITable::GUITable(gui::IGUIEnvironment *env, m_font = skin->getFont(); if (m_font) { m_font->grab(); - m_rowheight = m_font->getDimension(L"A").Height + 4; + m_rowheight = m_font->getDimension(L"Ay").Height + 4; m_rowheight = MYMAX(m_rowheight, 1); } @@ -586,6 +586,31 @@ void GUITable::setSelected(s32 index) } } +void GUITable::setOverrideFont(IGUIFont *font) +{ + if (m_font == font) + return; + + if (font == nullptr) + font = Environment->getSkin()->getFont(); + + if (m_font) + m_font->drop(); + + m_font = font; + m_font->grab(); + + m_rowheight = m_font->getDimension(L"Ay").Height + 4; + m_rowheight = MYMAX(m_rowheight, 1); + + updateScrollBar(); +} + +IGUIFont *GUITable::getOverrideFont() const +{ + return m_font; +} + GUITable::DynamicData GUITable::getDynamicData() const { DynamicData dyndata; diff --git a/src/gui/guiTable.h b/src/gui/guiTable.h index 11093ea72..76a0e94d0 100644 --- a/src/gui/guiTable.h +++ b/src/gui/guiTable.h @@ -123,6 +123,12 @@ public: // Autoscroll to make the selected row fully visible void setSelected(s32 index); + //! Sets another skin independent font. If this is set to zero, the button uses the font of the skin. + virtual void setOverrideFont(gui::IGUIFont *font = nullptr); + + //! Gets the override font (if any) + virtual gui::IGUIFont *getOverrideFont() const; + /* Get selection, scroll position and opened (sub)trees */ DynamicData getDynamicData() const; diff --git a/src/gui/guiVolumeChange.cpp b/src/gui/guiVolumeChange.cpp index 07b11248c..f17cfa986 100644 --- a/src/gui/guiVolumeChange.cpp +++ b/src/gui/guiVolumeChange.cpp @@ -171,7 +171,7 @@ bool GUIVolumeChange::OnEvent(const SEvent& event) if (event.GUIEvent.EventType == gui::EGET_ELEMENT_FOCUS_LOST && isVisible()) { if (!canTakeFocus(event.GUIEvent.Element)) { - dstream << "GUIMainMenu: Not allowing focus change." + infostream << "GUIVolumeChange: Not allowing focus change." << std::endl; // Returning true disables focus change return true; diff --git a/src/gui/intlGUIEditBox.cpp b/src/gui/intlGUIEditBox.cpp index 10395423c..0f09ea746 100644 --- a/src/gui/intlGUIEditBox.cpp +++ b/src/gui/intlGUIEditBox.cpp @@ -59,9 +59,7 @@ namespace gui intlGUIEditBox::intlGUIEditBox(const wchar_t* text, bool border, IGUIEnvironment* environment, IGUIElement* parent, s32 id, const core::rect<s32>& rectangle, bool writable, bool has_vscrollbar) - : IGUIEditBox(environment, parent, id, rectangle), - Border(border), FrameRect(rectangle), - m_scrollbar_width(0), m_vscrollbar(NULL), m_writable(writable) + : GUIEditBox(environment, parent, id, rectangle, border, writable) { #ifdef _DEBUG setDebugName("intlintlGUIEditBox"); @@ -70,10 +68,10 @@ intlGUIEditBox::intlGUIEditBox(const wchar_t* text, bool border, Text = text; if (Environment) - Operator = Environment->getOSOperator(); + m_operator = Environment->getOSOperator(); - if (Operator) - Operator->grab(); + if (m_operator) + m_operator->grab(); // this element can be tabbed to setTabStop(true); @@ -82,12 +80,12 @@ intlGUIEditBox::intlGUIEditBox(const wchar_t* text, bool border, IGUISkin *skin = 0; if (Environment) skin = Environment->getSkin(); - if (Border && skin) + if (m_border && skin) { - FrameRect.UpperLeftCorner.X += skin->getSize(EGDS_TEXT_DISTANCE_X)+1; - FrameRect.UpperLeftCorner.Y += skin->getSize(EGDS_TEXT_DISTANCE_Y)+1; - FrameRect.LowerRightCorner.X -= skin->getSize(EGDS_TEXT_DISTANCE_X)+1; - FrameRect.LowerRightCorner.Y -= skin->getSize(EGDS_TEXT_DISTANCE_Y)+1; + m_frame_rect.UpperLeftCorner.X += skin->getSize(EGDS_TEXT_DISTANCE_X)+1; + m_frame_rect.UpperLeftCorner.Y += skin->getSize(EGDS_TEXT_DISTANCE_Y)+1; + m_frame_rect.LowerRightCorner.X -= skin->getSize(EGDS_TEXT_DISTANCE_X)+1; + m_frame_rect.LowerRightCorner.Y -= skin->getSize(EGDS_TEXT_DISTANCE_Y)+1; } if (skin && has_vscrollbar) { @@ -104,96 +102,11 @@ intlGUIEditBox::intlGUIEditBox(const wchar_t* text, bool border, setWritable(writable); } - -//! destructor -intlGUIEditBox::~intlGUIEditBox() -{ - if (OverrideFont) - OverrideFont->drop(); - - if (Operator) - Operator->drop(); - - if (m_vscrollbar) - m_vscrollbar->drop(); -} - - -//! Sets another skin independent font. -void intlGUIEditBox::setOverrideFont(IGUIFont* font) -{ - if (OverrideFont == font) - return; - - if (OverrideFont) - OverrideFont->drop(); - - OverrideFont = font; - - if (OverrideFont) - OverrideFont->grab(); - - breakText(); -} - -IGUIFont * intlGUIEditBox::getOverrideFont() const -{ - return OverrideFont; -} - -//! Get the font which is used right now for drawing -IGUIFont* intlGUIEditBox::getActiveFont() const -{ - if ( OverrideFont ) - return OverrideFont; - IGUISkin* skin = Environment->getSkin(); - if (skin) - return skin->getFont(); - return 0; -} - -//! Sets another color for the text. -void intlGUIEditBox::setOverrideColor(video::SColor color) -{ - OverrideColor = color; - OverrideColorEnabled = true; -} - -video::SColor intlGUIEditBox::getOverrideColor() const -{ - return OverrideColor; -} - -//! Turns the border on or off -void intlGUIEditBox::setDrawBorder(bool border) -{ - Border = border; -} - //! Sets whether to draw the background void intlGUIEditBox::setDrawBackground(bool draw) { } -//! Sets if the text should use the overide color or the color in the gui skin. -void intlGUIEditBox::enableOverrideColor(bool enable) -{ - OverrideColorEnabled = enable; -} - -bool intlGUIEditBox::isOverrideColorEnabled() const -{ - return OverrideColorEnabled; -} - -//! Enables or disables word wrap -void intlGUIEditBox::setWordWrap(bool enable) -{ - WordWrap = enable; - breakText(); -} - - void intlGUIEditBox::updateAbsolutePosition() { core::rect<s32> oldAbsoluteRect(AbsoluteRect); @@ -205,557 +118,6 @@ void intlGUIEditBox::updateAbsolutePosition() } -//! Checks if word wrap is enabled -bool intlGUIEditBox::isWordWrapEnabled() const -{ - return WordWrap; -} - - -//! Enables or disables newlines. -void intlGUIEditBox::setMultiLine(bool enable) -{ - MultiLine = enable; -} - - -//! Checks if multi line editing is enabled -bool intlGUIEditBox::isMultiLineEnabled() const -{ - return MultiLine; -} - - -void intlGUIEditBox::setPasswordBox(bool passwordBox, wchar_t passwordChar) -{ - PasswordBox = passwordBox; - if (PasswordBox) - { - PasswordChar = passwordChar; - setMultiLine(false); - setWordWrap(false); - BrokenText.clear(); - } -} - - -bool intlGUIEditBox::isPasswordBox() const -{ - return PasswordBox; -} - - -//! Sets text justification -void intlGUIEditBox::setTextAlignment(EGUI_ALIGNMENT horizontal, EGUI_ALIGNMENT vertical) -{ - HAlign = horizontal; - VAlign = vertical; -} - - -//! called if an event happened. -bool intlGUIEditBox::OnEvent(const SEvent& event) -{ - if (IsEnabled) - { - - switch(event.EventType) - { - case EET_GUI_EVENT: - if (event.GUIEvent.EventType == EGET_ELEMENT_FOCUS_LOST) - { - if (event.GUIEvent.Caller == this) - { - MouseMarking = false; - setTextMarkers(0,0); - } - } - break; - case EET_KEY_INPUT_EVENT: - { -#if (defined(__linux__) || defined(__FreeBSD__)) || defined(__DragonFly__) - // ################################################################ - // ValkaTR: - // This part is the difference from the original intlGUIEditBox - // It converts UTF-8 character into a UCS-2 (wchar_t) - wchar_t wc = L'_'; - mbtowc( &wc, (char *) &event.KeyInput.Char, sizeof(event.KeyInput.Char) ); - - //printf( "char: %lc (%u) \r\n", wc, wc ); - - SEvent irrevent(event); - irrevent.KeyInput.Char = wc; - // ################################################################ - - if (processKey(irrevent)) - return true; -#else - if (processKey(event)) - return true; -#endif // defined(linux) - - break; - } - case EET_MOUSE_INPUT_EVENT: - if (processMouse(event)) - return true; - break; - default: - break; - } - } - - return IGUIElement::OnEvent(event); -} - - -bool intlGUIEditBox::processKey(const SEvent& event) -{ - if (!event.KeyInput.PressedDown) - return false; - - bool textChanged = false; - s32 newMarkBegin = MarkBegin; - s32 newMarkEnd = MarkEnd; - - // control shortcut handling - - if (event.KeyInput.Control) - { - // german backlash '\' entered with control + '?' - if ( event.KeyInput.Char == '\\' ) - { - inputChar(event.KeyInput.Char); - return true; - } - - switch(event.KeyInput.Key) - { - case KEY_KEY_A: - // select all - newMarkBegin = 0; - newMarkEnd = Text.size(); - break; - case KEY_KEY_C: - // copy to clipboard - if (!PasswordBox && Operator && MarkBegin != MarkEnd) - { - const s32 realmbgn = MarkBegin < MarkEnd ? MarkBegin : MarkEnd; - const s32 realmend = MarkBegin < MarkEnd ? MarkEnd : MarkBegin; - - core::stringc s; - s = Text.subString(realmbgn, realmend - realmbgn).c_str(); - Operator->copyToClipboard(s.c_str()); - } - break; - case KEY_KEY_X: - // cut to the clipboard - if (!PasswordBox && Operator && MarkBegin != MarkEnd) { - const s32 realmbgn = MarkBegin < MarkEnd ? MarkBegin : MarkEnd; - const s32 realmend = MarkBegin < MarkEnd ? MarkEnd : MarkBegin; - - // copy - core::stringc sc; - sc = Text.subString(realmbgn, realmend - realmbgn).c_str(); - Operator->copyToClipboard(sc.c_str()); - - if (IsEnabled && m_writable) { - // delete - core::stringw s; - s = Text.subString(0, realmbgn); - s.append( Text.subString(realmend, Text.size()-realmend) ); - Text = s; - - CursorPos = realmbgn; - newMarkBegin = 0; - newMarkEnd = 0; - textChanged = true; - } - } - break; - case KEY_KEY_V: - if (!IsEnabled || !m_writable) - break; - - // paste from the clipboard - if (Operator) - { - const s32 realmbgn = MarkBegin < MarkEnd ? MarkBegin : MarkEnd; - const s32 realmend = MarkBegin < MarkEnd ? MarkEnd : MarkBegin; - - // add new character - const c8* p = Operator->getTextFromClipboard(); - if (p) - { - if (MarkBegin == MarkEnd) - { - // insert text - core::stringw s = Text.subString(0, CursorPos); - s.append(p); - s.append( Text.subString(CursorPos, Text.size()-CursorPos) ); - - if (!Max || s.size()<=Max) // thx to Fish FH for fix - { - Text = s; - s = p; - CursorPos += s.size(); - } - } - else - { - // replace text - - core::stringw s = Text.subString(0, realmbgn); - s.append(p); - s.append( Text.subString(realmend, Text.size()-realmend) ); - - if (!Max || s.size()<=Max) // thx to Fish FH for fix - { - Text = s; - s = p; - CursorPos = realmbgn + s.size(); - } - } - } - - newMarkBegin = 0; - newMarkEnd = 0; - textChanged = true; - } - break; - case KEY_HOME: - // move/highlight to start of text - if (event.KeyInput.Shift) - { - newMarkEnd = CursorPos; - newMarkBegin = 0; - CursorPos = 0; - } - else - { - CursorPos = 0; - newMarkBegin = 0; - newMarkEnd = 0; - } - break; - case KEY_END: - // move/highlight to end of text - if (event.KeyInput.Shift) - { - newMarkBegin = CursorPos; - newMarkEnd = Text.size(); - CursorPos = 0; - } - else - { - CursorPos = Text.size(); - newMarkBegin = 0; - newMarkEnd = 0; - } - break; - default: - return false; - } - } - // default keyboard handling - else - switch(event.KeyInput.Key) - { - case KEY_END: - { - s32 p = Text.size(); - if (WordWrap || MultiLine) - { - p = getLineFromPos(CursorPos); - p = BrokenTextPositions[p] + (s32)BrokenText[p].size(); - if (p > 0 && (Text[p-1] == L'\r' || Text[p-1] == L'\n' )) - p-=1; - } - - if (event.KeyInput.Shift) - { - if (MarkBegin == MarkEnd) - newMarkBegin = CursorPos; - - newMarkEnd = p; - } - else - { - newMarkBegin = 0; - newMarkEnd = 0; - } - CursorPos = p; - BlinkStartTime = porting::getTimeMs(); - } - break; - case KEY_HOME: - { - - s32 p = 0; - if (WordWrap || MultiLine) - { - p = getLineFromPos(CursorPos); - p = BrokenTextPositions[p]; - } - - if (event.KeyInput.Shift) - { - if (MarkBegin == MarkEnd) - newMarkBegin = CursorPos; - newMarkEnd = p; - } - else - { - newMarkBegin = 0; - newMarkEnd = 0; - } - CursorPos = p; - BlinkStartTime = porting::getTimeMs(); - } - break; - case KEY_RETURN: - if (MultiLine) - { - inputChar(L'\n'); - return true; - } - else - { - sendGuiEvent( EGET_EDITBOX_ENTER ); - } - break; - case KEY_LEFT: - - if (event.KeyInput.Shift) - { - if (CursorPos > 0) - { - if (MarkBegin == MarkEnd) - newMarkBegin = CursorPos; - - newMarkEnd = CursorPos-1; - } - } - else - { - newMarkBegin = 0; - newMarkEnd = 0; - } - - if (CursorPos > 0) CursorPos--; - BlinkStartTime = porting::getTimeMs(); - break; - - case KEY_RIGHT: - if (event.KeyInput.Shift) - { - if (Text.size() > (u32)CursorPos) - { - if (MarkBegin == MarkEnd) - newMarkBegin = CursorPos; - - newMarkEnd = CursorPos+1; - } - } - else - { - newMarkBegin = 0; - newMarkEnd = 0; - } - - if (Text.size() > (u32)CursorPos) CursorPos++; - BlinkStartTime = porting::getTimeMs(); - break; - case KEY_UP: - if (MultiLine || (WordWrap && BrokenText.size() > 1) ) - { - s32 lineNo = getLineFromPos(CursorPos); - s32 mb = (MarkBegin == MarkEnd) ? CursorPos : (MarkBegin > MarkEnd ? MarkBegin : MarkEnd); - if (lineNo > 0) - { - s32 cp = CursorPos - BrokenTextPositions[lineNo]; - if ((s32)BrokenText[lineNo-1].size() < cp) - CursorPos = BrokenTextPositions[lineNo-1] + (s32)BrokenText[lineNo-1].size()-1; - else - CursorPos = BrokenTextPositions[lineNo-1] + cp; - } - - if (event.KeyInput.Shift) - { - newMarkBegin = mb; - newMarkEnd = CursorPos; - } - else - { - newMarkBegin = 0; - newMarkEnd = 0; - } - - } - else - { - return false; - } - break; - case KEY_DOWN: - if (MultiLine || (WordWrap && BrokenText.size() > 1) ) - { - s32 lineNo = getLineFromPos(CursorPos); - s32 mb = (MarkBegin == MarkEnd) ? CursorPos : (MarkBegin < MarkEnd ? MarkBegin : MarkEnd); - if (lineNo < (s32)BrokenText.size()-1) - { - s32 cp = CursorPos - BrokenTextPositions[lineNo]; - if ((s32)BrokenText[lineNo+1].size() < cp) - CursorPos = BrokenTextPositions[lineNo+1] + BrokenText[lineNo+1].size()-1; - else - CursorPos = BrokenTextPositions[lineNo+1] + cp; - } - - if (event.KeyInput.Shift) - { - newMarkBegin = mb; - newMarkEnd = CursorPos; - } - else - { - newMarkBegin = 0; - newMarkEnd = 0; - } - - } - else - { - return false; - } - break; - - case KEY_BACK: - if (!this->IsEnabled || !m_writable) - break; - - if (!Text.empty()) { - core::stringw s; - - if (MarkBegin != MarkEnd) - { - // delete marked text - const s32 realmbgn = MarkBegin < MarkEnd ? MarkBegin : MarkEnd; - const s32 realmend = MarkBegin < MarkEnd ? MarkEnd : MarkBegin; - - s = Text.subString(0, realmbgn); - s.append( Text.subString(realmend, Text.size()-realmend) ); - Text = s; - - CursorPos = realmbgn; - } - else - { - // delete text behind cursor - if (CursorPos>0) - s = Text.subString(0, CursorPos-1); - else - s = L""; - s.append( Text.subString(CursorPos, Text.size()-CursorPos) ); - Text = s; - --CursorPos; - } - - if (CursorPos < 0) - CursorPos = 0; - BlinkStartTime = porting::getTimeMs(); - newMarkBegin = 0; - newMarkEnd = 0; - textChanged = true; - } - break; - case KEY_DELETE: - if (!this->IsEnabled || !m_writable) - break; - - if (!Text.empty()) { - core::stringw s; - - if (MarkBegin != MarkEnd) - { - // delete marked text - const s32 realmbgn = MarkBegin < MarkEnd ? MarkBegin : MarkEnd; - const s32 realmend = MarkBegin < MarkEnd ? MarkEnd : MarkBegin; - - s = Text.subString(0, realmbgn); - s.append( Text.subString(realmend, Text.size()-realmend) ); - Text = s; - - CursorPos = realmbgn; - } - else - { - // delete text before cursor - s = Text.subString(0, CursorPos); - s.append( Text.subString(CursorPos+1, Text.size()-CursorPos-1) ); - Text = s; - } - - if (CursorPos > (s32)Text.size()) - CursorPos = (s32)Text.size(); - - BlinkStartTime = porting::getTimeMs(); - newMarkBegin = 0; - newMarkEnd = 0; - textChanged = true; - } - break; - - case KEY_ESCAPE: - case KEY_TAB: - case KEY_SHIFT: - case KEY_F1: - case KEY_F2: - case KEY_F3: - case KEY_F4: - case KEY_F5: - case KEY_F6: - case KEY_F7: - case KEY_F8: - case KEY_F9: - case KEY_F10: - case KEY_F11: - case KEY_F12: - case KEY_F13: - case KEY_F14: - case KEY_F15: - case KEY_F16: - case KEY_F17: - case KEY_F18: - case KEY_F19: - case KEY_F20: - case KEY_F21: - case KEY_F22: - case KEY_F23: - case KEY_F24: - // ignore these keys - return false; - - default: - inputChar(event.KeyInput.Char); - return true; - } - - // Set new text markers - setTextMarkers( newMarkBegin, newMarkEnd ); - - // break the text if it has changed - if (textChanged) - { - breakText(); - sendGuiEvent(EGET_EDITBOX_CHANGED); - } - - calculateScrollPos(); - - return true; -} - - //! draws the element and its children void intlGUIEditBox::draw() { @@ -768,31 +130,31 @@ void intlGUIEditBox::draw() if (!skin) return; - FrameRect = AbsoluteRect; + m_frame_rect = AbsoluteRect; // draw the border - if (Border) + if (m_border) { if (m_writable) { skin->draw3DSunkenPane(this, skin->getColor(EGDC_WINDOW), - false, true, FrameRect, &AbsoluteClippingRect); + false, true, m_frame_rect, &AbsoluteClippingRect); } - FrameRect.UpperLeftCorner.X += skin->getSize(EGDS_TEXT_DISTANCE_X)+1; - FrameRect.UpperLeftCorner.Y += skin->getSize(EGDS_TEXT_DISTANCE_Y)+1; - FrameRect.LowerRightCorner.X -= skin->getSize(EGDS_TEXT_DISTANCE_X)+1; - FrameRect.LowerRightCorner.Y -= skin->getSize(EGDS_TEXT_DISTANCE_Y)+1; + m_frame_rect.UpperLeftCorner.X += skin->getSize(EGDS_TEXT_DISTANCE_X)+1; + m_frame_rect.UpperLeftCorner.Y += skin->getSize(EGDS_TEXT_DISTANCE_Y)+1; + m_frame_rect.LowerRightCorner.X -= skin->getSize(EGDS_TEXT_DISTANCE_X)+1; + m_frame_rect.LowerRightCorner.Y -= skin->getSize(EGDS_TEXT_DISTANCE_Y)+1; } updateVScrollBar(); - core::rect<s32> localClipRect = FrameRect; + core::rect<s32> localClipRect = m_frame_rect; localClipRect.clipAgainst(AbsoluteClippingRect); // draw the text - IGUIFont* font = OverrideFont; - if (!OverrideFont) + IGUIFont* font = m_override_font; + if (!m_override_font) font = skin->getFont(); s32 cursorLine = 0; @@ -800,7 +162,7 @@ void intlGUIEditBox::draw() if (font) { - if (LastBreakFont != font) + if (m_last_break_font != font) { breakText(); } @@ -813,23 +175,23 @@ void intlGUIEditBox::draw() core::stringw s, s2; // get mark position - const bool ml = (!PasswordBox && (WordWrap || MultiLine)); - const s32 realmbgn = MarkBegin < MarkEnd ? MarkBegin : MarkEnd; - const s32 realmend = MarkBegin < MarkEnd ? MarkEnd : MarkBegin; + const bool ml = (!m_passwordbox && (m_word_wrap || m_multiline)); + const s32 realmbgn = m_mark_begin < m_mark_end ? m_mark_begin : m_mark_end; + const s32 realmend = m_mark_begin < m_mark_end ? m_mark_end : m_mark_begin; const s32 hlineStart = ml ? getLineFromPos(realmbgn) : 0; const s32 hlineCount = ml ? getLineFromPos(realmend) - hlineStart + 1 : 1; - const s32 lineCount = ml ? BrokenText.size() : 1; + const s32 lineCount = ml ? m_broken_text.size() : 1; // Save the override color information. // Then, alter it if the edit box is disabled. - const bool prevOver = OverrideColorEnabled; - const video::SColor prevColor = OverrideColor; + const bool prevOver = m_override_color_enabled; + const video::SColor prevColor = m_override_color; if (!Text.empty()) { - if (!IsEnabled && !OverrideColorEnabled) + if (!IsEnabled && !m_override_color_enabled) { - OverrideColorEnabled = true; - OverrideColor = skin->getColor(EGDC_GRAY_TEXT); + m_override_color_enabled = true; + m_override_color = skin->getColor(EGDC_GRAY_TEXT); } for (s32 i=0; i < lineCount; ++i) @@ -838,43 +200,43 @@ void intlGUIEditBox::draw() // clipping test - don't draw anything outside the visible area core::rect<s32> c = localClipRect; - c.clipAgainst(CurrentTextRect); + c.clipAgainst(m_current_text_rect); if (!c.isValid()) continue; // get current line - if (PasswordBox) + if (m_passwordbox) { - if (BrokenText.size() != 1) + if (m_broken_text.size() != 1) { - BrokenText.clear(); - BrokenText.push_back(core::stringw()); + m_broken_text.clear(); + m_broken_text.emplace_back(); } - if (BrokenText[0].size() != Text.size()) + if (m_broken_text[0].size() != Text.size()) { - BrokenText[0] = Text; + m_broken_text[0] = Text; for (u32 q = 0; q < Text.size(); ++q) { - BrokenText[0] [q] = PasswordChar; + m_broken_text[0] [q] = m_passwordchar; } } - txtLine = &BrokenText[0]; + txtLine = &m_broken_text[0]; startPos = 0; } else { - txtLine = ml ? &BrokenText[i] : &Text; - startPos = ml ? BrokenTextPositions[i] : 0; + txtLine = ml ? &m_broken_text[i] : &Text; + startPos = ml ? m_broken_text_positions[i] : 0; } // draw normal text - font->draw(txtLine->c_str(), CurrentTextRect, - OverrideColorEnabled ? OverrideColor : skin->getColor(EGDC_BUTTON_TEXT), + font->draw(txtLine->c_str(), m_current_text_rect, + m_override_color_enabled ? m_override_color : skin->getColor(EGDC_BUTTON_TEXT), false, true, &localClipRect); // draw mark and marked text - if (focus && MarkBegin != MarkEnd && i >= hlineStart && i < hlineStart + hlineCount) + if (focus && m_mark_begin != m_mark_end && i >= hlineStart && i < hlineStart + hlineCount) { s32 mbegin = 0, mend = 0; @@ -903,47 +265,47 @@ void intlGUIEditBox::draw() else mend = font->getDimension(txtLine->c_str()).Width; - CurrentTextRect.UpperLeftCorner.X += mbegin; - CurrentTextRect.LowerRightCorner.X = CurrentTextRect.UpperLeftCorner.X + mend - mbegin; + m_current_text_rect.UpperLeftCorner.X += mbegin; + m_current_text_rect.LowerRightCorner.X = m_current_text_rect.UpperLeftCorner.X + mend - mbegin; // draw mark - skin->draw2DRectangle(this, skin->getColor(EGDC_HIGH_LIGHT), CurrentTextRect, &localClipRect); + skin->draw2DRectangle(this, skin->getColor(EGDC_HIGH_LIGHT), m_current_text_rect, &localClipRect); // draw marked text s = txtLine->subString(lineStartPos, lineEndPos - lineStartPos); if (!s.empty()) - font->draw(s.c_str(), CurrentTextRect, - OverrideColorEnabled ? OverrideColor : skin->getColor(EGDC_HIGH_LIGHT_TEXT), + font->draw(s.c_str(), m_current_text_rect, + m_override_color_enabled ? m_override_color : skin->getColor(EGDC_HIGH_LIGHT_TEXT), false, true, &localClipRect); } } // Return the override color information to its previous settings. - OverrideColorEnabled = prevOver; - OverrideColor = prevColor; + m_override_color_enabled = prevOver; + m_override_color = prevColor; } // draw cursor - if (WordWrap || MultiLine) + if (m_word_wrap || m_multiline) { - cursorLine = getLineFromPos(CursorPos); - txtLine = &BrokenText[cursorLine]; - startPos = BrokenTextPositions[cursorLine]; + cursorLine = getLineFromPos(m_cursor_pos); + txtLine = &m_broken_text[cursorLine]; + startPos = m_broken_text_positions[cursorLine]; } - s = txtLine->subString(0,CursorPos-startPos); + s = txtLine->subString(0,m_cursor_pos-startPos); charcursorpos = font->getDimension(s.c_str()).Width + - font->getKerningWidth(L"_", CursorPos-startPos > 0 ? &((*txtLine)[CursorPos-startPos-1]) : 0); + font->getKerningWidth(L"_", m_cursor_pos-startPos > 0 ? &((*txtLine)[m_cursor_pos-startPos-1]) : 0); if (m_writable) { - if (focus && (porting::getTimeMs() - BlinkStartTime) % 700 < 350) { + if (focus && (porting::getTimeMs() - m_blink_start_time) % 700 < 350) { setTextRect(cursorLine); - CurrentTextRect.UpperLeftCorner.X += charcursorpos; + m_current_text_rect.UpperLeftCorner.X += charcursorpos; - font->draw(L"_", CurrentTextRect, - OverrideColorEnabled ? OverrideColor : skin->getColor(EGDC_BUTTON_TEXT), + font->draw(L"_", m_current_text_rect, + m_override_color_enabled ? m_override_color : skin->getColor(EGDC_BUTTON_TEXT), false, true, &localClipRect); } } @@ -954,154 +316,11 @@ void intlGUIEditBox::draw() } -//! Sets the new caption of this element. -void intlGUIEditBox::setText(const wchar_t* text) -{ - Text = text; - if (u32(CursorPos) > Text.size()) - CursorPos = Text.size(); - HScrollPos = 0; - breakText(); -} - - -//! Enables or disables automatic scrolling with cursor position -//! \param enable: If set to true, the text will move around with the cursor position -void intlGUIEditBox::setAutoScroll(bool enable) -{ - AutoScroll = enable; -} - - -//! Checks to see if automatic scrolling is enabled -//! \return true if automatic scrolling is enabled, false if not -bool intlGUIEditBox::isAutoScrollEnabled() const -{ - return AutoScroll; -} - - -//! Gets the area of the text in the edit box -//! \return Returns the size in pixels of the text -core::dimension2du intlGUIEditBox::getTextDimension() -{ - core::rect<s32> ret; - - setTextRect(0); - ret = CurrentTextRect; - - for (u32 i=1; i < BrokenText.size(); ++i) - { - setTextRect(i); - ret.addInternalPoint(CurrentTextRect.UpperLeftCorner); - ret.addInternalPoint(CurrentTextRect.LowerRightCorner); - } - - return core::dimension2du(ret.getSize()); -} - - -//! Sets the maximum amount of characters which may be entered in the box. -//! \param max: Maximum amount of characters. If 0, the character amount is -//! infinity. -void intlGUIEditBox::setMax(u32 max) -{ - Max = max; - - if (Text.size() > Max && Max != 0) - Text = Text.subString(0, Max); -} - - -//! Returns maximum amount of characters, previously set by setMax(); -u32 intlGUIEditBox::getMax() const -{ - return Max; -} - - -bool intlGUIEditBox::processMouse(const SEvent& event) -{ - switch(event.MouseInput.Event) - { - case irr::EMIE_LMOUSE_LEFT_UP: - if (Environment->hasFocus(this)) - { - CursorPos = getCursorPos(event.MouseInput.X, event.MouseInput.Y); - if (MouseMarking) - { - setTextMarkers( MarkBegin, CursorPos ); - } - MouseMarking = false; - calculateScrollPos(); - return true; - } - break; - case irr::EMIE_MOUSE_MOVED: - { - if (MouseMarking) - { - CursorPos = getCursorPos(event.MouseInput.X, event.MouseInput.Y); - setTextMarkers( MarkBegin, CursorPos ); - calculateScrollPos(); - return true; - } - } - break; - case EMIE_LMOUSE_PRESSED_DOWN: - if (!Environment->hasFocus(this)) - { - BlinkStartTime = porting::getTimeMs(); - MouseMarking = true; - CursorPos = getCursorPos(event.MouseInput.X, event.MouseInput.Y); - setTextMarkers(CursorPos, CursorPos ); - calculateScrollPos(); - return true; - } - else - { - if (!AbsoluteClippingRect.isPointInside( - core::position2d<s32>(event.MouseInput.X, event.MouseInput.Y))) { - return false; - } - - - // move cursor - CursorPos = getCursorPos(event.MouseInput.X, event.MouseInput.Y); - - s32 newMarkBegin = MarkBegin; - if (!MouseMarking) - newMarkBegin = CursorPos; - - MouseMarking = true; - setTextMarkers( newMarkBegin, CursorPos); - calculateScrollPos(); - return true; - } - break; - case EMIE_MOUSE_WHEEL: - if (m_vscrollbar && m_vscrollbar->isVisible()) { - s32 pos = m_vscrollbar->getPos(); - s32 step = m_vscrollbar->getSmallStep(); - m_vscrollbar->setPos(pos - event.MouseInput.Wheel * step); - } - break; - default: - break; - } - - return false; -} - - s32 intlGUIEditBox::getCursorPos(s32 x, s32 y) { - IGUIFont* font = OverrideFont; - IGUISkin* skin = Environment->getSkin(); - if (!OverrideFont) - font = skin->getFont(); + IGUIFont* font = getActiveFont(); - const u32 lineCount = (WordWrap || MultiLine) ? BrokenText.size() : 1; + const u32 lineCount = (m_word_wrap || m_multiline) ? m_broken_text.size() : 1; core::stringw *txtLine = NULL; s32 startPos = 0; @@ -1110,29 +329,29 @@ s32 intlGUIEditBox::getCursorPos(s32 x, s32 y) for (; curr_line_idx < lineCount; ++curr_line_idx) { setTextRect(curr_line_idx); - if (curr_line_idx == 0 && y < CurrentTextRect.UpperLeftCorner.Y) - y = CurrentTextRect.UpperLeftCorner.Y; - if (curr_line_idx == lineCount - 1 && y > CurrentTextRect.LowerRightCorner.Y) - y = CurrentTextRect.LowerRightCorner.Y; + if (curr_line_idx == 0 && y < m_current_text_rect.UpperLeftCorner.Y) + y = m_current_text_rect.UpperLeftCorner.Y; + if (curr_line_idx == lineCount - 1 && y > m_current_text_rect.LowerRightCorner.Y) + y = m_current_text_rect.LowerRightCorner.Y; // is it inside this region? - if (y >= CurrentTextRect.UpperLeftCorner.Y && y <= CurrentTextRect.LowerRightCorner.Y) { + if (y >= m_current_text_rect.UpperLeftCorner.Y && y <= m_current_text_rect.LowerRightCorner.Y) { // we've found the clicked line - txtLine = (WordWrap || MultiLine) ? &BrokenText[curr_line_idx] : &Text; - startPos = (WordWrap || MultiLine) ? BrokenTextPositions[curr_line_idx] : 0; + txtLine = (m_word_wrap || m_multiline) ? &m_broken_text[curr_line_idx] : &Text; + startPos = (m_word_wrap || m_multiline) ? m_broken_text_positions[curr_line_idx] : 0; break; } } - if (x < CurrentTextRect.UpperLeftCorner.X) - x = CurrentTextRect.UpperLeftCorner.X; - else if (x > CurrentTextRect.LowerRightCorner.X) - x = CurrentTextRect.LowerRightCorner.X; + if (x < m_current_text_rect.UpperLeftCorner.X) + x = m_current_text_rect.UpperLeftCorner.X; + else if (x > m_current_text_rect.LowerRightCorner.X) + x = m_current_text_rect.LowerRightCorner.X; - s32 idx = font->getCharacterFromPos(txtLine->c_str(), x - CurrentTextRect.UpperLeftCorner.X); + s32 idx = font->getCharacterFromPos(txtLine->c_str(), x - m_current_text_rect.UpperLeftCorner.X); // Special handling for last line, if we are on limits, add 1 extra shift because idx // will be the last char, not null char of the wstring - if (curr_line_idx == lineCount - 1 && x == CurrentTextRect.LowerRightCorner.X) + if (curr_line_idx == lineCount - 1 && x == m_current_text_rect.LowerRightCorner.X) idx++; return rangelim(idx + startPos, 0, S32_MAX); @@ -1144,20 +363,20 @@ void intlGUIEditBox::breakText() { IGUISkin* skin = Environment->getSkin(); - if ((!WordWrap && !MultiLine) || !skin) + if ((!m_word_wrap && !m_multiline) || !skin) return; - BrokenText.clear(); // need to reallocate :/ - BrokenTextPositions.set_used(0); + m_broken_text.clear(); // need to reallocate :/ + m_broken_text_positions.clear(); - IGUIFont* font = OverrideFont; - if (!OverrideFont) + IGUIFont* font = m_override_font; + if (!m_override_font) font = skin->getFont(); if (!font) return; - LastBreakFont = font; + m_last_break_font = font; core::stringw line; core::stringw word; @@ -1165,7 +384,7 @@ void intlGUIEditBox::breakText() s32 lastLineStart = 0; s32 size = Text.size(); s32 length = 0; - s32 elWidth = RelativeRect.getWidth() - 6; + s32 elWidth = RelativeRect.getWidth() - m_scrollbar_width - 10; wchar_t c; for (s32 i=0; i<size; ++i) @@ -1190,7 +409,7 @@ void intlGUIEditBox::breakText() } // don't break if we're not a multi-line edit box - if (!MultiLine) + if (!m_multiline) lineBreak = false; if (c == L' ' || c == 0 || i == (size-1)) @@ -1201,12 +420,12 @@ void intlGUIEditBox::breakText() s32 whitelgth = font->getDimension(whitespace.c_str()).Width; s32 worldlgth = font->getDimension(word.c_str()).Width; - if (WordWrap && length + worldlgth + whitelgth > elWidth) + if (m_word_wrap && length + worldlgth + whitelgth > elWidth) { // break to next line length = worldlgth; - BrokenText.push_back(line); - BrokenTextPositions.push_back(lastLineStart); + m_broken_text.push_back(line); + m_broken_text_positions.push_back(lastLineStart); lastLineStart = i - (s32)word.size(); line = word; } @@ -1229,8 +448,8 @@ void intlGUIEditBox::breakText() { line += whitespace; line += word; - BrokenText.push_back(line); - BrokenTextPositions.push_back(lastLineStart); + m_broken_text.push_back(line); + m_broken_text_positions.push_back(lastLineStart); lastLineStart = i+1; line = L""; word = L""; @@ -1247,8 +466,8 @@ void intlGUIEditBox::breakText() line += whitespace; line += word; - BrokenText.push_back(line); - BrokenTextPositions.push_back(lastLineStart); + m_broken_text.push_back(line); + m_broken_text_positions.push_back(lastLineStart); } @@ -1260,16 +479,16 @@ void intlGUIEditBox::setTextRect(s32 line) if (!skin) return; - IGUIFont* font = OverrideFont ? OverrideFont : skin->getFont(); + IGUIFont* font = m_override_font ? m_override_font : skin->getFont(); if (!font) return; // get text dimension - const u32 lineCount = (WordWrap || MultiLine) ? BrokenText.size() : 1; - if (WordWrap || MultiLine) + const u32 lineCount = (m_word_wrap || m_multiline) ? m_broken_text.size() : 1; + if (m_word_wrap || m_multiline) { - d = font->getDimension(BrokenText[line].c_str()); + d = font->getDimension(m_broken_text[line].c_str()); } else { @@ -1279,197 +498,112 @@ void intlGUIEditBox::setTextRect(s32 line) d.Height += font->getKerningHeight(); // justification - switch (HAlign) + switch (m_halign) { case EGUIA_CENTER: // align to h centre - CurrentTextRect.UpperLeftCorner.X = (FrameRect.getWidth()/2) - (d.Width/2); - CurrentTextRect.LowerRightCorner.X = (FrameRect.getWidth()/2) + (d.Width/2); + m_current_text_rect.UpperLeftCorner.X = (m_frame_rect.getWidth()/2) - (d.Width/2); + m_current_text_rect.LowerRightCorner.X = (m_frame_rect.getWidth()/2) + (d.Width/2); break; case EGUIA_LOWERRIGHT: // align to right edge - CurrentTextRect.UpperLeftCorner.X = FrameRect.getWidth() - d.Width; - CurrentTextRect.LowerRightCorner.X = FrameRect.getWidth(); + m_current_text_rect.UpperLeftCorner.X = m_frame_rect.getWidth() - d.Width; + m_current_text_rect.LowerRightCorner.X = m_frame_rect.getWidth(); break; default: // align to left edge - CurrentTextRect.UpperLeftCorner.X = 0; - CurrentTextRect.LowerRightCorner.X = d.Width; + m_current_text_rect.UpperLeftCorner.X = 0; + m_current_text_rect.LowerRightCorner.X = d.Width; } - switch (VAlign) + switch (m_valign) { case EGUIA_CENTER: // align to v centre - CurrentTextRect.UpperLeftCorner.Y = - (FrameRect.getHeight()/2) - (lineCount*d.Height)/2 + d.Height*line; + m_current_text_rect.UpperLeftCorner.Y = + (m_frame_rect.getHeight()/2) - (lineCount*d.Height)/2 + d.Height*line; break; case EGUIA_LOWERRIGHT: // align to bottom edge - CurrentTextRect.UpperLeftCorner.Y = - FrameRect.getHeight() - lineCount*d.Height + d.Height*line; + m_current_text_rect.UpperLeftCorner.Y = + m_frame_rect.getHeight() - lineCount*d.Height + d.Height*line; break; default: // align to top edge - CurrentTextRect.UpperLeftCorner.Y = d.Height*line; + m_current_text_rect.UpperLeftCorner.Y = d.Height*line; break; } - CurrentTextRect.UpperLeftCorner.X -= HScrollPos; - CurrentTextRect.LowerRightCorner.X -= HScrollPos; - CurrentTextRect.UpperLeftCorner.Y -= VScrollPos; - CurrentTextRect.LowerRightCorner.Y = CurrentTextRect.UpperLeftCorner.Y + d.Height; - - CurrentTextRect += FrameRect.UpperLeftCorner; - -} - - -s32 intlGUIEditBox::getLineFromPos(s32 pos) -{ - if (!WordWrap && !MultiLine) - return 0; - - s32 i=0; - while (i < (s32)BrokenTextPositions.size()) - { - if (BrokenTextPositions[i] > pos) - return i-1; - ++i; - } - return (s32)BrokenTextPositions.size() - 1; -} - - -void intlGUIEditBox::inputChar(wchar_t c) -{ - if (!IsEnabled || !m_writable) - return; - - if (c != 0) - { - if (Text.size() < Max || Max == 0) - { - core::stringw s; + m_current_text_rect.UpperLeftCorner.X -= m_hscroll_pos; + m_current_text_rect.LowerRightCorner.X -= m_hscroll_pos; + m_current_text_rect.UpperLeftCorner.Y -= m_vscroll_pos; + m_current_text_rect.LowerRightCorner.Y = m_current_text_rect.UpperLeftCorner.Y + d.Height; - if (MarkBegin != MarkEnd) - { - // replace marked text - const s32 realmbgn = MarkBegin < MarkEnd ? MarkBegin : MarkEnd; - const s32 realmend = MarkBegin < MarkEnd ? MarkEnd : MarkBegin; - - s = Text.subString(0, realmbgn); - s.append(c); - s.append( Text.subString(realmend, Text.size()-realmend) ); - Text = s; - CursorPos = realmbgn+1; - } - else - { - // add new character - s = Text.subString(0, CursorPos); - s.append(c); - s.append( Text.subString(CursorPos, Text.size()-CursorPos) ); - Text = s; - ++CursorPos; - } + m_current_text_rect += m_frame_rect.UpperLeftCorner; - BlinkStartTime = porting::getTimeMs(); - setTextMarkers(0, 0); - } - } - breakText(); - sendGuiEvent(EGET_EDITBOX_CHANGED); - calculateScrollPos(); } - void intlGUIEditBox::calculateScrollPos() { - if (!AutoScroll) + if (!m_autoscroll) return; // calculate horizontal scroll position - s32 cursLine = getLineFromPos(CursorPos); + s32 cursLine = getLineFromPos(m_cursor_pos); setTextRect(cursLine); // don't do horizontal scrolling when wordwrap is enabled. - if (!WordWrap) + if (!m_word_wrap) { // get cursor position IGUISkin* skin = Environment->getSkin(); if (!skin) return; - IGUIFont* font = OverrideFont ? OverrideFont : skin->getFont(); + IGUIFont* font = m_override_font ? m_override_font : skin->getFont(); if (!font) return; - core::stringw *txtLine = MultiLine ? &BrokenText[cursLine] : &Text; - s32 cPos = MultiLine ? CursorPos - BrokenTextPositions[cursLine] : CursorPos; + core::stringw *txtLine = m_multiline ? &m_broken_text[cursLine] : &Text; + s32 cPos = m_multiline ? m_cursor_pos - m_broken_text_positions[cursLine] : m_cursor_pos; - s32 cStart = CurrentTextRect.UpperLeftCorner.X + HScrollPos + + s32 cStart = m_current_text_rect.UpperLeftCorner.X + m_hscroll_pos + font->getDimension(txtLine->subString(0, cPos).c_str()).Width; s32 cEnd = cStart + font->getDimension(L"_ ").Width; - if (FrameRect.LowerRightCorner.X < cEnd) - HScrollPos = cEnd - FrameRect.LowerRightCorner.X; - else if (FrameRect.UpperLeftCorner.X > cStart) - HScrollPos = cStart - FrameRect.UpperLeftCorner.X; + if (m_frame_rect.LowerRightCorner.X < cEnd) + m_hscroll_pos = cEnd - m_frame_rect.LowerRightCorner.X; + else if (m_frame_rect.UpperLeftCorner.X > cStart) + m_hscroll_pos = cStart - m_frame_rect.UpperLeftCorner.X; else - HScrollPos = 0; + m_hscroll_pos = 0; // todo: adjust scrollbar } - if (!WordWrap && !MultiLine) + if (!m_word_wrap && !m_multiline) return; // vertical scroll position - if (FrameRect.LowerRightCorner.Y < CurrentTextRect.LowerRightCorner.Y) - VScrollPos += CurrentTextRect.LowerRightCorner.Y - FrameRect.LowerRightCorner.Y; // scrolling downwards - else if (FrameRect.UpperLeftCorner.Y > CurrentTextRect.UpperLeftCorner.Y) - VScrollPos += CurrentTextRect.UpperLeftCorner.Y - FrameRect.UpperLeftCorner.Y; // scrolling upwards + if (m_frame_rect.LowerRightCorner.Y < m_current_text_rect.LowerRightCorner.Y) + m_vscroll_pos += m_current_text_rect.LowerRightCorner.Y - m_frame_rect.LowerRightCorner.Y; // scrolling downwards + else if (m_frame_rect.UpperLeftCorner.Y > m_current_text_rect.UpperLeftCorner.Y) + m_vscroll_pos += m_current_text_rect.UpperLeftCorner.Y - m_frame_rect.UpperLeftCorner.Y; // scrolling upwards // todo: adjust scrollbar if (m_vscrollbar) - m_vscrollbar->setPos(VScrollPos); + m_vscrollbar->setPos(m_vscroll_pos); } -//! set text markers -void intlGUIEditBox::setTextMarkers(s32 begin, s32 end) -{ - if ( begin != MarkBegin || end != MarkEnd ) - { - MarkBegin = begin; - MarkEnd = end; - sendGuiEvent(EGET_EDITBOX_MARKING_CHANGED); - } -} - -//! send some gui event to parent -void intlGUIEditBox::sendGuiEvent(EGUI_EVENT_TYPE type) -{ - if ( Parent ) - { - SEvent e; - e.EventType = EET_GUI_EVENT; - e.GUIEvent.Caller = this; - e.GUIEvent.Element = 0; - e.GUIEvent.EventType = type; - - Parent->OnEvent(e); - } -} //! Create a vertical scrollbar void intlGUIEditBox::createVScrollBar() { s32 fontHeight = 1; - if (OverrideFont) { - fontHeight = OverrideFont->getDimension(L"").Height; + if (m_override_font) { + fontHeight = m_override_font->getDimension(L"").Height; } else { if (IGUISkin* skin = Environment->getSkin()) { if (IGUIFont* font = skin->getFont()) { @@ -1478,10 +612,8 @@ void intlGUIEditBox::createVScrollBar() } } - RelativeRect.LowerRightCorner.X -= m_scrollbar_width + 4; - - irr::core::rect<s32> scrollbarrect = FrameRect; - scrollbarrect.UpperLeftCorner.X += FrameRect.getWidth() - m_scrollbar_width; + irr::core::rect<s32> scrollbarrect = m_frame_rect; + scrollbarrect.UpperLeftCorner.X += m_frame_rect.getWidth() - m_scrollbar_width; m_vscrollbar = new GUIScrollBar(Environment, getParent(), -1, scrollbarrect, false, true); @@ -1490,110 +622,5 @@ void intlGUIEditBox::createVScrollBar() m_vscrollbar->setLargeStep(10 * fontHeight); } -//! Update the vertical scrollbar (visibilty & scroll position) -void intlGUIEditBox::updateVScrollBar() -{ - if (!m_vscrollbar) - return; - - // OnScrollBarChanged(...) - if (m_vscrollbar->getPos() != VScrollPos) { - s32 deltaScrollY = m_vscrollbar->getPos() - VScrollPos; - CurrentTextRect.UpperLeftCorner.Y -= deltaScrollY; - CurrentTextRect.LowerRightCorner.Y -= deltaScrollY; - - s32 scrollymax = getTextDimension().Height - FrameRect.getHeight(); - if (scrollymax != m_vscrollbar->getMax()) { - // manage a newline or a deleted line - m_vscrollbar->setMax(scrollymax); - m_vscrollbar->setPageSize(s32(getTextDimension().Height)); - calculateScrollPos(); - } else { - // manage a newline or a deleted line - VScrollPos = m_vscrollbar->getPos(); - } - } - - // check if a vertical scrollbar is needed ? - if (getTextDimension().Height > (u32) FrameRect.getHeight()) { - s32 scrollymax = getTextDimension().Height - FrameRect.getHeight(); - if (scrollymax != m_vscrollbar->getMax()) { - m_vscrollbar->setMax(scrollymax); - m_vscrollbar->setPageSize(s32(getTextDimension().Height)); - } - - if (!m_vscrollbar->isVisible() && MultiLine) { - AbsoluteRect.LowerRightCorner.X -= m_scrollbar_width; - - m_vscrollbar->setVisible(true); - } - } else { - if (m_vscrollbar->isVisible()) { - AbsoluteRect.LowerRightCorner.X += m_scrollbar_width; - - VScrollPos = 0; - m_vscrollbar->setPos(0); - m_vscrollbar->setMax(1); - m_vscrollbar->setPageSize(s32(getTextDimension().Height)); - m_vscrollbar->setVisible(false); - } - } -} - -void intlGUIEditBox::setWritable(bool can_write_text) -{ - m_writable = can_write_text; -} - -//! Writes attributes of the element. -void intlGUIEditBox::serializeAttributes(io::IAttributes* out, io::SAttributeReadWriteOptions* options=0) const -{ - // IGUIEditBox::serializeAttributes(out,options); - - out->addBool ("OverrideColorEnabled",OverrideColorEnabled ); - out->addColor ("OverrideColor", OverrideColor); - // out->addFont("OverrideFont",OverrideFont); - out->addInt ("MaxChars", Max); - out->addBool ("WordWrap", WordWrap); - out->addBool ("MultiLine", MultiLine); - out->addBool ("AutoScroll", AutoScroll); - out->addBool ("PasswordBox", PasswordBox); - core::stringw ch = L" "; - ch[0] = PasswordChar; - out->addString("PasswordChar", ch.c_str()); - out->addEnum ("HTextAlign", HAlign, GUIAlignmentNames); - out->addEnum ("VTextAlign", VAlign, GUIAlignmentNames); - out->addBool ("Writable", m_writable); - - IGUIEditBox::serializeAttributes(out,options); -} - - -//! Reads attributes of the element -void intlGUIEditBox::deserializeAttributes(io::IAttributes* in, io::SAttributeReadWriteOptions* options=0) -{ - IGUIEditBox::deserializeAttributes(in,options); - - setOverrideColor(in->getAttributeAsColor("OverrideColor")); - enableOverrideColor(in->getAttributeAsBool("OverrideColorEnabled")); - setMax(in->getAttributeAsInt("MaxChars")); - setWordWrap(in->getAttributeAsBool("WordWrap")); - setMultiLine(in->getAttributeAsBool("MultiLine")); - setAutoScroll(in->getAttributeAsBool("AutoScroll")); - core::stringw ch = in->getAttributeAsStringW("PasswordChar"); - - if (ch.empty()) - setPasswordBox(in->getAttributeAsBool("PasswordBox")); - else - setPasswordBox(in->getAttributeAsBool("PasswordBox"), ch[0]); - - setTextAlignment( (EGUI_ALIGNMENT) in->getAttributeAsEnumeration("HTextAlign", GUIAlignmentNames), - (EGUI_ALIGNMENT) in->getAttributeAsEnumeration("VTextAlign", GUIAlignmentNames)); - - setWritable(in->getAttributeAsBool("Writable")); - // setOverrideFont(in->getAttributeAsFont("OverrideFont")); -} - - } // end namespace gui } // end namespace irr diff --git a/src/gui/intlGUIEditBox.h b/src/gui/intlGUIEditBox.h index 9d643495e..007fe1c93 100644 --- a/src/gui/intlGUIEditBox.h +++ b/src/gui/intlGUIEditBox.h @@ -7,16 +7,15 @@ #include "IrrCompileConfig.h" //#ifdef _IRR_COMPILE_WITH_GUI_ -#include <IGUIEditBox.h> +#include "guiEditBox.h" #include "irrArray.h" #include "IOSOperator.h" -#include "guiScrollBar.h" namespace irr { namespace gui { - class intlGUIEditBox : public IGUIEditBox + class intlGUIEditBox : public GUIEditBox { public: @@ -26,113 +25,19 @@ namespace gui bool writable = true, bool has_vscrollbar = false); //! destructor - virtual ~intlGUIEditBox(); - - //! Sets another skin independent font. - virtual void setOverrideFont(IGUIFont* font=0); - - //! Gets the override font (if any) - /** \return The override font (may be 0) */ - virtual IGUIFont* getOverrideFont() const; - - //! Get the font which is used right now for drawing - /** Currently this is the override font when one is set and the - font of the active skin otherwise */ - virtual IGUIFont* getActiveFont() const; - - //! Sets another color for the text. - virtual void setOverrideColor(video::SColor color); - - //! Gets the override color - virtual video::SColor getOverrideColor() const; - - //! Sets if the text should use the overide color or the - //! color in the gui skin. - virtual void enableOverrideColor(bool enable); - - //! Checks if an override color is enabled - /** \return true if the override color is enabled, false otherwise */ - virtual bool isOverrideColorEnabled(void) const; + virtual ~intlGUIEditBox() {} //! Sets whether to draw the background virtual void setDrawBackground(bool draw); virtual bool isDrawBackgroundEnabled() const { return true; } - //! Turns the border on or off - virtual void setDrawBorder(bool border); - - virtual bool isDrawBorderEnabled() const { return Border; } - - //! Enables or disables word wrap for using the edit box as multiline text editor. - virtual void setWordWrap(bool enable); - - //! Checks if word wrap is enabled - //! \return true if word wrap is enabled, false otherwise - virtual bool isWordWrapEnabled() const; - - //! Enables or disables newlines. - /** \param enable: If set to true, the EGET_EDITBOX_ENTER event will not be fired, - instead a newline character will be inserted. */ - virtual void setMultiLine(bool enable); - - //! Checks if multi line editing is enabled - //! \return true if mult-line is enabled, false otherwise - virtual bool isMultiLineEnabled() const; - - //! Enables or disables automatic scrolling with cursor position - //! \param enable: If set to true, the text will move around with the cursor position - virtual void setAutoScroll(bool enable); - - //! Checks to see if automatic scrolling is enabled - //! \return true if automatic scrolling is enabled, false if not - virtual bool isAutoScrollEnabled() const; - - //! Gets the size area of the text in the edit box - //! \return Returns the size in pixels of the text - virtual core::dimension2du getTextDimension(); - - //! Sets text justification - virtual void setTextAlignment(EGUI_ALIGNMENT horizontal, EGUI_ALIGNMENT vertical); - - //! called if an event happened. - virtual bool OnEvent(const SEvent& event); - //! draws the element and its children virtual void draw(); - //! Sets the new caption of this element. - virtual void setText(const wchar_t* text); - - //! Sets the maximum amount of characters which may be entered in the box. - //! \param max: Maximum amount of characters. If 0, the character amount is - //! infinity. - virtual void setMax(u32 max); - - //! Returns maximum amount of characters, previously set by setMax(); - virtual u32 getMax() const; - - //! Sets whether the edit box is a password box. Setting this to true will - /** disable MultiLine, WordWrap and the ability to copy with ctrl+c or ctrl+x - \param passwordBox: true to enable password, false to disable - \param passwordChar: the character that is displayed instead of letters */ - virtual void setPasswordBox(bool passwordBox, wchar_t passwordChar = L'*'); - - //! Returns true if the edit box is currently a password box. - virtual bool isPasswordBox() const; - //! Updates the absolute position, splits text if required virtual void updateAbsolutePosition(); - //! set true if this EditBox is writable - virtual void setWritable(bool can_write_text); - - //! Writes attributes of the element. - virtual void serializeAttributes(io::IAttributes* out, io::SAttributeReadWriteOptions* options) const; - - //! Reads attributes of the element - virtual void deserializeAttributes(io::IAttributes* in, io::SAttributeReadWriteOptions* options); - virtual void setCursorChar(const wchar_t cursorChar) {} virtual wchar_t getCursorChar() const { return L'|'; } @@ -143,64 +48,17 @@ namespace gui protected: //! Breaks the single text line. - void breakText(); + virtual void breakText(); //! sets the area of the given line - void setTextRect(s32 line); - //! returns the line number that the cursor is on - s32 getLineFromPos(s32 pos); - //! adds a letter to the edit box - void inputChar(wchar_t c); + virtual void setTextRect(s32 line); + //! calculates the current scroll position void calculateScrollPos(); - //! send some gui event to parent - void sendGuiEvent(EGUI_EVENT_TYPE type); - //! set text markers - void setTextMarkers(s32 begin, s32 end); - bool processKey(const SEvent& event); - bool processMouse(const SEvent& event); s32 getCursorPos(s32 x, s32 y); //! Create a vertical scrollbar void createVScrollBar(); - - //! Update the vertical scrollbar (visibilty & scroll position) - void updateVScrollBar(); - - bool MouseMarking = false; - bool Border; - bool OverrideColorEnabled = false; - s32 MarkBegin = 0; - s32 MarkEnd = 0; - - video::SColor OverrideColor = video::SColor(101,255,255,255); - gui::IGUIFont *OverrideFont = nullptr; - gui::IGUIFont *LastBreakFont = nullptr; - IOSOperator *Operator = nullptr; - - u64 BlinkStartTime = 0; - s32 CursorPos = 0; - s32 HScrollPos = 0; - s32 VScrollPos = 0; // scroll position in characters - u32 Max = 0; - - bool WordWrap = false; - bool MultiLine = false; - bool AutoScroll = true; - bool PasswordBox = false; - wchar_t PasswordChar = L'*'; - EGUI_ALIGNMENT HAlign = EGUIA_UPPERLEFT; - EGUI_ALIGNMENT VAlign = EGUIA_CENTER; - - core::array<core::stringw> BrokenText; - core::array<s32> BrokenTextPositions; - - core::rect<s32> CurrentTextRect = core::rect<s32>(0,0,1,1); - core::rect<s32> FrameRect; // temporary values - u32 m_scrollbar_width; - GUIScrollBar *m_vscrollbar; - bool m_writable; - }; diff --git a/src/gui/modalMenu.cpp b/src/gui/modalMenu.cpp index a6fe7ebaf..0d3fb55f0 100644 --- a/src/gui/modalMenu.cpp +++ b/src/gui/modalMenu.cpp @@ -28,14 +28,16 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "touchscreengui.h" #endif -GUIModalMenu::GUIModalMenu(gui::IGUIEnvironment *env, gui::IGUIElement *parent, s32 id, - IMenuManager *menumgr) : +// clang-format off +GUIModalMenu::GUIModalMenu(gui::IGUIEnvironment* env, gui::IGUIElement* parent, + s32 id, IMenuManager *menumgr, bool remap_dbl_click) : IGUIElement(gui::EGUIET_ELEMENT, env, parent, id, core::rect<s32>(0, 0, 100, 100)), #ifdef __ANDROID__ m_jni_field_name(""), #endif - m_menumgr(menumgr) + m_menumgr(menumgr), + m_remap_dbl_click(remap_dbl_click) { m_gui_scale = g_settings->getFloat("gui_scaling"); #ifdef __ANDROID__ @@ -45,6 +47,12 @@ GUIModalMenu::GUIModalMenu(gui::IGUIEnvironment *env, gui::IGUIElement *parent, setVisible(true); Environment->setFocus(this); m_menumgr->createdMenu(this); + + m_doubleclickdetect[0].time = 0; + m_doubleclickdetect[1].time = 0; + + m_doubleclickdetect[0].pos = v2s32(0, 0); + m_doubleclickdetect[1].pos = v2s32(0, 0); } // clang-format on @@ -112,6 +120,127 @@ void GUIModalMenu::removeChildren() } } +// clang-format off +bool GUIModalMenu::DoubleClickDetection(const SEvent &event) +{ + /* The following code is for capturing double-clicks of the mouse button + * and translating the double-click into an EET_KEY_INPUT_EVENT event + * -- which closes the form -- under some circumstances. + * + * There have been many github issues reporting this as a bug even though it + * was an intended feature. For this reason, remapping the double-click as + * an ESC must be explicitly set when creating this class via the + * /p remap_dbl_click parameter of the constructor. + */ + + if (!m_remap_dbl_click) + return false; + + if (event.MouseInput.Event == EMIE_LMOUSE_PRESSED_DOWN) { + m_doubleclickdetect[0].pos = m_doubleclickdetect[1].pos; + m_doubleclickdetect[0].time = m_doubleclickdetect[1].time; + + m_doubleclickdetect[1].pos = m_pointer; + m_doubleclickdetect[1].time = porting::getTimeMs(); + } else if (event.MouseInput.Event == EMIE_LMOUSE_LEFT_UP) { + u64 delta = porting::getDeltaMs( + m_doubleclickdetect[0].time, porting::getTimeMs()); + if (delta > 400) + return false; + + double squaredistance = m_doubleclickdetect[0].pos. + getDistanceFromSQ(m_doubleclickdetect[1].pos); + + if (squaredistance > (30 * 30)) { + return false; + } + + SEvent translated{}; + // translate doubleclick to escape + translated.EventType = EET_KEY_INPUT_EVENT; + translated.KeyInput.Key = KEY_ESCAPE; + translated.KeyInput.Control = false; + translated.KeyInput.Shift = false; + translated.KeyInput.PressedDown = true; + translated.KeyInput.Char = 0; + OnEvent(translated); + + return true; + } + + return false; +} +// clang-format on + +static bool isChild(gui::IGUIElement *tocheck, gui::IGUIElement *parent) +{ + while (tocheck) { + if (tocheck == parent) { + return true; + } + tocheck = tocheck->getParent(); + } + return false; +} + +#ifdef __ANDROID__ + +bool GUIModalMenu::simulateMouseEvent( + gui::IGUIElement *target, ETOUCH_INPUT_EVENT touch_event) +{ + SEvent mouse_event{}; // value-initialized, not unitialized + mouse_event.EventType = EET_MOUSE_INPUT_EVENT; + mouse_event.MouseInput.X = m_pointer.X; + mouse_event.MouseInput.Y = m_pointer.Y; + switch (touch_event) { + case ETIE_PRESSED_DOWN: + mouse_event.MouseInput.Event = EMIE_LMOUSE_PRESSED_DOWN; + mouse_event.MouseInput.ButtonStates = EMBSM_LEFT; + break; + case ETIE_MOVED: + mouse_event.MouseInput.Event = EMIE_MOUSE_MOVED; + mouse_event.MouseInput.ButtonStates = EMBSM_LEFT; + break; + case ETIE_LEFT_UP: + mouse_event.MouseInput.Event = EMIE_LMOUSE_LEFT_UP; + mouse_event.MouseInput.ButtonStates = 0; + break; + default: + return false; + } + if (preprocessEvent(mouse_event)) + return true; + if (!target) + return false; + return target->OnEvent(mouse_event); +} + +void GUIModalMenu::enter(gui::IGUIElement *hovered) +{ + sanity_check(!m_hovered); + m_hovered.grab(hovered); + SEvent gui_event{}; + gui_event.EventType = EET_GUI_EVENT; + gui_event.GUIEvent.Caller = m_hovered.get(); + gui_event.GUIEvent.EventType = EGET_ELEMENT_HOVERED; + gui_event.GUIEvent.Element = gui_event.GUIEvent.Caller; + m_hovered->OnEvent(gui_event); +} + +void GUIModalMenu::leave() +{ + if (!m_hovered) + return; + SEvent gui_event{}; + gui_event.EventType = EET_GUI_EVENT; + gui_event.GUIEvent.Caller = m_hovered.get(); + gui_event.GUIEvent.EventType = EGET_ELEMENT_LEFT; + m_hovered->OnEvent(gui_event); + m_hovered.reset(); +} + +#endif + bool GUIModalMenu::preprocessEvent(const SEvent &event) { #ifdef __ANDROID__ @@ -159,92 +288,66 @@ bool GUIModalMenu::preprocessEvent(const SEvent &event) } if (event.EventType == EET_TOUCH_INPUT_EVENT) { - SEvent translated; - memset(&translated, 0, sizeof(SEvent)); - translated.EventType = EET_MOUSE_INPUT_EVENT; - gui::IGUIElement *root = Environment->getRootGUIElement(); - - if (!root) { - errorstream << "GUIModalMenu::preprocessEvent" - << " unable to get root element" << std::endl; - return false; - } - gui::IGUIElement *hovered = - root->getElementFromPoint(core::position2d<s32>( - event.TouchInput.X, event.TouchInput.Y)); + irr_ptr<GUIModalMenu> holder; + holder.grab(this); // keep this alive until return (it might be dropped downstream [?]) - translated.MouseInput.X = event.TouchInput.X; - translated.MouseInput.Y = event.TouchInput.Y; - translated.MouseInput.Control = false; - - if (event.TouchInput.touchedCount == 1) { - switch (event.TouchInput.Event) { - case ETIE_PRESSED_DOWN: + switch ((int)event.TouchInput.touchedCount) { + case 1: { + if (event.TouchInput.Event == ETIE_PRESSED_DOWN || event.TouchInput.Event == ETIE_MOVED) m_pointer = v2s32(event.TouchInput.X, event.TouchInput.Y); - translated.MouseInput.Event = EMIE_LMOUSE_PRESSED_DOWN; - translated.MouseInput.ButtonStates = EMBSM_LEFT; + if (event.TouchInput.Event == ETIE_PRESSED_DOWN) m_down_pos = m_pointer; - break; - case ETIE_MOVED: - m_pointer = v2s32(event.TouchInput.X, event.TouchInput.Y); - translated.MouseInput.Event = EMIE_MOUSE_MOVED; - translated.MouseInput.ButtonStates = EMBSM_LEFT; - break; - case ETIE_LEFT_UP: - translated.MouseInput.Event = EMIE_LMOUSE_LEFT_UP; - translated.MouseInput.ButtonStates = 0; - hovered = root->getElementFromPoint(m_down_pos); - // we don't have a valid pointer element use last - // known pointer pos - translated.MouseInput.X = m_pointer.X; - translated.MouseInput.Y = m_pointer.Y; - - // reset down pos - m_down_pos = v2s32(0, 0); - break; - default: - break; + gui::IGUIElement *hovered = Environment->getRootGUIElement()->getElementFromPoint(core::position2d<s32>(m_pointer)); + if (event.TouchInput.Event == ETIE_PRESSED_DOWN) + Environment->setFocus(hovered); + if (m_hovered != hovered) { + leave(); + enter(hovered); } - } else if ((event.TouchInput.touchedCount == 2) && - (event.TouchInput.Event == ETIE_PRESSED_DOWN)) { - hovered = root->getElementFromPoint(m_down_pos); - - translated.MouseInput.Event = EMIE_RMOUSE_PRESSED_DOWN; - translated.MouseInput.ButtonStates = EMBSM_LEFT | EMBSM_RIGHT; - translated.MouseInput.X = m_pointer.X; - translated.MouseInput.Y = m_pointer.Y; - if (hovered) - hovered->OnEvent(translated); - - translated.MouseInput.Event = EMIE_RMOUSE_LEFT_UP; - translated.MouseInput.ButtonStates = EMBSM_LEFT; - - if (hovered) - hovered->OnEvent(translated); - - return true; - } else { - // ignore unhandled 2 touch events (accidental moving for example) + gui::IGUIElement *focused = Environment->getFocus(); + bool ret = simulateMouseEvent(focused, event.TouchInput.Event); + if (!ret && m_hovered != focused) + ret = simulateMouseEvent(m_hovered.get(), event.TouchInput.Event); + if (event.TouchInput.Event == ETIE_LEFT_UP) + leave(); + return ret; + } + case 2: { + if (event.TouchInput.Event != ETIE_PRESSED_DOWN) + return true; // ignore + auto focused = Environment->getFocus(); + if (!focused) + return true; + SEvent rclick_event{}; + rclick_event.EventType = EET_MOUSE_INPUT_EVENT; + rclick_event.MouseInput.Event = EMIE_RMOUSE_PRESSED_DOWN; + rclick_event.MouseInput.ButtonStates = EMBSM_LEFT | EMBSM_RIGHT; + rclick_event.MouseInput.X = m_pointer.X; + rclick_event.MouseInput.Y = m_pointer.Y; + focused->OnEvent(rclick_event); + rclick_event.MouseInput.Event = EMIE_RMOUSE_LEFT_UP; + rclick_event.MouseInput.ButtonStates = EMBSM_LEFT; + focused->OnEvent(rclick_event); return true; } - - // check if translated event needs to be preprocessed again - if (preprocessEvent(translated)) + default: // ignored return true; - - if (hovered) { - grab(); - bool retval = hovered->OnEvent(translated); - - if (event.TouchInput.Event == ETIE_LEFT_UP) - // reset pointer - m_pointer = v2s32(0, 0); - - drop(); - return retval; } } #endif + + if (event.EventType == EET_MOUSE_INPUT_EVENT) { + s32 x = event.MouseInput.X; + s32 y = event.MouseInput.Y; + gui::IGUIElement *hovered = + Environment->getRootGUIElement()->getElementFromPoint( + core::position2d<s32>(x, y)); + if (!isChild(hovered, this)) { + if (DoubleClickDetection(event)) { + return true; + } + } + } return false; } diff --git a/src/gui/modalMenu.h b/src/gui/modalMenu.h index 5bd70bb84..ed0da3205 100644 --- a/src/gui/modalMenu.h +++ b/src/gui/modalMenu.h @@ -20,6 +20,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #pragma once #include "irrlichttypes_extrabloated.h" +#include "irr_ptr.h" #include "util/string.h" class GUIModalMenu; @@ -39,7 +40,7 @@ class GUIModalMenu : public gui::IGUIElement { public: GUIModalMenu(gui::IGUIEnvironment* env, gui::IGUIElement* parent, s32 id, - IMenuManager *menumgr); + IMenuManager *menumgr, bool remap_dbl_click = true); virtual ~GUIModalMenu(); void allowFocusRemoval(bool allow); @@ -50,8 +51,8 @@ public: virtual void regenerateGui(v2u32 screensize) = 0; virtual void drawMenu() = 0; - virtual bool preprocessEvent(const SEvent& event); - virtual bool OnEvent(const SEvent& event) { return false; }; + virtual bool preprocessEvent(const SEvent &event); + virtual bool OnEvent(const SEvent &event) { return false; }; virtual bool pausesGame() { return false; } // Used for pause menu #ifdef __ANDROID__ virtual bool getAndroidUIInput() { return false; } @@ -62,6 +63,13 @@ protected: virtual std::wstring getLabelByID(s32 id) = 0; virtual std::string getNameByID(s32 id) = 0; + /** + * check if event is part of a double click + * @param event event to evaluate + * @return true/false if a doubleclick was detected + */ + bool DoubleClickDetection(const SEvent &event); + v2s32 m_pointer; v2s32 m_old_pointer; // Mouse position after previous mouse event v2u32 m_screensize_old; @@ -73,9 +81,32 @@ protected: #ifdef HAVE_TOUCHSCREENGUI bool m_touchscreen_visible = true; #endif + private: + struct clickpos + { + v2s32 pos; + s64 time; + }; + clickpos m_doubleclickdetect[2]; + IMenuManager *m_menumgr; + /* If true, remap a double-click (or double-tap) action to ESC. This is so + * that, for example, Android users can double-tap to close a formspec. + * + * This value can (currently) only be set by the class constructor + * and the default value for the setting is true. + */ + bool m_remap_dbl_click; // This might be necessary to expose to the implementation if it // wants to launch other menus bool m_allow_focus_removal = false; + +#ifdef __ANDROID__ + irr_ptr<gui::IGUIElement> m_hovered; + + bool simulateMouseEvent(gui::IGUIElement *target, ETOUCH_INPUT_EVENT touch_event); + void enter(gui::IGUIElement *element); + void leave(); +#endif }; diff --git a/src/gui/touchscreengui.cpp b/src/gui/touchscreengui.cpp index 0d64aa618..e1a971462 100644 --- a/src/gui/touchscreengui.cpp +++ b/src/gui/touchscreengui.cpp @@ -881,8 +881,7 @@ void TouchScreenGUI::translateEvent(const SEvent &event) s32 dyj = event.TouchInput.Y - m_screensize.Y + button_size * 5.0f / 2.0f; bool inside_joystick = (dxj * dxj + dyj * dyj <= button_size * button_size * 1.5 * 1.5); - if (m_joystick_has_really_moved || - (!m_joystick_has_really_moved && inside_joystick) || + if (m_joystick_has_really_moved || inside_joystick || (!m_fixed_joystick && distance_sq > m_touchscreen_threshold * m_touchscreen_threshold)) { m_joystick_has_really_moved = true; diff --git a/src/gui/touchscreengui.h b/src/gui/touchscreengui.h index 761d33207..0349624fa 100644 --- a/src/gui/touchscreengui.h +++ b/src/gui/touchscreengui.h @@ -18,6 +18,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #pragma once +#include "irrlichttypes.h" #include <IEventReceiver.h> #include <IGUIButton.h> #include <IGUIEnvironment.h> |