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