]> git.lizzy.rs Git - minetest.git/blob - src/guiInventoryMenu.cpp
Tool progress bar tweaking
[minetest.git] / src / guiInventoryMenu.cpp
1 /*
2 Minetest-c55
3 Copyright (C) 2010 celeron55, Perttu Ahola <celeron55@gmail.com>
4
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 2 of the License, or
8 (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License along
16 with this program; if not, write to the Free Software Foundation, Inc.,
17 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 */
19
20
21 #include "guiInventoryMenu.h"
22 #include "constants.h"
23 #include "gamedef.h"
24 #include "keycode.h"
25 #include "strfnd.h"
26 #include <IGUICheckBox.h>
27 #include <IGUIEditBox.h>
28 #include <IGUIButton.h>
29 #include <IGUIStaticText.h>
30 #include <IGUIFont.h>
31 #include "log.h"
32
33 void drawItemStack(video::IVideoDriver *driver,
34                 gui::IGUIFont *font,
35                 const ItemStack &item,
36                 const core::rect<s32> &rect,
37                 const core::rect<s32> *clip,
38                 IGameDef *gamedef)
39 {
40         if(item.empty())
41                 return;
42         
43         const ItemDefinition &def = item.getDefinition(gamedef->idef());
44         video::ITexture *texture = def.inventory_texture;
45
46         // Draw the inventory texture
47         if(texture != NULL)
48         {
49                 const video::SColor color(255,255,255,255);
50                 const video::SColor colors[] = {color,color,color,color};
51                 driver->draw2DImage(texture, rect,
52                         core::rect<s32>(core::position2d<s32>(0,0),
53                         core::dimension2di(texture->getOriginalSize())),
54                         clip, colors, true);
55         }
56
57         if(def.type == ITEM_TOOL && item.wear != 0)
58         {
59                 // Draw a progressbar
60                 float barheight = rect.getHeight()/16;
61                 float barpad_x = rect.getWidth()/16;
62                 float barpad_y = rect.getHeight()/16;
63                 core::rect<s32> progressrect(
64                         rect.UpperLeftCorner.X + barpad_x,
65                         rect.LowerRightCorner.Y - barpad_y - barheight,
66                         rect.LowerRightCorner.X - barpad_x,
67                         rect.LowerRightCorner.Y - barpad_y);
68
69                 // Shrink progressrect by amount of tool damage
70                 float wear = item.wear / 65535.0;
71                 int progressmid =
72                         wear * progressrect.UpperLeftCorner.X +
73                         (1-wear) * progressrect.LowerRightCorner.X;
74
75                 // Compute progressbar color
76                 //   wear = 0.0: green
77                 //   wear = 0.5: yellow
78                 //   wear = 1.0: red
79                 video::SColor color(255,255,255,255);
80                 int wear_i = MYMIN(floor(wear * 600), 511);
81                 wear_i = MYMIN(wear_i + 10, 511);
82                 if(wear_i <= 255)
83                         color.set(255, wear_i, 255, 0);
84                 else
85                         color.set(255, 255, 511-wear_i, 0);
86
87                 core::rect<s32> progressrect2 = progressrect;
88                 progressrect2.LowerRightCorner.X = progressmid;
89                 driver->draw2DRectangle(color, progressrect2, clip);
90
91                 color = video::SColor(255,0,0,0);
92                 progressrect2 = progressrect;
93                 progressrect2.UpperLeftCorner.X = progressmid;
94                 driver->draw2DRectangle(color, progressrect2, clip);
95         }
96
97         if(font != NULL && item.count >= 2)
98         {
99                 // Get the item count as a string
100                 std::string text = itos(item.count);
101                 v2u32 dim = font->getDimension(narrow_to_wide(text).c_str());
102                 v2s32 sdim(dim.X,dim.Y);
103
104                 core::rect<s32> rect2(
105                         /*rect.UpperLeftCorner,
106                         core::dimension2d<u32>(rect.getWidth(), 15)*/
107                         rect.LowerRightCorner - sdim,
108                         sdim
109                 );
110
111                 video::SColor bgcolor(128,0,0,0);
112                 driver->draw2DRectangle(bgcolor, rect2, clip);
113
114                 video::SColor color(255,255,255,255);
115                 font->draw(text.c_str(), rect2, color, false, false, clip);
116         }
117 }
118
119 /*
120         GUIInventoryMenu
121 */
122
123 GUIInventoryMenu::GUIInventoryMenu(gui::IGUIEnvironment* env,
124                 gui::IGUIElement* parent, s32 id,
125                 IMenuManager *menumgr,
126                 v2s16 menu_size,
127                 InventoryManager *invmgr,
128                 IGameDef *gamedef
129                 ):
130         GUIModalMenu(env, parent, id, menumgr),
131         m_menu_size(menu_size),
132         m_invmgr(invmgr),
133         m_gamedef(gamedef)
134 {
135         m_selected_item = NULL;
136 }
137
138 GUIInventoryMenu::~GUIInventoryMenu()
139 {
140         removeChildren();
141
142         if(m_selected_item)
143                 delete m_selected_item;
144 }
145
146 void GUIInventoryMenu::removeChildren()
147 {
148         const core::list<gui::IGUIElement*> &children = getChildren();
149         core::list<gui::IGUIElement*> children_copy;
150         for(core::list<gui::IGUIElement*>::ConstIterator
151                         i = children.begin(); i != children.end(); i++)
152         {
153                 children_copy.push_back(*i);
154         }
155         for(core::list<gui::IGUIElement*>::Iterator
156                         i = children_copy.begin();
157                         i != children_copy.end(); i++)
158         {
159                 (*i)->remove();
160         }
161         /*{
162                 gui::IGUIElement *e = getElementFromId(256);
163                 if(e != NULL)
164                         e->remove();
165         }*/
166 }
167
168 void GUIInventoryMenu::regenerateGui(v2u32 screensize)
169 {
170         // Remove children
171         removeChildren();
172         
173         /*padding = v2s32(24,24);
174         spacing = v2s32(60,56);
175         imgsize = v2s32(48,48);*/
176
177         padding = v2s32(screensize.Y/40, screensize.Y/40);
178         spacing = v2s32(screensize.Y/12, screensize.Y/13);
179         imgsize = v2s32(screensize.Y/15, screensize.Y/15);
180
181         s32 helptext_h = 15;
182
183         v2s32 size(
184                 padding.X*2+spacing.X*(m_menu_size.X-1)+imgsize.X,
185                 padding.Y*2+spacing.Y*(m_menu_size.Y-1)+imgsize.Y + helptext_h
186         );
187
188         core::rect<s32> rect(
189                         screensize.X/2 - size.X/2,
190                         screensize.Y/2 - size.Y/2,
191                         screensize.X/2 + size.X/2,
192                         screensize.Y/2 + size.Y/2
193         );
194         
195         DesiredRect = rect;
196         recalculateAbsolutePosition(false);
197
198         v2s32 basepos = getBasePos();
199         
200         m_draw_spec.clear();
201         for(u16 i=0; i<m_init_draw_spec.size(); i++)
202         {
203                 DrawSpec &s = m_init_draw_spec[i];
204                 if(s.type == "list")
205                 {
206                         m_draw_spec.push_back(ListDrawSpec(s.name, s.subname,
207                                         basepos + v2s32(spacing.X*s.pos.X, spacing.Y*s.pos.Y),
208                                         s.geom));
209                 }
210         }
211
212         /*
213         m_draw_spec.clear();
214         m_draw_spec.push_back(ListDrawSpec("main",
215                         basepos + v2s32(spacing.X*0, spacing.Y*3), v2s32(8, 4)));
216         m_draw_spec.push_back(ListDrawSpec("craft",
217                         basepos + v2s32(spacing.X*3, spacing.Y*0), v2s32(3, 3)));
218         m_draw_spec.push_back(ListDrawSpec("craftresult",
219                         basepos + v2s32(spacing.X*7, spacing.Y*1), v2s32(1, 1)));
220         */
221         
222         // Add children
223         {
224                 core::rect<s32> rect(0, 0, size.X-padding.X*2, helptext_h);
225                 rect = rect + v2s32(size.X/2 - rect.getWidth()/2,
226                                 size.Y-rect.getHeight()-15);
227                 const wchar_t *text =
228                 L"Left click: Move all items, Right click: Move single item";
229                 Environment->addStaticText(text, rect, false, true, this, 256);
230         }
231 }
232
233 GUIInventoryMenu::ItemSpec GUIInventoryMenu::getItemAtPos(v2s32 p) const
234 {
235         core::rect<s32> imgrect(0,0,imgsize.X,imgsize.Y);
236         
237         for(u32 i=0; i<m_draw_spec.size(); i++)
238         {
239                 const ListDrawSpec &s = m_draw_spec[i];
240
241                 for(s32 i=0; i<s.geom.X*s.geom.Y; i++)
242                 {
243                         s32 x = (i%s.geom.X) * spacing.X;
244                         s32 y = (i/s.geom.X) * spacing.Y;
245                         v2s32 p0(x,y);
246                         core::rect<s32> rect = imgrect + s.pos + p0;
247                         if(rect.isPointInside(p))
248                         {
249                                 return ItemSpec(s.inventoryloc, s.listname, i);
250                         }
251                 }
252         }
253
254         return ItemSpec(InventoryLocation(), "", -1);
255 }
256
257 void GUIInventoryMenu::drawList(const ListDrawSpec &s)
258 {
259         video::IVideoDriver* driver = Environment->getVideoDriver();
260
261         // Get font
262         gui::IGUIFont *font = NULL;
263         gui::IGUISkin* skin = Environment->getSkin();
264         if (skin)
265                 font = skin->getFont();
266         
267         Inventory *inv = m_invmgr->getInventory(s.inventoryloc);
268         assert(inv);
269         InventoryList *ilist = inv->getList(s.listname);
270         
271         core::rect<s32> imgrect(0,0,imgsize.X,imgsize.Y);
272         
273         for(s32 i=0; i<s.geom.X*s.geom.Y; i++)
274         {
275                 s32 x = (i%s.geom.X) * spacing.X;
276                 s32 y = (i/s.geom.X) * spacing.Y;
277                 v2s32 p(x,y);
278                 core::rect<s32> rect = imgrect + s.pos + p;
279                 ItemStack item;
280                 if(ilist)
281                         item = ilist->getItem(i);
282
283                 if(m_selected_item != NULL && m_selected_item->listname == s.listname
284                                 && m_selected_item->i == i)
285                 {
286                         /*s32 border = imgsize.X/12;
287                         driver->draw2DRectangle(video::SColor(255,192,192,192),
288                                         core::rect<s32>(rect.UpperLeftCorner - v2s32(1,1)*border,
289                                                         rect.LowerRightCorner + v2s32(1,1)*border),
290                                         NULL);
291                         driver->draw2DRectangle(video::SColor(255,0,0,0),
292                                         core::rect<s32>(rect.UpperLeftCorner - v2s32(1,1)*((border+1)/2),
293                                                         rect.LowerRightCorner + v2s32(1,1)*((border+1)/2)),
294                                         NULL);*/
295                         s32 border = 2;
296                         driver->draw2DRectangle(video::SColor(255,255,0,0),
297                                         core::rect<s32>(rect.UpperLeftCorner - v2s32(1,1)*border,
298                                                         rect.LowerRightCorner + v2s32(1,1)*border),
299                                         &AbsoluteClippingRect);
300                 }
301                 
302                 if(rect.isPointInside(m_pointer) && m_selected_item)
303                 {
304                     video::SColor bgcolor(255,192,192,192);
305                     driver->draw2DRectangle(bgcolor, rect, &AbsoluteClippingRect);
306                 }
307                 else
308                 {
309                     video::SColor bgcolor(255,128,128,128);
310                     driver->draw2DRectangle(bgcolor, rect, &AbsoluteClippingRect);
311                 }
312
313                 if(!item.empty())
314                 {
315                         drawItemStack(driver, font, item,
316                                         rect, &AbsoluteClippingRect, m_gamedef);
317                 }
318
319         }
320 }
321
322 void GUIInventoryMenu::drawMenu()
323 {
324         gui::IGUISkin* skin = Environment->getSkin();
325         if (!skin)
326                 return;
327         video::IVideoDriver* driver = Environment->getVideoDriver();
328         
329         video::SColor bgcolor(140,0,0,0);
330         driver->draw2DRectangle(bgcolor, AbsoluteRect, &AbsoluteClippingRect);
331
332         /*
333                 Draw items
334         */
335         
336         for(u32 i=0; i<m_draw_spec.size(); i++)
337         {
338                 drawList(m_draw_spec[i]);
339         }
340
341         /*
342                 Call base class
343         */
344         gui::IGUIElement::draw();
345 }
346
347 bool GUIInventoryMenu::OnEvent(const SEvent& event)
348 {
349         if(event.EventType==EET_KEY_INPUT_EVENT)
350         {
351                 KeyPress kp(event.KeyInput);
352                 if (event.KeyInput.PressedDown && (kp == EscapeKey ||
353                         kp == getKeySetting("keymap_inventory")))
354                 {
355                         quitMenu();
356                         return true;
357                 }
358         }
359         if(event.EventType==EET_MOUSE_INPUT_EVENT)
360         {
361                 char amount = -1;
362
363                 v2s32 p(event.MouseInput.X, event.MouseInput.Y);
364                 ItemSpec s = getItemAtPos(p);
365
366                 if(event.MouseInput.Event==EMIE_MOUSE_MOVED)
367                     m_pointer = v2s32(event.MouseInput.X, event.MouseInput.Y);
368                 else if(event.MouseInput.Event == EMIE_LMOUSE_PRESSED_DOWN)
369                         amount = 0;
370                 else if(event.MouseInput.Event == EMIE_RMOUSE_PRESSED_DOWN)
371                         amount = 1;
372                 else if(event.MouseInput.Event == EMIE_MMOUSE_PRESSED_DOWN)
373                         amount = 10;
374                 else if(event.MouseInput.Event == EMIE_LMOUSE_LEFT_UP &&
375                                 m_selected_item &&
376                                 (m_selected_item->listname != s.listname
377                                         || m_selected_item->i != s.i))
378                         amount = 0;
379                         
380                 
381                 if(amount >= 0)
382                 {
383                         //infostream<<"Mouse action at p=("<<p.X<<","<<p.Y<<")"<<std::endl;
384                         if(s.isValid())
385                         {
386                                 infostream<<"Mouse action on "<<s.inventoryloc.dump()
387                                                 <<"/"<<s.listname<<" "<<s.i<<std::endl;
388                                 if(m_selected_item)
389                                 {
390                                         Inventory *inv_from = m_invmgr->getInventory(
391                                                         m_selected_item->inventoryloc);
392                                         Inventory *inv_to = m_invmgr->getInventory(
393                                                         s.inventoryloc);
394                                         assert(inv_from);
395                                         assert(inv_to);
396                                         InventoryList *list_from =
397                                                         inv_from->getList(m_selected_item->listname);
398                                         InventoryList *list_to =
399                                                         inv_to->getList(s.listname);
400                                         if(list_from == NULL)
401                                                 infostream<<"from list doesn't exist"<<std::endl;
402                                         if(list_to == NULL)
403                                                 infostream<<"to list doesn't exist"<<std::endl;
404                                         // Indicates whether source slot completely empties
405                                         bool source_empties = false;
406                                         if(list_from && list_to
407                                                         && !list_from->getItem(m_selected_item->i).empty())
408                                         {
409                                                 infostream<<"Handing IACTION_MOVE to manager"<<std::endl;
410                                                 IMoveAction *a = new IMoveAction();
411                                                 a->count = amount;
412                                                 a->from_inv = m_selected_item->inventoryloc;
413                                                 a->from_list = m_selected_item->listname;
414                                                 a->from_i = m_selected_item->i;
415                                                 a->to_inv = s.inventoryloc;
416                                                 a->to_list = s.listname;
417                                                 a->to_i = s.i;
418                                                 //ispec.actions->push_back(a);
419                                                 m_invmgr->inventoryAction(a);
420                                                 
421                                                 if(list_from->getItem(m_selected_item->i).count<=amount)
422                                                         source_empties = true;
423                                         }
424                                         // Remove selection if target was left-clicked or source
425                                         // slot was emptied
426                                         if(amount == 0 || source_empties)
427                                         {
428                                                 delete m_selected_item;
429                                                 m_selected_item = NULL;
430                                         }
431                                 }
432                                 else
433                                 {
434                                         /*
435                                                 Select if nonempty
436                                         */
437                                         Inventory *inv = m_invmgr->getInventory(
438                                                         s.inventoryloc);
439                                         assert(inv);
440                                         InventoryList *list = inv->getList(s.listname);
441                                         if(!list->getItem(s.i).empty())
442                                         {
443                                                 m_selected_item = new ItemSpec(s);
444                                         }
445                                 }
446                         }
447                         else
448                         {
449                                 if(m_selected_item)
450                                 {
451                                         delete m_selected_item;
452                                         m_selected_item = NULL;
453                                 }
454                         }
455                 }
456         }
457         if(event.EventType==EET_GUI_EVENT)
458         {
459                 if(event.GUIEvent.EventType==gui::EGET_ELEMENT_FOCUS_LOST
460                                 && isVisible())
461                 {
462                         if(!canTakeFocus(event.GUIEvent.Element))
463                         {
464                                 infostream<<"GUIInventoryMenu: Not allowing focus change."
465                                                 <<std::endl;
466                                 // Returning true disables focus change
467                                 return true;
468                         }
469                 }
470                 if(event.GUIEvent.EventType==gui::EGET_BUTTON_CLICKED)
471                 {
472                         /*switch(event.GUIEvent.Caller->getID())
473                         {
474                         case 256: // continue
475                                 setVisible(false);
476                                 break;
477                         case 257: // exit
478                                 dev->closeDevice();
479                                 break;
480                         }*/
481                 }
482         }
483
484         return Parent ? Parent->OnEvent(event) : false;
485 }
486
487 /*
488         Here is an example traditional set-up sequence for a DrawSpec list:
489
490         std::string furnace_inv_id = "nodemetadata:0,1,2";
491         core::array<GUIInventoryMenu::DrawSpec> draw_spec;
492         draw_spec.push_back(GUIInventoryMenu::DrawSpec(
493                         "list", furnace_inv_id, "fuel",
494                         v2s32(2, 3), v2s32(1, 1)));
495         draw_spec.push_back(GUIInventoryMenu::DrawSpec(
496                         "list", furnace_inv_id, "src",
497                         v2s32(2, 1), v2s32(1, 1)));
498         draw_spec.push_back(GUIInventoryMenu::DrawSpec(
499                         "list", furnace_inv_id, "dst",
500                         v2s32(5, 1), v2s32(2, 2)));
501         draw_spec.push_back(GUIInventoryMenu::DrawSpec(
502                         "list", "current_player", "main",
503                         v2s32(0, 5), v2s32(8, 4)));
504         setDrawSpec(draw_spec);
505
506         Here is the string for creating the same DrawSpec list (a single line,
507         spread to multiple lines here):
508         
509         GUIInventoryMenu::makeDrawSpecArrayFromString(
510                         draw_spec,
511                         "nodemetadata:0,1,2",
512                         "invsize[8,9;]"
513                         "list[current_name;fuel;2,3;1,1;]"
514                         "list[current_name;src;2,1;1,1;]"
515                         "list[current_name;dst;5,1;2,2;]"
516                         "list[current_player;main;0,5;8,4;]");
517         
518         Returns inventory menu size defined by invsize[].
519 */
520 v2s16 GUIInventoryMenu::makeDrawSpecArrayFromString(
521                 core::array<GUIInventoryMenu::DrawSpec> &draw_spec,
522                 const std::string &data,
523                 const InventoryLocation &current_location)
524 {
525         v2s16 invsize(8,9);
526         Strfnd f(data);
527         while(f.atend() == false)
528         {
529                 std::string type = trim(f.next("["));
530                 //infostream<<"type="<<type<<std::endl;
531                 if(type == "list")
532                 {
533                         std::string name = f.next(";");
534                         InventoryLocation loc;
535                         if(name == "current_name")
536                                 loc = current_location;
537                         else
538                                 loc.deSerialize(name);
539                         std::string subname = f.next(";");
540                         s32 pos_x = stoi(f.next(","));
541                         s32 pos_y = stoi(f.next(";"));
542                         s32 geom_x = stoi(f.next(","));
543                         s32 geom_y = stoi(f.next(";"));
544                         infostream<<"list name="<<name<<", subname="<<subname
545                                         <<", pos=("<<pos_x<<","<<pos_y<<")"
546                                         <<", geom=("<<geom_x<<","<<geom_y<<")"
547                                         <<std::endl;
548                         draw_spec.push_back(GUIInventoryMenu::DrawSpec(
549                                         type, loc, subname,
550                                         v2s32(pos_x,pos_y),v2s32(geom_x,geom_y)));
551                         f.next("]");
552                 }
553                 else if(type == "invsize")
554                 {
555                         invsize.X = stoi(f.next(","));
556                         invsize.Y = stoi(f.next(";"));
557                         infostream<<"invsize ("<<invsize.X<<","<<invsize.Y<<")"<<std::endl;
558                         f.next("]");
559                 }
560                 else
561                 {
562                         // Ignore others
563                         std::string ts = f.next("]");
564                         infostream<<"Unknown DrawSpec: type="<<type<<", data=\""<<ts<<"\""
565                                         <<std::endl;
566                 }
567         }
568
569         return invsize;
570 }
571