Index: ps/trunk/binaries/data/mods/mod/gui/gui.rng =================================================================== --- ps/trunk/binaries/data/mods/mod/gui/gui.rng +++ ps/trunk/binaries/data/mods/mod/gui/gui.rng @@ -263,6 +263,11 @@ + + + + + 1 Index: ps/trunk/binaries/data/mods/public/gui/session/minimap/MiniMap.xml =================================================================== --- ps/trunk/binaries/data/mods/public/gui/session/minimap/MiniMap.xml +++ ps/trunk/binaries/data/mods/public/gui/session/minimap/MiniMap.xml @@ -22,6 +22,7 @@ sprite="stretched:session/minimap-idle.png" sprite_over="stretched:session/minimap-idle-highlight.png" sprite_disabled="stretched:session/minimap-idle-disabled.png" + mouse_event_mask="texture:session/minimap-idle.png" /> Index: ps/trunk/source/gui/ObjectTypes/CButton.h =================================================================== --- ps/trunk/source/gui/ObjectTypes/CButton.h +++ ps/trunk/source/gui/ObjectTypes/CButton.h @@ -23,6 +23,7 @@ #include "gui/ObjectBases/IGUIObject.h" #include "gui/ObjectBases/IGUITextOwner.h" #include "gui/SettingTypes/CGUIString.h" +#include "gui/SettingTypes/MouseEventMask.h" #include "maths/Vector2D.h" class CButton : public IGUIObject, public IGUITextOwner, public IGUIButtonBehavior @@ -57,6 +58,11 @@ */ virtual void Draw(); + /** + * @see IGUIObject#IsMouseOver() + */ + virtual bool IsMouseOver() const; + protected: /** * Sets up text, should be called every time changes has been @@ -88,6 +94,7 @@ CGUISimpleSetting m_TextColorOver; CGUISimpleSetting m_TextColorPressed; CGUISimpleSetting m_TextColorDisabled; + CGUIMouseEventMask m_MouseEventMask; }; #endif // INCLUDED_CBUTTON Index: ps/trunk/source/gui/ObjectTypes/CButton.cpp =================================================================== --- ps/trunk/source/gui/ObjectTypes/CButton.cpp +++ ps/trunk/source/gui/ObjectTypes/CButton.cpp @@ -37,7 +37,8 @@ m_TextColor(this, "textcolor"), m_TextColorOver(this, "textcolor_over"), m_TextColorPressed(this, "textcolor_pressed"), - m_TextColorDisabled(this, "textcolor_disabled") + m_TextColorDisabled(this, "textcolor_disabled"), + m_MouseEventMask(this) { AddText(); } @@ -91,6 +92,15 @@ DrawText(0, ChooseColor(), m_TextPos, bz + 0.1f); } +bool CButton::IsMouseOver() const +{ + if (!IGUIObject::IsMouseOver()) + return false; + if (!m_MouseEventMask) + return true; + return m_MouseEventMask.IsMouseOver(m_pGUI.GetMousePos(), m_CachedActualSize); +} + const CGUIColor& CButton::ChooseColor() { if (!m_Enabled) Index: ps/trunk/source/gui/SettingTypes/MouseEventMask.h =================================================================== --- ps/trunk/source/gui/SettingTypes/MouseEventMask.h +++ ps/trunk/source/gui/SettingTypes/MouseEventMask.h @@ -0,0 +1,65 @@ +/* Copyright (C) 2021 Wildfire Games. + * This file is part of 0 A.D. + * + * 0 A.D. is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * 0 A.D. 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with 0 A.D. If not, see . + */ + +#ifndef INCLUDED_GUI_MOUSE_EVENT_MASK +#define INCLUDED_GUI_MOUSE_EVENT_MASK + +#include +#include + +class CRect; +class CVector2D; +class IGUIObject; + +/** + * A custom shape that changes the object's "over-ability", and thus where one can click on it. + * Supported: + * - "texture:[path]" loads a texture and uses either the alpha or the red channel. Any non-0 is clickable. + * The texture is always 'stretched' in sprite terminology. + * + * TODO: + * - the minimap circular shape should be moved here. + */ +class CGUIMouseEventMask : public IGUISetting +{ +public: + CGUIMouseEventMask(IGUIObject* owner); + ~CGUIMouseEventMask(); + + /** + * @return true if the mask is initialised <=> its spec is not "" + */ + explicit operator bool() const { return !!m_Impl; } + + /** + * @return true if the mouse pointer is over the mask. False if the mask is not initialised. + */ + bool IsMouseOver(const CVector2D& mousePos, const CRect& objectSize) const; + + void ToJSVal(const ScriptRequest& rq, JS::MutableHandleValue value) override; + + class Impl; +protected: + bool DoFromString(const CStrW& value) override; + bool DoFromJSVal(const ScriptRequest& rq, JS::HandleValue value) override; + CStr GetName() const override; + + std::string m_Spec; + std::unique_ptr m_Impl; +}; + +#endif // INCLUDED_GUI_MOUSE_EVENT_MASK Index: ps/trunk/source/gui/SettingTypes/MouseEventMask.cpp =================================================================== --- ps/trunk/source/gui/SettingTypes/MouseEventMask.cpp +++ ps/trunk/source/gui/SettingTypes/MouseEventMask.cpp @@ -0,0 +1,165 @@ +/* Copyright (C) 2021 Wildfire Games. + * This file is part of 0 A.D. + * + * 0 A.D. is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * 0 A.D. 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with 0 A.D. If not, see . + */ + +#include "precompiled.h" + +#include "MouseEventMask.h" + +#include "gui/CGUISetting.h" +#include "lib/tex/tex.h" +#include "ps/Filesystem.h" +#include "ps/CLogger.h" +#include "ps/CStr.h" +#include "scriptinterface/ScriptInterface.h" + +namespace +{ + const std::string MOUSE_EVENT_MASK = "mouse_event_mask"; +} + +class CGUIMouseEventMask::Impl +{ +public: + virtual ~Impl() = default; + virtual bool IsMouseOver(const CVector2D& mousePos, const CRect& objectSize) const = 0; +}; + +CGUIMouseEventMask::CGUIMouseEventMask(IGUIObject* owner) : IGUISetting(MOUSE_EVENT_MASK, owner) +{ +} + +CGUIMouseEventMask::~CGUIMouseEventMask() +{ +} + +void CGUIMouseEventMask::ToJSVal(const ScriptRequest& rq, JS::MutableHandleValue Value) +{ + ScriptInterface::ToJSVal(rq, Value, m_Spec); +} + +bool CGUIMouseEventMask::DoFromJSVal(const ScriptRequest& rq, JS::HandleValue value) +{ + CStrW spec; + if (!ScriptInterface::FromJSVal(rq, value, spec)) + return false; + return DoFromString(spec); +} + +CStr CGUIMouseEventMask::GetName() const +{ + return MOUSE_EVENT_MASK; +} + +bool CGUIMouseEventMask::IsMouseOver(const CVector2D& mousePos, const CRect& objectSize) const +{ + if (m_Impl) + return m_Impl->IsMouseOver(mousePos, objectSize); + return false; +} + +class CGUIMouseEventMaskTexture final : public CGUIMouseEventMask::Impl +{ +public: + static constexpr const char* identifier = "texture:"; + static constexpr size_t specOffset = sizeof(identifier); + + static std::unique_ptr Create(const std::string& spec) + { + std::shared_ptr shapeFile; + size_t size; + if (g_VFS->LoadFile(VfsPath("art") / L"textures" / L"ui" / spec.substr(specOffset), shapeFile, size) != INFO::OK) + { + LOGWARNING("Mouse event mask texture not found ('%s')", spec); + return nullptr; + } + Tex tex; + if (tex.decode(shapeFile, size) != INFO::OK) + { + LOGERROR("Could not decode mouse event mask texture '%s'", spec); + return nullptr; + } + // TODO > would be nice to downscale, maybe. + if (tex.m_Width == 0 || tex.m_Height == 0) + { + LOGERROR("Mouse event mask texture must have a non-null size ('%s')", spec); + return nullptr; + } + + auto mask = std::make_unique(); + mask->m_Width = tex.m_Width; + mask->m_Height = tex.m_Height; + mask->m_Data.reserve(mask->m_Width * mask->m_Height); + for (u8* ptr = tex.get_data(); ptr < tex.get_data() + tex.m_DataSize; ptr += tex.m_Bpp/8) + { + if (tex.m_Bpp == 32) + mask->m_Data.push_back(*(ptr + 3) > 0); + else + mask->m_Data.push_back(*ptr > 0); + } + return mask; + } + + virtual bool IsMouseOver(const CVector2D& mousePos, const CRect& objectSize) const override + { + if (m_Data.empty()) + return false; + + CVector2D delta = mousePos - objectSize.TopLeft(); + int x = floor(delta.X * m_Width / objectSize.GetWidth()); + int y = floor(delta.Y * m_Height / objectSize.GetHeight()); + if (x < 0 || y < 0 || x >= m_Width || y >= m_Height) + return false; + + return m_Data[x + y * m_Width] > 0; + } + +private: + // This uses the bool specialization on purpose for the 'compression' effect. + std::vector m_Data; + u16 m_Width; + u16 m_Height; +}; + +bool CGUIMouseEventMask::DoFromString(const CStrW& Value) +{ + std::string spec = Value.ToUTF8(); + if (spec == m_Spec) + return true; + + // Empty spec - reset the mask and return. + if (spec.empty()) + { + m_Impl.reset(); + return true; + } + + if (spec.find(CGUIMouseEventMaskTexture::identifier) != std::string::npos) + { + std::unique_ptr newImpl = CGUIMouseEventMaskTexture::Create(spec); + if (newImpl) + { + m_Impl = std::move(newImpl); + m_Spec = spec; + return true; + } + else + LOGERROR("Could not create shape for: %s", spec); + } + else + LOGWARNING("Unknown clickable shape: %s", spec); + return false; +}