Line data Source code
1 : /* Copyright (C) 2022 Wildfire Games.
2 : * This file is part of 0 A.D.
3 : *
4 : * 0 A.D. is free software: you can redistribute it and/or modify
5 : * it under the terms of the GNU General Public License as published by
6 : * the Free Software Foundation, either version 2 of the License, or
7 : * (at your option) any later version.
8 : *
9 : * 0 A.D. is distributed in the hope that it will be useful,
10 : * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 : * GNU General Public License for more details.
13 : *
14 : * You should have received a copy of the GNU General Public License
15 : * along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
16 : */
17 :
18 : #include "precompiled.h"
19 :
20 : #include "MouseEventMask.h"
21 :
22 : #include "gui/CGUISetting.h"
23 : #include "lib/tex/tex.h"
24 : #include "maths/Rect.h"
25 : #include "maths/Vector2D.h"
26 : #include "ps/Filesystem.h"
27 : #include "ps/CacheLoader.h"
28 : #include "ps/CLogger.h"
29 : #include "ps/CStr.h"
30 : #include "scriptinterface/ScriptConversions.h"
31 :
32 : #include <string_view>
33 :
34 : class IGUIObject;
35 : class IGUISetting;
36 :
37 : namespace
38 : {
39 1 : const std::string MOUSE_EVENT_MASK = "mouse_event_mask";
40 : }
41 :
42 0 : class CGUIMouseEventMask::Impl
43 : {
44 : public:
45 0 : virtual ~Impl() = default;
46 : virtual bool IsMouseOver(const CVector2D& mousePos, const CRect& objectSize) const = 0;
47 : };
48 :
49 0 : CGUIMouseEventMask::CGUIMouseEventMask(IGUIObject* owner) : IGUISetting(MOUSE_EVENT_MASK, owner)
50 : {
51 0 : }
52 :
53 0 : CGUIMouseEventMask::~CGUIMouseEventMask()
54 : {
55 0 : }
56 :
57 0 : void CGUIMouseEventMask::ToJSVal(const ScriptRequest& rq, JS::MutableHandleValue Value)
58 : {
59 0 : Script::ToJSVal(rq, Value, m_Spec);
60 0 : }
61 :
62 0 : bool CGUIMouseEventMask::DoFromJSVal(const ScriptRequest& rq, JS::HandleValue value)
63 : {
64 0 : CStrW spec;
65 0 : if (!Script::FromJSVal(rq, value, spec))
66 0 : return false;
67 0 : return DoFromString(spec);
68 : }
69 :
70 0 : CStr CGUIMouseEventMask::GetName() const
71 : {
72 0 : return MOUSE_EVENT_MASK;
73 : }
74 :
75 0 : bool CGUIMouseEventMask::IsMouseOver(const CVector2D& mousePos, const CRect& objectSize) const
76 : {
77 0 : if (m_Impl)
78 0 : return m_Impl->IsMouseOver(mousePos, objectSize);
79 0 : return false;
80 : }
81 :
82 0 : class CGUIMouseEventMaskTexture final : public CGUIMouseEventMask::Impl
83 : {
84 : public:
85 : static constexpr std::string_view identifier = "texture:";
86 : static constexpr size_t specOffset = identifier.size();
87 :
88 0 : static std::unique_ptr<CGUIMouseEventMaskTexture> Create(const std::string_view spec)
89 : {
90 0 : std::shared_ptr<u8> shapeFile;
91 0 : CCacheLoader loader(g_VFS, L".dds");
92 0 : VfsPath sourcePath = VfsPath("art") / L"textures" / L"ui" /
93 0 : std::string{spec.substr(specOffset)};
94 0 : VfsPath archivePath = loader.ArchiveCachePath(sourcePath);
95 : Status status;
96 : size_t size;
97 0 : if (loader.CanUseArchiveCache(sourcePath, archivePath))
98 0 : status = g_VFS->LoadFile(archivePath, shapeFile, size);
99 : else
100 0 : status = g_VFS->LoadFile(sourcePath, shapeFile, size);
101 0 : if (status != INFO::OK)
102 : {
103 0 : LOGWARNING("Mouse event mask texture not found ('%s')", spec);
104 0 : return nullptr;
105 : }
106 0 : Tex tex;
107 0 : if (tex.decode(shapeFile, size) != INFO::OK)
108 : {
109 0 : LOGERROR("Could not decode mouse event mask texture '%s'", spec);
110 0 : return nullptr;
111 : }
112 0 : if (tex.transform(TEX_DXT | TEX_MIPMAPS) != INFO::OK)
113 : {
114 0 : LOGERROR("Could not transform texture '%s'", spec);
115 0 : return nullptr;
116 : }
117 :
118 : // TODO > would be nice to downscale, maybe.
119 0 : if (tex.m_Width == 0 || tex.m_Height == 0)
120 : {
121 0 : LOGERROR("Mouse event mask texture must have a non-null size ('%s')", spec);
122 0 : return nullptr;
123 : }
124 :
125 0 : auto mask = std::make_unique<CGUIMouseEventMaskTexture>();
126 0 : mask->m_Width = static_cast<u16>(tex.m_Width);
127 0 : mask->m_Height = static_cast<u16>(tex.m_Height);
128 0 : mask->m_Data.reserve(mask->m_Width * mask->m_Height);
129 0 : for (u8* ptr = tex.get_data(); ptr < tex.get_data() + tex.m_DataSize; ptr += tex.m_Bpp/8)
130 : {
131 0 : if (tex.m_Bpp == 32)
132 0 : mask->m_Data.push_back(*(ptr + 3) > 0);
133 : else
134 0 : mask->m_Data.push_back(*ptr > 0);
135 : }
136 :
137 0 : return mask;
138 : }
139 :
140 0 : virtual bool IsMouseOver(const CVector2D& mousePos, const CRect& objectSize) const override
141 : {
142 0 : if (m_Data.empty())
143 0 : return false;
144 :
145 0 : CVector2D delta = mousePos - objectSize.TopLeft();
146 0 : int x = floor(delta.X * m_Width / objectSize.GetWidth());
147 0 : int y = floor(delta.Y * m_Height / objectSize.GetHeight());
148 0 : if (x < 0 || y < 0 || x >= m_Width || y >= m_Height)
149 0 : return false;
150 :
151 0 : return m_Data[x + y * m_Width];
152 : }
153 :
154 : private:
155 : // This uses the bool specialization on purpose for the 'compression' effect.
156 : std::vector<bool> m_Data;
157 : u16 m_Width;
158 : u16 m_Height;
159 : };
160 :
161 0 : bool CGUIMouseEventMask::DoFromString(const CStrW& Value)
162 : {
163 0 : std::string spec = Value.ToUTF8();
164 0 : if (spec == m_Spec)
165 0 : return true;
166 :
167 : // Empty spec - reset the mask and return.
168 0 : if (spec.empty())
169 : {
170 0 : m_Impl.reset();
171 0 : return true;
172 : }
173 :
174 0 : if (spec.find(CGUIMouseEventMaskTexture::identifier) != std::string::npos)
175 : {
176 0 : std::unique_ptr<CGUIMouseEventMask::Impl> newImpl = CGUIMouseEventMaskTexture::Create(spec);
177 0 : if (newImpl)
178 : {
179 0 : m_Impl = std::move(newImpl);
180 0 : m_Spec = spec;
181 0 : return true;
182 : }
183 : else
184 0 : LOGERROR("Could not create shape for: %s", spec);
185 : }
186 : else
187 0 : LOGWARNING("Unknown clickable shape: %s", spec);
188 0 : return false;
189 3 : }
|