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:
parent
e94faf7827
commit
3af1fc64d2
@ -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">
|
||||||
|
@ -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 -->
|
||||||
|
@ -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)
|
||||||
|
@ -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
|
||||||
|
165
source/gui/SettingTypes/MouseEventMask.cpp
Normal file
165
source/gui/SettingTypes/MouseEventMask.cpp
Normal 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;
|
||||||
|
}
|
65
source/gui/SettingTypes/MouseEventMask.h
Normal file
65
source/gui/SettingTypes/MouseEventMask.h
Normal 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
|
Loading…
Reference in New Issue
Block a user