gcsx_layerlist.cpp 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519
  1. /* GCSx
  2. ** LAYERLIST.CPP
  3. **
  4. ** Listbox for layer selection
  5. */
  6. /*****************************************************************************
  7. ** Copyright (C) 2003-2006 Janson
  8. **
  9. ** This program is free software; you can redistribute it and/or modify
  10. ** it under the terms of the GNU General Public License as published by
  11. ** the Free Software Foundation; either version 2 of the License, or
  12. ** (at your option) any later version.
  13. **
  14. ** This program is distributed in the hope that it will be useful,
  15. ** but WITHOUT ANY WARRANTY; without even the implied warranty of
  16. ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  17. ** GNU General Public License for more details.
  18. **
  19. ** You should have received a copy of the GNU General Public License
  20. ** along with this program; if not, write to the Free Software
  21. ** Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
  22. *****************************************************************************/
  23. #include "all.h"
  24. const string WLayerListBox::wtLayerVisible("o");
  25. const string WLayerListBox::wtLayerEditable("p");
  26. const string WLayerListBox::wtLayerSprites("s");
  27. const string WLayerListBox::wtLayerTile("q");
  28. const string WLayerListBox::wtLayerFont("r");
  29. const string WLayerListBox::wtLayerImage("g");
  30. WLayerListBox::WLayerListBox(int lId, int* lSelection, int lWidth, int lLines) : WListBox(lId, lSelection, 0, lWidth, lLines) { start_func
  31. // Determine sizing of icons, gutters and such
  32. int iconWidth[4];
  33. iconWidth[0] = fontWidth(wtLayerVisible, FONT_WIDGET);
  34. iconWidth[1] = fontWidth(wtLayerSprites, FONT_WIDGET);
  35. iconWidth[2] = fontWidth(wtLayerEditable, FONT_WIDGET);
  36. iconWidth[3] = max(fontWidth(wtLayerTile, FONT_WIDGET), fontWidth(wtLayerFont, FONT_WIDGET));
  37. iconWidth[3] = max(iconWidth[3], fontWidth(wtLayerImage, FONT_WIDGET));
  38. int iconGutter = iconWidth[0] / 2;
  39. int total = GUI_LISTBOX_HORIZPAD;
  40. for (int pos = 0; pos < 5; ++pos) {
  41. iconPos[pos] = total;
  42. if (pos < 4) total += iconWidth[pos] + iconGutter;
  43. }
  44. // Width of items is larger, for widgets
  45. showWidth += iconPos[4] - GUI_LISTBOX_HORIZPAD;
  46. // Height of items may need to be larger for widget
  47. valueHeight = max(valueHeight, fontHeight(FONT_WIDGET));
  48. // Center text and icons
  49. yAddIcon = yAddText = 0;
  50. int iconH = fontHeight(FONT_WIDGET);
  51. int textH = fontHeight();
  52. if (iconH > textH) yAddText = (iconH - textH) / 2;
  53. else yAddIcon = (textH - iconH) / 2;
  54. scene = NULL;
  55. }
  56. WLayerListBox::~WLayerListBox() { start_func
  57. }
  58. void WLayerListBox::setScene(SceneEdit* lScene) { start_func
  59. scene = lScene;
  60. }
  61. void WLayerListBox::removeItem(int position) { start_func
  62. // Remove item
  63. vector<ListEntry>::iterator pos = contents.begin();
  64. int posnum = 0;
  65. for (; (pos != contents.end()) && (posnum < position); ++pos, ++posnum) ;
  66. contents.erase(pos);
  67. --numValues;
  68. // Close up gap in ids
  69. for (int adjpos = position; adjpos < numValues; ++adjpos) {
  70. --contents[adjpos].id;
  71. }
  72. resize(valueWidth + GUI_LISTBOX_HORIZPAD + iconPos[3],
  73. valueHeight * numValues);
  74. // No items?
  75. if (!numValues) {
  76. dragSet = -1;
  77. cursorPos = 0;
  78. selectedItem = SELECTED_NONE;
  79. }
  80. else {
  81. // Adjust cursor/selected/drag?
  82. if (dragSet > posnum) --dragSet;
  83. else if (dragSet == posnum) dragSet = -1;
  84. if (selectedItem > posnum) --selectedItem;
  85. else if (selectedItem == posnum) selectedItem = -SELECTED_NONE;
  86. if (cursorPos > posnum) --cursorPos;
  87. else if (cursorPos == posnum) cursorPos = -1;
  88. if ((selectedItem == SELECTED_NONE) || (cursorPos < 0)) {
  89. // Move cursor to first non-disabled item
  90. int selpos = 0;
  91. for (; selpos < numValues; ++selpos) {
  92. if (!contents[selpos].disabled) break;
  93. }
  94. // (if nothing to select...)
  95. if (selpos >= numValues) selpos = 0;
  96. moveCursor(selpos);
  97. selectItem(selpos, 1);
  98. }
  99. }
  100. // Scroll, update screen
  101. if (wparent) wparent->scrollToView(0, cursorPos * valueHeight, width, valueHeight);
  102. setDirty();
  103. }
  104. void WLayerListBox::swapItems(int pos1, int pos2) { start_func
  105. // Swap cursor/selected/drag if applicable
  106. if (cursorPos == pos1) cursorPos = pos2;
  107. else if (cursorPos == pos2) cursorPos = pos1;
  108. if (selectedItem == pos1) selectedItem = pos2;
  109. else if (selectedItem == pos2) selectedItem = pos1;
  110. if (dragSet == pos1) dragSet = pos2;
  111. else if (dragSet == pos2) dragSet = pos1;
  112. // Swap the two items EXCEPT for ids (which are position based)
  113. swap(contents[pos1], contents[pos2]);
  114. swap(contents[pos1].id, contents[pos2].id);
  115. // Scroll, update screen
  116. if (wparent) wparent->scrollToView(0, cursorPos * valueHeight, width, valueHeight);
  117. setDirty();
  118. }
  119. void WLayerListBox::addItem(const ListEntry& item, int selectIt) { start_func
  120. addItem(numValues, item, selectIt);
  121. }
  122. void WLayerListBox::addItem(int position, const ListEntry& item, int selectIt) { start_func
  123. if (position >= numValues) {
  124. contents.push_back(item);
  125. }
  126. else {
  127. vector<ListEntry>::iterator pos = contents.begin();
  128. int posnum = 0;
  129. for (; (pos != contents.end()) && (posnum < position); ++pos, ++posnum) ;
  130. contents.insert(pos, item);
  131. position = posnum;
  132. // Modify ids (numValues hasn't changed yet)
  133. for (int adjpos = position + 1; adjpos <= numValues; ++adjpos) {
  134. ++contents[adjpos].id;
  135. }
  136. // Move cursor/selected/drag up if applicable
  137. if (cursorPos >= position) ++cursorPos;
  138. if (selectedItem >= position) ++selectedItem;
  139. if (dragSet >= position) ++dragSet;
  140. }
  141. numValues = contents.size();
  142. // (limit length of display but don't affect actual data)
  143. int itemWidth = fontWidth(item.label.substr(0, MAX_LINELENGTH));
  144. if (itemWidth > valueWidth) valueWidth = itemWidth;
  145. if (selectIt) {
  146. moveCursor(position);
  147. selectItem(cursorPos);
  148. }
  149. resize(valueWidth + GUI_LISTBOX_HORIZPAD + iconPos[3],
  150. valueHeight * numValues);
  151. }
  152. void WLayerListBox::display(SDL_Surface* destSurface, Rect& toDisplay, const Rect& clipArea, int xOffset, int yOffset) { start_func
  153. assert(destSurface);
  154. if (visible) {
  155. // If dirty, redraw all
  156. if (dirty) {
  157. getRect(toDisplay);
  158. toDisplay.x += xOffset;
  159. toDisplay.y += yOffset;
  160. // Always fill entire width of view
  161. if (viewWidth > toDisplay.w) toDisplay.w = viewWidth;
  162. dirty = 0;
  163. intersectRects(toDisplay, clipArea);
  164. }
  165. // Anything to draw?
  166. if (toDisplay.w) {
  167. SDL_SetClipRect(destSurface, &toDisplay);
  168. xOffset += x;
  169. yOffset += y;
  170. // This widget attempts to only redraw listbox entries within the
  171. // dirtied area.
  172. // Background
  173. SDL_FillRect(destSurface, &toDisplay, guiPacked[COLOR_TEXTBOX]);
  174. // Draw all listbox entries we can see
  175. int pos = (toDisplay.y - yOffset) / valueHeight;
  176. // (add in value height - 1 so it rounds up)
  177. int last = (toDisplay.y + toDisplay.h - yOffset + valueHeight - 1) / valueHeight;
  178. if (last > numValues) last = numValues;
  179. int shownWidth = valueWidth + iconPos[4] + GUI_LISTBOX_HORIZPAD;
  180. if (viewWidth > shownWidth) shownWidth = viewWidth;
  181. for (; pos < last; ++pos) {
  182. int color = COLOR_TEXT;
  183. if ((contents[pos].selected) && (!disabled) && (!contents[pos].disabled)) {
  184. drawGradient(xOffset, valueHeight * pos + yOffset,
  185. shownWidth, valueHeight, guiRGB[COLOR_SELECTION1],
  186. guiRGB[COLOR_SELECTION2], destSurface);
  187. drawText(contents[pos].label, guiRGB[COLOR_TEXTBOX],
  188. xOffset + iconPos[4], valueHeight * pos + yOffset + yAddText, destSurface);
  189. color = COLOR_TEXTBOX;
  190. }
  191. else {
  192. if (disabled || contents[pos].disabled) color = COLOR_LIGHT2;
  193. drawText(contents[pos].label, guiRGB[color],
  194. xOffset + iconPos[4], valueHeight * pos + yOffset + yAddText, destSurface);
  195. }
  196. // Icons
  197. if (contents[pos].code1 & FLAG_VISIBLE) drawText(wtLayerVisible, guiRGB[contents[pos].code1 & FLAG_DIMMED ? COLOR_DARK2 : color == COLOR_LIGHT2 ? COLOR_TEXT : color], xOffset + iconPos[0], valueHeight * pos + yOffset + yAddIcon, destSurface, FONT_WIDGET);
  198. if (contents[pos].code1 & FLAG_SPRITES_VISIBLE) drawText(wtLayerSprites, guiRGB[contents[pos].code1 & FLAG_SPRITES_DIMMED ? COLOR_DARK2 : color == COLOR_LIGHT2 ? COLOR_TEXT : color], xOffset + iconPos[1], valueHeight * pos + yOffset + yAddIcon, destSurface, FONT_WIDGET);
  199. if (contents[pos].code1 & FLAG_EDITABLE) drawText(wtLayerEditable, guiRGB[color], xOffset + iconPos[2], valueHeight * pos + yOffset + yAddIcon, destSurface, FONT_WIDGET);
  200. const string* icon = NULL;
  201. switch (contents[pos].code2) {
  202. case Layer::LAYER_TILE:
  203. icon = &wtLayerTile;
  204. break;
  205. case Layer::LAYER_IMAGE:
  206. icon = &wtLayerImage;
  207. break;
  208. case Layer::LAYER_FONT:
  209. icon = &wtLayerFont;
  210. break;
  211. }
  212. if (icon) drawText(*icon, guiRGB[color], xOffset + iconPos[3], valueHeight * pos + yOffset + yAddIcon, destSurface, FONT_WIDGET);
  213. if ((haveFocus) && (pos == cursorPos)) {
  214. drawFocusBox(xOffset, valueHeight * pos + yOffset,
  215. shownWidth, valueHeight, destSurface);
  216. }
  217. }
  218. }
  219. }
  220. }
  221. void WLayerListBox::dblClickItem(int id) { start_func
  222. // @TODO: technically should pass our overall frame parent to this function
  223. // but getting that info is a lot of work
  224. scene->getLayerEdit(id)->propertiesDialog(0);
  225. }
  226. void WLayerListBox::rtClickItem(int id) { start_func
  227. dblClickItem(id);
  228. }
  229. void WLayerListBox::delClickItem(int id) { start_func
  230. // @TODO: Confirmation if last non-disabled layer in list
  231. try {
  232. // @TODO: technically should pass our overall frame parent to this function
  233. // but getting that info is a lot of work
  234. scene->deleteLayer(id);
  235. }
  236. catch (FileException& e) {
  237. guiErrorBox(string(e.details), errorTitleFile);
  238. }
  239. }
  240. int WLayerListBox::event(int hasFocus, const SDL_Event* event) { start_func
  241. assert(event);
  242. assert(parent);
  243. if ((numValues == 0) && (event->type != SDL_INPUTFOCUS)) return 0;
  244. switch (event->type) {
  245. case SDL_KEYDOWN:
  246. switch (combineKey(event->key.keysym.sym, event->key.keysym.mod)) {
  247. case SDLK_KP_ENTER:
  248. case SDLK_RETURN:
  249. case SDLK_SPACE:
  250. dblClickItem(cursorPos);
  251. return 1;
  252. case SDLK_DELETE:
  253. case SDLK_BACKSPACE:
  254. delClickItem(cursorPos);
  255. return 1;
  256. }
  257. break;
  258. case SDL_COMMAND:
  259. switch (event->user.code) {
  260. case VIEW_ALLLAYER:
  261. case VIEW_DIMLAYER:
  262. case VIEW_NOLAYER:
  263. // Change all non-selected entries
  264. for (int pos = 0; pos < numValues; ++pos) {
  265. if (!contents[pos].selected) {
  266. contents[pos].code1 = contents[pos].code1 & FLAG_EDITABLE;
  267. if (event->user.code != VIEW_NOLAYER) contents[pos].code1 = contents[pos].code1 | FLAG_VISIBLE | FLAG_SPRITES_VISIBLE;
  268. if (event->user.code == VIEW_DIMLAYER) contents[pos].code1 = contents[pos].code1 | FLAG_DIMMED | FLAG_SPRITES_DIMMED;
  269. }
  270. }
  271. if (wparent) wparent->myParent()->childModified((WListBox*)this);
  272. setDirty();
  273. return 1;
  274. case VIEW_NEXT:
  275. if (selectedItem >= 0) {
  276. for (int pos = selectedItem + 1; pos < numValues; ++pos) {
  277. if (!contents[pos].disabled) {
  278. selectItem(pos, 1, 1);
  279. break;
  280. }
  281. }
  282. }
  283. return 1;
  284. case VIEW_PREV:
  285. if (selectedItem >= 0) {
  286. for (int pos = selectedItem - 1; pos >= 0; --pos) {
  287. if (!contents[pos].disabled) {
  288. selectItem(pos, 1, 1);
  289. break;
  290. }
  291. }
  292. }
  293. return 1;
  294. }
  295. break;
  296. case SDL_MOUSEMOTION:
  297. // Dragging lets us move layers
  298. if (event->motion.state & SDL_BUTTON_LMASK) {
  299. int dragToPos = event->motion.y / valueHeight;
  300. // If dragging to another position, and something IS being dragged...
  301. if ((event->motion.y < height) && (dragToPos != dragSet) && (dragSet >= 0)) {
  302. try {
  303. // @TODO: technically should pass our overall frame parent to this function
  304. // but getting that info is a lot of work
  305. if (scene) scene->swapLayer(dragSet, dragToPos);
  306. }
  307. catch (UndoException &e) {
  308. }
  309. }
  310. return 1;
  311. }
  312. break;
  313. case SDL_MOUSEBUTTONDBL:
  314. case SDL_MOUSEBUTTONDOWN:
  315. // Right click- properties @TODO: context-menu
  316. if (event->button.button == SDL_BUTTON_RIGHT) {
  317. if (event->button.y < height) {
  318. // Move cursor, don't select
  319. moveCursor(event->motion.y / valueHeight);
  320. rtClickItem(cursorPos);
  321. return 1;
  322. }
  323. }
  324. if (event->button.button == SDL_BUTTON_LEFT) {
  325. int targetPos = event->button.y / valueHeight;
  326. // (nothing being dragged, by default)
  327. dragSet = -1;
  328. if (event->button.y < height) {
  329. if (event->button.x < iconPos[4]) {
  330. // (disallow invisible/dim/uneditable active layer(s))
  331. if (!contents[targetPos].selected) {
  332. // Which icon was clicked?
  333. if (event->button.x < iconPos[1]) {
  334. if (contents[targetPos].code1 & FLAG_DIMMED) contents[targetPos].code1 &= ~(FLAG_VISIBLE | FLAG_DIMMED);
  335. else if (contents[targetPos].code1 & FLAG_VISIBLE) contents[targetPos].code1 |= FLAG_DIMMED;
  336. else contents[targetPos].code1 |= FLAG_VISIBLE;
  337. // (make uneditable if invisible, also)
  338. if (!(contents[targetPos].code1 & FLAG_VISIBLE)) contents[targetPos].code1 &= ~FLAG_EDITABLE;
  339. }
  340. // Disallow editable disabled/invisible layers
  341. if ((event->button.x >= iconPos[2]) && (event->button.x < iconPos[3]) && (!contents[targetPos].disabled) && (contents[targetPos].code1 & FLAG_VISIBLE)) {
  342. contents[targetPos].code1 ^= FLAG_EDITABLE;
  343. }
  344. }
  345. // (sprite visibility currently unaffected by other things)
  346. if ((event->button.x >= iconPos[1]) && (event->button.x < iconPos[2])) {
  347. if (contents[targetPos].code1 & FLAG_SPRITES_DIMMED) contents[targetPos].code1 &= ~(FLAG_SPRITES_VISIBLE | FLAG_SPRITES_DIMMED);
  348. else if (contents[targetPos].code1 & FLAG_SPRITES_VISIBLE) contents[targetPos].code1 |= FLAG_SPRITES_DIMMED;
  349. else contents[targetPos].code1 |= FLAG_SPRITES_VISIBLE;
  350. }
  351. if (wparent) wparent->myParent()->childModified((WListBox*)this);
  352. setDirty();
  353. }
  354. else {
  355. // Select item, allow dragging or handle dbl-click
  356. moveCursor(targetPos);
  357. selectItem(cursorPos, 1);
  358. if (event->type == SDL_MOUSEBUTTONDBL) dblClickItem(cursorPos);
  359. else dragSet = cursorPos;
  360. }
  361. }
  362. return 1;
  363. }
  364. break;
  365. }
  366. return WListBox::event(hasFocus, event);
  367. }
  368. void WLayerListBox::selectItem(int id, int select, int alone) { start_func
  369. assert(id >= 0);
  370. assert(id < numValues);
  371. // (if disabled, no effect at all)
  372. if (contents[id].disabled) return;
  373. // We assume 'alone' and 'select' are both true
  374. // (manually deselect all so we don't send dirty/modified yet)
  375. for (int pos = 0; pos < numValues; ++pos) {
  376. if (contents[pos].selected) {
  377. // Change the item we're deselecting to match the item we're selecting
  378. // (visible, invisible, dim; editable)
  379. contents[pos].code1 = contents[id].code1;
  380. contents[pos].selected = 0;
  381. }
  382. }
  383. selectedItem = id;
  384. contents[id].selected = 1;
  385. // Item we're selecting is 100% visible and editable, now
  386. contents[id].code1 = FLAG_VISIBLE | FLAG_EDITABLE | FLAG_SPRITES_VISIBLE;
  387. if (wparent) wparent->myParent()->childModified(this);
  388. setDirty();
  389. }
  390. void WLayerListBox::setBitMask(Uint32 layersAffect, Uint32 layersView, Uint32 layersDim, int selectedLayer) { start_func
  391. int dirty = 0;
  392. for (int pos = 0; pos < numValues; ++pos) {
  393. int bit = 1 << pos;
  394. int viewType = 0;
  395. if (layersView & bit) {
  396. if (layersDim & bit) viewType = FLAG_VISIBLE | FLAG_DIMMED;
  397. else viewType = FLAG_VISIBLE;
  398. }
  399. if (contents[pos].code1 & (FLAG_VISIBLE | FLAG_DIMMED) != viewType) {
  400. contents[pos].code1 = (contents[pos].code1 & ~(FLAG_VISIBLE | FLAG_DIMMED)) | viewType;
  401. dirty = 1;
  402. }
  403. if (layersAffect & bit) {
  404. if (!(contents[pos].code1 & FLAG_EDITABLE)) {
  405. contents[pos].code1 |= FLAG_EDITABLE;
  406. dirty = 1;
  407. }
  408. }
  409. else if (contents[pos].code1 & FLAG_EDITABLE) {
  410. contents[pos].code1 &= ~FLAG_EDITABLE;
  411. dirty = 1;
  412. }
  413. // Selection modification?
  414. if (selectedLayer >= 0) {
  415. if (selectedLayer == pos) {
  416. if (!contents[pos].selected) {
  417. contents[pos].selected = 1;
  418. selectedItem = pos;
  419. dirty = 1;
  420. }
  421. }
  422. else if (contents[pos].selected) {
  423. contents[pos].selected = 0;
  424. dirty = 1;
  425. }
  426. }
  427. }
  428. if (dirty) setDirty();
  429. }
  430. void WLayerListBox::determineBitMasks(Uint32& layersView, Uint32& layersDim, Uint32& layersAffect) const { start_func
  431. layersView = 0;
  432. layersDim = 0;
  433. layersAffect = 0;
  434. for (int pos = 0; pos < numValues; ++pos) {
  435. int bit = 1 << pos;
  436. if (contents[pos].code1 & FLAG_VISIBLE) {
  437. layersView |= bit;
  438. if (contents[pos].code1 & FLAG_DIMMED) layersDim |= bit;
  439. }
  440. if (contents[pos].code1 & FLAG_EDITABLE) layersAffect |= bit;
  441. }
  442. }