llresizehandle.cpp 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397
  1. /**
  2. * @file llresizehandle.cpp
  3. * @brief LLResizeHandle base class
  4. *
  5. * $LicenseInfo:firstyear=2001&license=viewergpl$
  6. *
  7. * Copyright (c) 2001-2009, Linden Research, Inc.
  8. *
  9. * Second Life Viewer Source Code
  10. * The source code in this file ("Source Code") is provided by Linden Lab
  11. * to you under the terms of the GNU General Public License, version 2.0
  12. * ("GPL"), unless you have obtained a separate licensing agreement
  13. * ("Other License"), formally executed by you and Linden Lab. Terms of
  14. * the GPL can be found in doc/GPL-license.txt in this distribution, or
  15. * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2
  16. *
  17. * There are special exceptions to the terms and conditions of the GPL as
  18. * it is applied to this Source Code. View the full text of the exception
  19. * in the file doc/FLOSS-exception.txt in this software distribution, or
  20. * online at
  21. * http://secondlifegrid.net/programs/open_source/licensing/flossexception
  22. *
  23. * By copying, modifying or distributing this software, you acknowledge
  24. * that you have read and understood your obligations described above,
  25. * and agree to abide by those obligations.
  26. *
  27. * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
  28. * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
  29. * COMPLETENESS OR PERFORMANCE.
  30. * $/LicenseInfo$
  31. */
  32. #include "linden_common.h"
  33. #include "llresizehandle.h"
  34. #include "llcontrol.h"
  35. #include "llfloater.h"
  36. #include "llmenugl.h"
  37. #include "llwindow.h"
  38. constexpr S32 RESIZE_BORDER_WIDTH = 3;
  39. LLResizeHandle::LLResizeHandle(const std::string& name, const LLRect& rect,
  40. S32 min_width, S32 min_height, ECorner corner,
  41. bool set_tooltip)
  42. : LLView(name, rect, true),
  43. mDragLastScreenX(0),
  44. mDragLastScreenY(0),
  45. mLastMouseScreenX(0),
  46. mLastMouseScreenY(0),
  47. mImage(NULL),
  48. mMinWidth(min_width),
  49. mMinHeight(min_height),
  50. mCorner(corner),
  51. mResizing(false)
  52. {
  53. // This is a decorator object: never serialize it.
  54. setSaveToXML(false);
  55. if (RIGHT_BOTTOM == mCorner)
  56. {
  57. mImage = LLUI::getUIImage("UIImgResizeBottomRightUUID");
  58. }
  59. switch (mCorner)
  60. {
  61. case LEFT_TOP:
  62. setFollows(FOLLOWS_LEFT | FOLLOWS_TOP);
  63. break;
  64. case LEFT_BOTTOM:
  65. setFollows(FOLLOWS_LEFT | FOLLOWS_BOTTOM);
  66. break;
  67. case RIGHT_TOP:
  68. setFollows(FOLLOWS_RIGHT | FOLLOWS_TOP);
  69. break;
  70. case RIGHT_BOTTOM:
  71. setFollows(FOLLOWS_RIGHT | FOLLOWS_BOTTOM);
  72. default:
  73. break;
  74. }
  75. if (set_tooltip)
  76. {
  77. setToolTip("Resize"); // *TODO: translate
  78. }
  79. }
  80. bool LLResizeHandle::handleMouseDown(S32 x, S32 y, MASK mask)
  81. {
  82. if (pointInHandle(x, y))
  83. {
  84. // Route future Mouse messages here preemptively (release on mouse up).
  85. // No handler needed for focus lost since this class has no state that
  86. // depends on it.
  87. gFocusMgr.setMouseCapture(this);
  88. localPointToScreen(x, y, &mDragLastScreenX, &mDragLastScreenY);
  89. mLastMouseScreenX = mDragLastScreenX;
  90. mLastMouseScreenY = mDragLastScreenY;
  91. return true;
  92. }
  93. return false;
  94. }
  95. bool LLResizeHandle::handleMouseUp(S32 x, S32 y, MASK mask)
  96. {
  97. mResizing = false;
  98. if (hasMouseCapture())
  99. {
  100. // Release the mouse
  101. gFocusMgr.setMouseCapture(NULL);
  102. return true;
  103. }
  104. if (pointInHandle(x, y))
  105. {
  106. return true;
  107. }
  108. return false;
  109. }
  110. bool LLResizeHandle::handleHover(S32 x, S32 y, MASK mask)
  111. {
  112. mResizing = false;
  113. bool handled = false;
  114. // We only handle the click if the click both started and ended within us
  115. if (hasMouseCapture())
  116. {
  117. // Make sure the mouse in still over the application: we do not want to
  118. // make the parent so big that we cannot see the resize handle any
  119. // more.
  120. S32 screen_x;
  121. S32 screen_y;
  122. localPointToScreen(x, y, &screen_x, &screen_y);
  123. const LLRect valid_rect = getRootView()->getRect();
  124. screen_x = llclamp(screen_x, valid_rect.mLeft, valid_rect.mRight);
  125. screen_y = llclamp(screen_y, valid_rect.mBottom, valid_rect.mTop);
  126. LLView* resize_viewp = getParent();
  127. if (resize_viewp)
  128. {
  129. mResizing = true;
  130. // Resize the parent
  131. LLRect orig_rect = resize_viewp->getRect();
  132. LLRect scaled_rect = orig_rect;
  133. S32 delta_x = screen_x - mDragLastScreenX;
  134. S32 delta_y = screen_y - mDragLastScreenY;
  135. LLCoordGL mouse_dir;
  136. // Use hysteresis on mouse motion to preserve user intent when
  137. // mouse stops moving
  138. mouse_dir.mX =
  139. screen_x == mLastMouseScreenX ? mLastMouseDir.mX
  140. : screen_x - mLastMouseScreenX;
  141. mouse_dir.mY =
  142. screen_y == mLastMouseScreenY ? mLastMouseDir.mY
  143. : screen_y - mLastMouseScreenY;
  144. mLastMouseScreenX = screen_x;
  145. mLastMouseScreenY = screen_y;
  146. mLastMouseDir = mouse_dir;
  147. S32 x_multiple = 1;
  148. S32 y_multiple = 1;
  149. switch (mCorner)
  150. {
  151. case LEFT_TOP:
  152. x_multiple = -1;
  153. y_multiple = 1;
  154. break;
  155. case LEFT_BOTTOM:
  156. x_multiple = -1;
  157. y_multiple = -1;
  158. break;
  159. case RIGHT_TOP:
  160. x_multiple = 1;
  161. y_multiple = 1;
  162. break;
  163. case RIGHT_BOTTOM:
  164. x_multiple = 1;
  165. y_multiple = -1;
  166. }
  167. S32 new_width = orig_rect.getWidth() + x_multiple * delta_x;
  168. if (new_width < mMinWidth)
  169. {
  170. new_width = mMinWidth;
  171. delta_x = x_multiple * (mMinWidth - orig_rect.getWidth());
  172. }
  173. S32 new_height = orig_rect.getHeight() + y_multiple * delta_y;
  174. if (new_height < mMinHeight)
  175. {
  176. new_height = mMinHeight;
  177. delta_y = y_multiple * (mMinHeight - orig_rect.getHeight());
  178. }
  179. switch (mCorner)
  180. {
  181. case LEFT_TOP:
  182. scaled_rect.translate(delta_x, 0);
  183. break;
  184. case LEFT_BOTTOM:
  185. scaled_rect.translate(delta_x, delta_y);
  186. break;
  187. case RIGHT_TOP:
  188. break;
  189. case RIGHT_BOTTOM:
  190. scaled_rect.translate(0, delta_y);
  191. }
  192. // Temporarily set new parent rect
  193. scaled_rect.mRight = scaled_rect.mLeft + new_width;
  194. scaled_rect.mTop = scaled_rect.mBottom + new_height;
  195. resize_viewp->setRect(scaled_rect);
  196. LLView* snap_viewp = NULL;
  197. LLView* test_viewp = NULL;
  198. // Now do snapping
  199. switch (mCorner)
  200. {
  201. case LEFT_TOP:
  202. snap_viewp =
  203. resize_viewp->findSnapEdge(scaled_rect.mLeft,
  204. mouse_dir, SNAP_LEFT,
  205. SNAP_PARENT_AND_SIBLINGS,
  206. LLUI::sSnapMargin);
  207. test_viewp =
  208. resize_viewp->findSnapEdge(scaled_rect.mTop,
  209. mouse_dir, SNAP_TOP,
  210. SNAP_PARENT_AND_SIBLINGS,
  211. LLUI::sSnapMargin);
  212. if (!snap_viewp)
  213. {
  214. snap_viewp = test_viewp;
  215. }
  216. break;
  217. case LEFT_BOTTOM:
  218. snap_viewp =
  219. resize_viewp->findSnapEdge(scaled_rect.mLeft,
  220. mouse_dir, SNAP_LEFT,
  221. SNAP_PARENT_AND_SIBLINGS,
  222. LLUI::sSnapMargin);
  223. test_viewp =
  224. resize_viewp->findSnapEdge(scaled_rect.mBottom,
  225. mouse_dir, SNAP_BOTTOM,
  226. SNAP_PARENT_AND_SIBLINGS,
  227. LLUI::sSnapMargin);
  228. if (!snap_viewp)
  229. {
  230. snap_viewp = test_viewp;
  231. }
  232. break;
  233. case RIGHT_TOP:
  234. snap_viewp =
  235. resize_viewp->findSnapEdge(scaled_rect.mRight,
  236. mouse_dir, SNAP_RIGHT,
  237. SNAP_PARENT_AND_SIBLINGS,
  238. LLUI::sSnapMargin);
  239. test_viewp =
  240. resize_viewp->findSnapEdge(scaled_rect.mTop,
  241. mouse_dir, SNAP_TOP,
  242. SNAP_PARENT_AND_SIBLINGS,
  243. LLUI::sSnapMargin);
  244. if (!snap_viewp)
  245. {
  246. snap_viewp = test_viewp;
  247. }
  248. break;
  249. case RIGHT_BOTTOM:
  250. snap_viewp =
  251. resize_viewp->findSnapEdge(scaled_rect.mRight,
  252. mouse_dir, SNAP_RIGHT,
  253. SNAP_PARENT_AND_SIBLINGS,
  254. LLUI::sSnapMargin);
  255. test_viewp =
  256. resize_viewp->findSnapEdge(scaled_rect.mBottom,
  257. mouse_dir, SNAP_BOTTOM,
  258. SNAP_PARENT_AND_SIBLINGS,
  259. LLUI::sSnapMargin);
  260. if (!snap_viewp)
  261. {
  262. snap_viewp = test_viewp;
  263. }
  264. }
  265. // Register "snap" behavior with snapped view
  266. resize_viewp->snappedTo(snap_viewp);
  267. // Reset parent rect
  268. resize_viewp->setRect(orig_rect);
  269. // Translate and scale to new shape
  270. resize_viewp->userSetShape(scaled_rect);
  271. // Update last valid mouse cursor position based on resized view's
  272. // actual size
  273. LLRect new_rect = resize_viewp->getRect();
  274. switch (mCorner)
  275. {
  276. case LEFT_TOP:
  277. mDragLastScreenX += new_rect.mLeft - orig_rect.mLeft;
  278. mDragLastScreenY += new_rect.mTop - orig_rect.mTop;
  279. break;
  280. case LEFT_BOTTOM:
  281. mDragLastScreenX += new_rect.mLeft - orig_rect.mLeft;
  282. mDragLastScreenY += new_rect.mBottom- orig_rect.mBottom;
  283. break;
  284. case RIGHT_TOP:
  285. mDragLastScreenX += new_rect.mRight - orig_rect.mRight;
  286. mDragLastScreenY += new_rect.mTop - orig_rect.mTop;
  287. break;
  288. case RIGHT_BOTTOM:
  289. mDragLastScreenX += new_rect.mRight - orig_rect.mRight;
  290. mDragLastScreenY += new_rect.mBottom- orig_rect.mBottom;
  291. default:
  292. break;
  293. }
  294. }
  295. handled = true;
  296. }
  297. // We do not have a mouse capture
  298. else if (pointInHandle(x, y))
  299. {
  300. handled = true;
  301. }
  302. if (handled)
  303. {
  304. switch (mCorner)
  305. {
  306. case LEFT_TOP:
  307. case RIGHT_BOTTOM:
  308. gWindowp->setCursor(UI_CURSOR_SIZENWSE);
  309. break;
  310. case LEFT_BOTTOM:
  311. case RIGHT_TOP:
  312. gWindowp->setCursor(UI_CURSOR_SIZENESW);
  313. }
  314. }
  315. return handled;
  316. }
  317. // Assumes GL state is set for 2D
  318. void LLResizeHandle::draw()
  319. {
  320. if (mCorner == RIGHT_BOTTOM && mImage.notNull() && getVisible())
  321. {
  322. mImage->draw(0, 0);
  323. }
  324. }
  325. bool LLResizeHandle::pointInHandle(S32 x, S32 y)
  326. {
  327. if (pointInView(x, y))
  328. {
  329. switch (mCorner)
  330. {
  331. case LEFT_TOP:
  332. return x <= RESIZE_BORDER_WIDTH ||
  333. y >= getRect().getHeight() - RESIZE_BORDER_WIDTH;
  334. case LEFT_BOTTOM:
  335. return x <= RESIZE_BORDER_WIDTH || y <= RESIZE_BORDER_WIDTH;
  336. case RIGHT_TOP:
  337. return x >= getRect().getWidth() - RESIZE_BORDER_WIDTH ||
  338. y >= getRect().getHeight() - RESIZE_BORDER_WIDTH;
  339. case RIGHT_BOTTOM:
  340. return true;
  341. }
  342. }
  343. return false;
  344. }