1 #include "components/ComponentList.h"
2 
3 #define TOTAL_HORIZONTAL_PADDING_PX 20
4 
ComponentList(Window * window)5 ComponentList::ComponentList(Window* window) : IList<ComponentListRow, void*>(window, LIST_SCROLL_STYLE_SLOW, LIST_NEVER_LOOP)
6 {
7 	mSelectorBarOffset = 0;
8 	mCameraOffset = 0;
9 	mFocused = false;
10 }
11 
addRow(const ComponentListRow & row,bool setCursorHere)12 void ComponentList::addRow(const ComponentListRow& row, bool setCursorHere)
13 {
14 	IList<ComponentListRow, void*>::Entry e;
15 	e.name = "";
16 	e.object = NULL;
17 	e.data = row;
18 
19 	this->add(e);
20 
21 	for(auto it = mEntries.back().data.elements.cbegin(); it != mEntries.back().data.elements.cend(); it++)
22 		addChild(it->component.get());
23 
24 	updateElementSize(mEntries.back().data);
25 	updateElementPosition(mEntries.back().data);
26 
27 	if(setCursorHere)
28 	{
29 		mCursor = (int)mEntries.size() - 1;
30 		onCursorChanged(CURSOR_STOPPED);
31 	}
32 }
33 
onSizeChanged()34 void ComponentList::onSizeChanged()
35 {
36 	for(auto it = mEntries.cbegin(); it != mEntries.cend(); it++)
37 	{
38 		updateElementSize(it->data);
39 		updateElementPosition(it->data);
40 	}
41 
42 	updateCameraOffset();
43 }
44 
onFocusLost()45 void ComponentList::onFocusLost()
46 {
47 	mFocused = false;
48 }
49 
onFocusGained()50 void ComponentList::onFocusGained()
51 {
52 	mFocused = true;
53 }
54 
input(InputConfig * config,Input input)55 bool ComponentList::input(InputConfig* config, Input input)
56 {
57 	if(size() == 0)
58 		return false;
59 
60 	// give it to the current row's input handler
61 	if(mEntries.at(mCursor).data.input_handler)
62 	{
63 		if(mEntries.at(mCursor).data.input_handler(config, input))
64 			return true;
65 	}else{
66 		// no input handler assigned, do the default, which is to give it to the rightmost element in the row
67 		auto& row = mEntries.at(mCursor).data;
68 		if(row.elements.size())
69 		{
70 			if(row.elements.back().component->input(config, input))
71 				return true;
72 		}
73 	}
74 
75 	// input handler didn't consume the input - try to scroll
76 	if(config->isMappedLike("up", input))
77 	{
78 		return listInput(input.value != 0 ? -1 : 0);
79 	}else if(config->isMappedLike("down", input))
80 	{
81 		return listInput(input.value != 0 ? 1 : 0);
82 
83 	}else if(config->isMappedLike("leftshoulder", input))
84 	{
85 		return listInput(input.value != 0 ? -6 : 0);
86 	}else if(config->isMappedLike("rightshoulder", input)){
87 		return listInput(input.value != 0 ? 6 : 0);
88 	}
89 
90 	return false;
91 }
92 
update(int deltaTime)93 void ComponentList::update(int deltaTime)
94 {
95 	listUpdate(deltaTime);
96 
97 	if(size())
98 	{
99 		// update our currently selected row
100 		for(auto it = mEntries.at(mCursor).data.elements.cbegin(); it != mEntries.at(mCursor).data.elements.cend(); it++)
101 			it->component->update(deltaTime);
102 	}
103 }
104 
onCursorChanged(const CursorState & state)105 void ComponentList::onCursorChanged(const CursorState& state)
106 {
107 	// update the selector bar position
108 	// in the future this might be animated
109 	mSelectorBarOffset = 0;
110 	for(int i = 0; i < mCursor; i++)
111 	{
112 		mSelectorBarOffset += getRowHeight(mEntries.at(i).data);
113 	}
114 
115 	updateCameraOffset();
116 
117 	// this is terribly inefficient but we don't know what we came from so...
118 	if(size())
119 	{
120 		for(auto it = mEntries.cbegin(); it != mEntries.cend(); it++)
121 			it->data.elements.back().component->onFocusLost();
122 
123 		mEntries.at(mCursor).data.elements.back().component->onFocusGained();
124 	}
125 
126 	if(mCursorChangedCallback)
127 		mCursorChangedCallback(state);
128 
129 	updateHelpPrompts();
130 }
131 
updateCameraOffset()132 void ComponentList::updateCameraOffset()
133 {
134 	// move the camera to scroll
135 	const float totalHeight = getTotalRowHeight();
136 	if(totalHeight > mSize.y())
137 	{
138 		float target = mSelectorBarOffset + getRowHeight(mEntries.at(mCursor).data)/2 - (mSize.y() / 2);
139 
140 		// clamp it
141 		mCameraOffset = 0;
142 		unsigned int i = 0;
143 		while(mCameraOffset < target && i < mEntries.size())
144 		{
145 			mCameraOffset += getRowHeight(mEntries.at(i).data);
146 			i++;
147 		}
148 
149 		if(mCameraOffset < 0)
150 			mCameraOffset = 0;
151 		else if(mCameraOffset + mSize.y() > totalHeight)
152 			mCameraOffset = totalHeight - mSize.y();
153 	}else{
154 		mCameraOffset = 0;
155 	}
156 }
157 
render(const Transform4x4f & parentTrans)158 void ComponentList::render(const Transform4x4f& parentTrans)
159 {
160 	if(!size())
161 		return;
162 
163 	Transform4x4f trans = parentTrans * getTransform();
164 
165 	// clip everything to be inside our bounds
166 	Vector3f dim(mSize.x(), mSize.y(), 0);
167 	dim = trans * dim - trans.translation();
168 	Renderer::pushClipRect(Vector2i((int)trans.translation().x(), (int)trans.translation().y()),
169 		Vector2i((int)Math::round(dim.x()), (int)Math::round(dim.y() + 1)));
170 
171 	// scroll the camera
172 	trans.translate(Vector3f(0, -Math::round(mCameraOffset), 0));
173 
174 	// draw our entries
175 	std::vector<GuiComponent*> drawAfterCursor;
176 	bool drawAll;
177 	for(unsigned int i = 0; i < mEntries.size(); i++)
178 	{
179 		auto& entry = mEntries.at(i);
180 		drawAll = !mFocused || i != (unsigned int)mCursor;
181 		for(auto it = entry.data.elements.cbegin(); it != entry.data.elements.cend(); it++)
182 		{
183 			if(drawAll || it->invert_when_selected)
184 			{
185 				it->component->render(trans);
186 			}else{
187 				drawAfterCursor.push_back(it->component.get());
188 			}
189 		}
190 	}
191 
192 	// custom rendering
193 	Renderer::setMatrix(trans);
194 
195 	// draw selector bar
196 	if(mFocused)
197 	{
198 		// inversion: src * (1 - dst) + dst * 0 = where src = 1
199 		// need a function that goes roughly 0x777777 -> 0xFFFFFF
200 		// and 0xFFFFFF -> 0x777777
201 		// (1 - dst) + 0x77
202 
203 		const float selectedRowHeight = getRowHeight(mEntries.at(mCursor).data);
204 		Renderer::drawRect(0.0f, mSelectorBarOffset, mSize.x(), selectedRowHeight, 0xFFFFFFFF, 0xFFFFFFFF, false, Renderer::Blend::ONE_MINUS_DST_COLOR, Renderer::Blend::ZERO);
205 		Renderer::drawRect(0.0f, mSelectorBarOffset, mSize.x(), selectedRowHeight, 0x777777FF, 0x777777FF, false, Renderer::Blend::ONE,                 Renderer::Blend::ONE);
206 
207 		// hack to draw 2px dark on left/right of the bar
208 		Renderer::drawRect(0.0f, mSelectorBarOffset, 2.0f, selectedRowHeight, 0x878787FF, 0x878787FF);
209 		Renderer::drawRect(mSize.x() - 2.0f, mSelectorBarOffset, 2.0f, selectedRowHeight, 0x878787FF, 0x878787FF);
210 
211 		for(auto it = drawAfterCursor.cbegin(); it != drawAfterCursor.cend(); it++)
212 			(*it)->render(trans);
213 
214 		// reset matrix if one of these components changed it
215 		if(drawAfterCursor.size())
216 			Renderer::setMatrix(trans);
217 	}
218 
219 	// draw separators
220 	float y = 0;
221 	for(unsigned int i = 0; i < mEntries.size(); i++)
222 	{
223 		Renderer::drawRect(0.0f, y, mSize.x(), 1.0f, 0xC6C7C6FF, 0xC6C7C6FF);
224 		y += getRowHeight(mEntries.at(i).data);
225 	}
226 	Renderer::drawRect(0.0f, y, mSize.x(), 1.0f, 0xC6C7C6FF, 0xC6C7C6FF);
227 
228 	Renderer::popClipRect();
229 }
230 
getRowHeight(const ComponentListRow & row) const231 float ComponentList::getRowHeight(const ComponentListRow& row) const
232 {
233 	// returns the highest component height found in the row
234 	float height = 0;
235 	for(unsigned int i = 0; i < row.elements.size(); i++)
236 	{
237 		if(row.elements.at(i).component->getSize().y() > height)
238 			height = row.elements.at(i).component->getSize().y();
239 	}
240 
241 	return height;
242 }
243 
getTotalRowHeight() const244 float ComponentList::getTotalRowHeight() const
245 {
246 	float height = 0;
247 	for(auto it = mEntries.cbegin(); it != mEntries.cend(); it++)
248 	{
249 		height += getRowHeight(it->data);
250 	}
251 
252 	return height;
253 }
254 
updateElementPosition(const ComponentListRow & row)255 void ComponentList::updateElementPosition(const ComponentListRow& row)
256 {
257 	float yOffset = 0;
258 	for(auto it = mEntries.cbegin(); it != mEntries.cend() && &it->data != &row; it++)
259 	{
260 		yOffset += getRowHeight(it->data);
261 	}
262 
263 	// assumes updateElementSize has already been called
264 	float rowHeight = getRowHeight(row);
265 
266 	float x = TOTAL_HORIZONTAL_PADDING_PX / 2;
267 	for(unsigned int i = 0; i < row.elements.size(); i++)
268 	{
269 		const auto comp = row.elements.at(i).component;
270 
271 		// center vertically
272 		comp->setPosition(x, (rowHeight - comp->getSize().y()) / 2 + yOffset);
273 		x += comp->getSize().x();
274 	}
275 }
276 
updateElementSize(const ComponentListRow & row)277 void ComponentList::updateElementSize(const ComponentListRow& row)
278 {
279 	float width = mSize.x() - TOTAL_HORIZONTAL_PADDING_PX;
280 	std::vector< std::shared_ptr<GuiComponent> > resizeVec;
281 
282 	for(auto it = row.elements.cbegin(); it != row.elements.cend(); it++)
283 	{
284 		if(it->resize_width)
285 			resizeVec.push_back(it->component);
286 		else
287 			width -= it->component->getSize().x();
288 	}
289 
290 	// redistribute the "unused" width equally among the components with resize_width set to true
291 	width = width / resizeVec.size();
292 	for(auto it = resizeVec.cbegin(); it != resizeVec.cend(); it++)
293 	{
294 		(*it)->setSize(width, (*it)->getSize().y());
295 	}
296 }
297 
textInput(const char * text)298 void ComponentList::textInput(const char* text)
299 {
300 	if(!size())
301 		return;
302 
303 	mEntries.at(mCursor).data.elements.back().component->textInput(text);
304 }
305 
getHelpPrompts()306 std::vector<HelpPrompt> ComponentList::getHelpPrompts()
307 {
308 	if(!size())
309 		return std::vector<HelpPrompt>();
310 
311 	std::vector<HelpPrompt> prompts = mEntries.at(mCursor).data.elements.back().component->getHelpPrompts();
312 
313 	if(size() > 1)
314 	{
315 		bool addMovePrompt = true;
316 		for(auto it = prompts.cbegin(); it != prompts.cend(); it++)
317 		{
318 			if(it->first == "up/down" || it->first == "up/down/left/right")
319 			{
320 				addMovePrompt = false;
321 				break;
322 			}
323 		}
324 
325 		if(addMovePrompt)
326 			prompts.push_back(HelpPrompt("up/down", "choose"));
327 	}
328 
329 	return prompts;
330 }
331 
moveCursor(int amt)332 bool ComponentList::moveCursor(int amt)
333 {
334 	bool ret = listInput(amt);
335 	listInput(0);
336 	return ret;
337 }
338