1
0
forked from 0ad/0ad

Allow GUI elements to define custom mouse event mask shapes.

This allows GUI elements to use a texture as their "mouse event mask",
making it possible to have arbitrarily-shaped GUI elements with regards
to mouse interactions.

Used for the minimap idle worker button as a proof-of-concept.

Differential Revision: https://code.wildfiregames.com/D3814
This was SVN commit r25408.
This commit is contained in:
wraitii 2021-05-09 13:08:07 +00:00
parent e94faf7827
commit 3af1fc64d2
6 changed files with 254 additions and 1 deletions

View File

@ -262,6 +262,11 @@
<ref name="bool"/> <ref name="bool"/>
</attribute> </attribute>
</optional> </optional>
<optional>
<attribute name="mouse_event_mask">
<text/>
</attribute>
</optional>
<optional> <optional>
<attribute name="mask_char"> <attribute name="mask_char">
<data type="string"> <data type="string">

View File

@ -22,6 +22,7 @@
sprite="stretched:session/minimap-idle.png" sprite="stretched:session/minimap-idle.png"
sprite_over="stretched:session/minimap-idle-highlight.png" sprite_over="stretched:session/minimap-idle-highlight.png"
sprite_disabled="stretched:session/minimap-idle-disabled.png" sprite_disabled="stretched:session/minimap-idle-disabled.png"
mouse_event_mask="texture:session/minimap-idle.png"
/> />
<!-- Diplomacy Colors Button --> <!-- Diplomacy Colors Button -->

View File

@ -37,7 +37,8 @@ CButton::CButton(CGUI& pGUI)
m_TextColor(this, "textcolor"), m_TextColor(this, "textcolor"),
m_TextColorOver(this, "textcolor_over"), m_TextColorOver(this, "textcolor_over"),
m_TextColorPressed(this, "textcolor_pressed"), m_TextColorPressed(this, "textcolor_pressed"),
m_TextColorDisabled(this, "textcolor_disabled") m_TextColorDisabled(this, "textcolor_disabled"),
m_MouseEventMask(this)
{ {
AddText(); AddText();
} }
@ -91,6 +92,15 @@ void CButton::Draw()
DrawText(0, ChooseColor(), m_TextPos, bz + 0.1f); 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() const CGUIColor& CButton::ChooseColor()
{ {
if (!m_Enabled) if (!m_Enabled)

View File

@ -23,6 +23,7 @@
#include "gui/ObjectBases/IGUIObject.h" #include "gui/ObjectBases/IGUIObject.h"
#include "gui/ObjectBases/IGUITextOwner.h" #include "gui/ObjectBases/IGUITextOwner.h"
#include "gui/SettingTypes/CGUIString.h" #include "gui/SettingTypes/CGUIString.h"
#include "gui/SettingTypes/MouseEventMask.h"
#include "maths/Vector2D.h" #include "maths/Vector2D.h"
class CButton : public IGUIObject, public IGUITextOwner, public IGUIButtonBehavior class CButton : public IGUIObject, public IGUITextOwner, public IGUIButtonBehavior
@ -57,6 +58,11 @@ public:
*/ */
virtual void Draw(); virtual void Draw();
/**
* @see IGUIObject#IsMouseOver()
*/
virtual bool IsMouseOver() const;
protected: protected:
/** /**
* Sets up text, should be called every time changes has been * Sets up text, should be called every time changes has been
@ -88,6 +94,7 @@ protected:
CGUISimpleSetting<CGUIColor> m_TextColorOver; CGUISimpleSetting<CGUIColor> m_TextColorOver;
CGUISimpleSetting<CGUIColor> m_TextColorPressed; CGUISimpleSetting<CGUIColor> m_TextColorPressed;
CGUISimpleSetting<CGUIColor> m_TextColorDisabled; CGUISimpleSetting<CGUIColor> m_TextColorDisabled;
CGUIMouseEventMask m_MouseEventMask;
}; };
#endif // INCLUDED_CBUTTON #endif // INCLUDED_CBUTTON

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#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<CGUIMouseEventMaskTexture> Create(const std::string& spec)
{
std::shared_ptr<u8> 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<CGUIMouseEventMaskTexture>();
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<bool> 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<CGUIMouseEventMask::Impl> 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;
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#ifndef INCLUDED_GUI_MOUSE_EVENT_MASK
#define INCLUDED_GUI_MOUSE_EVENT_MASK
#include <string>
#include <memory>
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<Impl> m_Impl;
};
#endif // INCLUDED_GUI_MOUSE_EVENT_MASK