tb_widgets_common.cpp 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799
  1. // ================================================================================
  2. // == This file is a part of Turbo Badger. (C) 2011-2014, Emil Segerås ==
  3. // == See tb_core.h for more information. ==
  4. // ================================================================================
  5. #include "tb_widgets_common.h"
  6. #include "tb_font_renderer.h"
  7. #include "tb_widgets_listener.h"
  8. #include "tb_system.h"
  9. #include <assert.h>
  10. namespace tb {
  11. // == TBWidgetString =======================================
  12. TBWidgetString::TBWidgetString()
  13. : m_text_align(TB_TEXT_ALIGN_CENTER)
  14. {
  15. }
  16. int TBWidgetString::GetWidth(TBWidget *widget)
  17. {
  18. return widget->GetFont()->GetStringWidth(m_text);
  19. }
  20. int TBWidgetString::GetHeight(TBWidget *widget)
  21. {
  22. return widget->GetFont()->GetHeight();
  23. }
  24. void TBWidgetString::Paint(TBWidget *widget, const TBRect &rect, const TBColor &color)
  25. {
  26. TBFontFace *font = widget->GetFont();
  27. int string_w = GetWidth(widget);
  28. int x = rect.x;
  29. if (m_text_align == TB_TEXT_ALIGN_RIGHT)
  30. x += rect.w - string_w;
  31. else if (m_text_align == TB_TEXT_ALIGN_CENTER)
  32. x += MAX(0, (rect.w - string_w) / 2);
  33. int y = rect.y + (rect.h - GetHeight(widget)) / 2;
  34. if (string_w <= rect.w)
  35. font->DrawString(x, y, color, m_text);
  36. else
  37. {
  38. // There's not enough room for the entire string
  39. // so cut it off and end with ellipsis (...)
  40. // const char *end = "…"; // 2026 HORIZONTAL ELLIPSIS
  41. // Some fonts seem to render ellipsis a lot uglier than three dots.
  42. const char *end = "...";
  43. int endw = font->GetStringWidth(end);
  44. int startw = 0;
  45. int startlen = 0;
  46. while (m_text.CStr()[startlen])
  47. {
  48. int new_startw = font->GetStringWidth(m_text, startlen);
  49. if (new_startw + endw > rect.w)
  50. break;
  51. startw = new_startw;
  52. startlen++;
  53. }
  54. startlen = MAX(0, startlen - 1);
  55. font->DrawString(x, y, color, m_text, startlen);
  56. font->DrawString(x + startw, y, color, end);
  57. }
  58. }
  59. // == TBTextField =======================================
  60. /** This value on m_cached_text_width means it needs to be updated again. */
  61. #define UPDATE_TEXT_WIDTH_CACHE -1
  62. TBTextField::TBTextField()
  63. : m_cached_text_width(UPDATE_TEXT_WIDTH_CACHE)
  64. , m_squeezable(false)
  65. {
  66. SetSkinBg(TBIDC("TBTextField"), WIDGET_INVOKE_INFO_NO_CALLBACKS);
  67. }
  68. bool TBTextField::SetText(const char *text)
  69. {
  70. if (m_text.m_text.Equals(text))
  71. return true;
  72. m_cached_text_width = UPDATE_TEXT_WIDTH_CACHE;
  73. Invalidate();
  74. InvalidateLayout(INVALIDATE_LAYOUT_RECURSIVE);
  75. return m_text.SetText(text);
  76. }
  77. void TBTextField::SetSqueezable(bool squeezable)
  78. {
  79. if (squeezable == m_squeezable)
  80. return;
  81. m_squeezable = squeezable;
  82. Invalidate();
  83. InvalidateLayout(INVALIDATE_LAYOUT_RECURSIVE);
  84. }
  85. PreferredSize TBTextField::OnCalculatePreferredContentSize(const SizeConstraints &constraints)
  86. {
  87. PreferredSize ps;
  88. if (m_cached_text_width == UPDATE_TEXT_WIDTH_CACHE)
  89. m_cached_text_width = m_text.GetWidth(this);
  90. ps.pref_w = m_cached_text_width;
  91. ps.pref_h = ps.min_h = m_text.GetHeight(this);
  92. // If gravity pull both up and down, use default max_h (grow as much as possible).
  93. // Otherwise it makes sense to only accept one line height.
  94. if (!((GetGravity() & WIDGET_GRAVITY_TOP) && (GetGravity() & WIDGET_GRAVITY_BOTTOM)))
  95. ps.max_h = ps.pref_h;
  96. if (!m_squeezable)
  97. ps.min_w = ps.pref_w;
  98. return ps;
  99. }
  100. void TBTextField::OnFontChanged()
  101. {
  102. m_cached_text_width = UPDATE_TEXT_WIDTH_CACHE;
  103. InvalidateLayout(INVALIDATE_LAYOUT_RECURSIVE);
  104. }
  105. void TBTextField::OnPaint(const PaintProps &paint_props)
  106. {
  107. m_text.Paint(this, GetPaddingRect(), paint_props.text_color);
  108. }
  109. // == TBButton =======================================
  110. const int auto_click_first_delay = 500;
  111. const int auto_click_repeat_delay = 100;
  112. TBButton::TBButton()
  113. : m_auto_repeat_click(false)
  114. , m_toggle_mode(false)
  115. {
  116. SetIsFocusable(true);
  117. SetClickByKey(true);
  118. SetSkinBg(TBIDC("TBButton"), WIDGET_INVOKE_INFO_NO_CALLBACKS);
  119. AddChild(&m_layout);
  120. // Set the textfield gravity to all, even though it would display the same with default gravity.
  121. // This will make the buttons layout expand if there is space available, without forcing the parent
  122. // layout to grow to make the space available.
  123. m_textfield.SetGravity(WIDGET_GRAVITY_ALL);
  124. m_layout.AddChild(&m_textfield);
  125. m_layout.SetRect(GetPaddingRect());
  126. m_layout.SetGravity(WIDGET_GRAVITY_ALL);
  127. m_layout.SetPaintOverflowFadeout(false);
  128. }
  129. TBButton::~TBButton()
  130. {
  131. m_layout.RemoveChild(&m_textfield);
  132. RemoveChild(&m_layout);
  133. }
  134. bool TBButton::SetText(const char *text)
  135. {
  136. bool ret = m_textfield.SetText(text);
  137. UpdateTextFieldVisibility();
  138. return ret;
  139. }
  140. void TBButton::OnCaptureChanged(bool captured)
  141. {
  142. if (captured && m_auto_repeat_click)
  143. PostMessageDelayed(TBIDC("auto_click"), nullptr, auto_click_first_delay);
  144. else if (!captured)
  145. {
  146. if (TBMessage *msg = GetMessageByID(TBIDC("auto_click")))
  147. DeleteMessage(msg);
  148. }
  149. SetCaptured(captured);
  150. }
  151. void TBButton::OnSkinChanged()
  152. {
  153. m_layout.SetRect(GetPaddingRect());
  154. }
  155. bool TBButton::OnEvent(const TBWidgetEvent &ev)
  156. {
  157. if (m_toggle_mode && ev.type == EVENT_TYPE_CLICK && ev.target == this)
  158. {
  159. TBWidgetSafePointer this_widget(this);
  160. SetValue(!GetValue());
  161. if (!this_widget.Get())
  162. return true; // We got removed so we actually handled this event.
  163. // Invoke a changed event.
  164. TBWidgetEvent ev(EVENT_TYPE_CHANGED);
  165. InvokeEvent(ev);
  166. if (!this_widget.Get())
  167. return true; // We got removed so we actually handled this event.
  168. // Intentionally don't return true for this event. We want it to continue propagating.
  169. }
  170. return TBWidget::OnEvent(ev);
  171. }
  172. void TBButton::OnMessageReceived(TBMessage *msg)
  173. {
  174. if (msg->message == TBIDC("auto_click"))
  175. {
  176. assert(captured_widget == this);
  177. if (!cancel_click && GetHitStatus(pointer_move_widget_x, pointer_move_widget_y))
  178. {
  179. TBWidgetEvent ev(EVENT_TYPE_CLICK, pointer_move_widget_x, pointer_move_widget_y, true);
  180. captured_widget->InvokeEvent(ev);
  181. }
  182. if (auto_click_repeat_delay)
  183. PostMessageDelayed(TBIDC("auto_click"), nullptr, auto_click_repeat_delay);
  184. }
  185. }
  186. WIDGET_HIT_STATUS TBButton::GetHitStatus(int x, int y)
  187. {
  188. // Never hit any of the children to the button. We always want to the button itself.
  189. return TBWidget::GetHitStatus(x, y) ? WIDGET_HIT_STATUS_HIT_NO_CHILDREN : WIDGET_HIT_STATUS_NO_HIT;
  190. }
  191. void TBButton::UpdateTextFieldVisibility()
  192. {
  193. // Auto-collapse the textfield if the text is empty and there are other
  194. // widgets added apart from the textfield. This removes the extra spacing
  195. // added between the textfield and the other widget.
  196. bool collapse_textfield = m_textfield.IsEmpty() && m_layout.GetFirstChild() != m_layout.GetLastChild();
  197. m_textfield.SetVisibilility(collapse_textfield ? WIDGET_VISIBILITY_GONE : WIDGET_VISIBILITY_VISIBLE);
  198. }
  199. void TBButton::ButtonLayout::OnChildAdded(TBWidget *child)
  200. {
  201. static_cast<TBButton*>(GetParent())->UpdateTextFieldVisibility();
  202. }
  203. void TBButton::ButtonLayout::OnChildRemove(TBWidget *child)
  204. {
  205. static_cast<TBButton*>(GetParent())->UpdateTextFieldVisibility();
  206. }
  207. // == TBClickLabel ==========================================================================================
  208. TBClickLabel::TBClickLabel()
  209. {
  210. AddChild(&m_layout);
  211. m_layout.AddChild(&m_textfield);
  212. m_layout.SetRect(GetPaddingRect());
  213. m_layout.SetGravity(WIDGET_GRAVITY_ALL);
  214. m_layout.SetLayoutDistributionPosition(LAYOUT_DISTRIBUTION_POSITION_LEFT_TOP);
  215. }
  216. TBClickLabel::~TBClickLabel()
  217. {
  218. m_layout.RemoveChild(&m_textfield);
  219. RemoveChild(&m_layout);
  220. }
  221. bool TBClickLabel::OnEvent(const TBWidgetEvent &ev)
  222. {
  223. // Get a widget from the layout that isn't the textfield, or just bail out
  224. // if we only have the textfield.
  225. if (m_layout.GetFirstChild() == m_layout.GetLastChild())
  226. return false;
  227. TBWidget *click_target = (m_layout.GetFirstChild() == &m_textfield ? m_layout.GetLastChild() : m_layout.GetFirstChild());
  228. // Invoke the event on it, as if it was invoked on the target itself.
  229. if (click_target && ev.target != click_target)
  230. {
  231. // Focus the target if we clicked the label.
  232. if (ev.type == EVENT_TYPE_CLICK)
  233. click_target->SetFocus(WIDGET_FOCUS_REASON_POINTER);
  234. // Sync our pressed state with the click target. Special case for when we're just about to
  235. // lose it ourself (pointer is being released).
  236. bool pressed_state = (ev.target->GetAutoState() & WIDGET_STATE_PRESSED) ? true : false;
  237. if (ev.type == EVENT_TYPE_POINTER_UP || ev.type == EVENT_TYPE_CLICK)
  238. pressed_state = false;
  239. click_target->SetState(WIDGET_STATE_PRESSED, pressed_state);
  240. TBWidgetEvent target_ev(ev.type, ev.target_x - click_target->GetRect().x, ev.target_y - click_target->GetRect().y,
  241. ev.touch, ev.modifierkeys);
  242. return click_target->InvokeEvent(target_ev);
  243. }
  244. return false;
  245. }
  246. // == TBSkinImage =======================================
  247. PreferredSize TBSkinImage::OnCalculatePreferredSize(const SizeConstraints &constraints)
  248. {
  249. PreferredSize ps = TBWidget::OnCalculatePreferredSize(constraints);
  250. // FIX: Make it stretched proportionally if shrunk.
  251. ps.max_w = ps.pref_w;
  252. ps.max_h = ps.pref_h;
  253. return ps;
  254. }
  255. // == TBSeparator ===========================================
  256. TBSeparator::TBSeparator()
  257. {
  258. SetSkinBg(TBIDC("TBSeparator"), WIDGET_INVOKE_INFO_NO_CALLBACKS);
  259. SetState(WIDGET_STATE_DISABLED, true);
  260. }
  261. // == TBProgressSpinner =====================================
  262. // FIX: Add spin_speed to skin!
  263. // FIX: Make it post messages only if visible
  264. const int spin_speed = 1000/30; ///< How fast should the spinner animation animate.
  265. TBProgressSpinner::TBProgressSpinner()
  266. : m_value(0)
  267. , m_frame(0)
  268. {
  269. SetSkinBg(TBIDC("TBProgressSpinner"), WIDGET_INVOKE_INFO_NO_CALLBACKS);
  270. m_skin_fg.Set(TBIDC("TBProgressSpinner.fg"));
  271. }
  272. void TBProgressSpinner::SetValue(int value)
  273. {
  274. if (value == m_value)
  275. return;
  276. InvalidateSkinStates();
  277. assert(value >= 0); // If this happens, you probably have unballanced Begin/End calls.
  278. m_value = value;
  279. if (value > 0)
  280. {
  281. // Start animation
  282. if (!GetMessageByID(TBID(1)))
  283. {
  284. m_frame = 0;
  285. PostMessageDelayed(TBID(1), nullptr, spin_speed);
  286. }
  287. }
  288. else
  289. {
  290. // Stop animation
  291. if (TBMessage *msg = GetMessageByID(TBID(1)))
  292. DeleteMessage(msg);
  293. }
  294. }
  295. void TBProgressSpinner::OnPaint(const PaintProps &paint_props)
  296. {
  297. if (IsRunning())
  298. {
  299. TBSkinElement *e = g_tb_skin->GetSkinElement(m_skin_fg);
  300. if (e && e->bitmap)
  301. {
  302. int size = e->bitmap->Height();
  303. int num_frames = e->bitmap->Width() / e->bitmap->Height();
  304. int current_frame = m_frame % num_frames;
  305. g_renderer->DrawBitmap(GetPaddingRect(), TBRect(current_frame * size, 0, size, size), e->bitmap);
  306. }
  307. }
  308. }
  309. void TBProgressSpinner::OnMessageReceived(TBMessage *msg)
  310. {
  311. m_frame++;
  312. Invalidate();
  313. // Keep animation running
  314. PostMessageDelayed(TBID(1), nullptr, spin_speed);
  315. }
  316. // == TBRadioCheckBox =======================================
  317. TBRadioCheckBox::TBRadioCheckBox()
  318. : m_value(0)
  319. {
  320. SetIsFocusable(true);
  321. SetClickByKey(true);
  322. }
  323. //static
  324. void TBRadioCheckBox::ToggleGroup(TBWidget *root, TBWidget *toggled)
  325. {
  326. if (root != toggled && root->GetGroupID() == toggled->GetGroupID())
  327. root->SetValue(0);
  328. for (TBWidget *child = root->GetFirstChild(); child; child = child->GetNext())
  329. ToggleGroup(child, toggled);
  330. }
  331. void TBRadioCheckBox::SetValue(int value)
  332. {
  333. if (m_value == value)
  334. return;
  335. m_value = value;
  336. SetState(WIDGET_STATE_SELECTED, value ? true : false);
  337. Invalidate();
  338. TBWidgetEvent ev(EVENT_TYPE_CHANGED);
  339. InvokeEvent(ev);
  340. if (!value || !GetGroupID())
  341. return;
  342. // Toggle all other widgets in the same group. First get a root widget
  343. // for the search.
  344. TBWidget *group = this;
  345. while (group && !group->GetIsGroupRoot())
  346. group = group->GetParent();
  347. if (group)
  348. {
  349. ToggleGroup(group, this);
  350. }
  351. }
  352. PreferredSize TBRadioCheckBox::OnCalculatePreferredSize(const SizeConstraints &constraints)
  353. {
  354. PreferredSize ps = TBWidget::OnCalculatePreferredSize(constraints);
  355. ps.min_w = ps.max_w = ps.pref_w;
  356. ps.min_h = ps.max_h = ps.pref_h;
  357. return ps;
  358. }
  359. bool TBRadioCheckBox::OnEvent(const TBWidgetEvent &ev)
  360. {
  361. if (ev.target == this && ev.type == EVENT_TYPE_CLICK)
  362. {
  363. // Toggle the value, if it's not a grouped widget with value on.
  364. if (!(GetGroupID() && GetValue()))
  365. {
  366. SetValue(!GetValue());
  367. }
  368. }
  369. return TBWidget::OnEvent(ev);
  370. }
  371. // == TBScrollBar =======================================
  372. TBScrollBar::TBScrollBar()
  373. : m_axis(AXIS_Y) ///< Make SetAxis below always succeed and set the skin
  374. , m_value(0)
  375. , m_min(0)
  376. , m_max(1)
  377. , m_visible(1)
  378. , m_to_pixel_factor(0)
  379. {
  380. SetAxis(AXIS_X);
  381. AddChild(&m_handle);
  382. }
  383. TBScrollBar::~TBScrollBar()
  384. {
  385. RemoveChild(&m_handle);
  386. }
  387. void TBScrollBar::SetAxis(AXIS axis)
  388. {
  389. if (axis == m_axis)
  390. return;
  391. m_axis = axis;
  392. if (axis == AXIS_X)
  393. {
  394. SetSkinBg(TBIDC("TBScrollBarBgX"), WIDGET_INVOKE_INFO_NO_CALLBACKS);
  395. m_handle.SetSkinBg(TBIDC("TBScrollBarFgX"), WIDGET_INVOKE_INFO_NO_CALLBACKS);
  396. }
  397. else
  398. {
  399. SetSkinBg(TBIDC("TBScrollBarBgY"), WIDGET_INVOKE_INFO_NO_CALLBACKS);
  400. m_handle.SetSkinBg(TBIDC("TBScrollBarFgY"), WIDGET_INVOKE_INFO_NO_CALLBACKS);
  401. }
  402. Invalidate();
  403. }
  404. void TBScrollBar::SetLimits(double min, double max, double visible)
  405. {
  406. max = MAX(min, max);
  407. visible = MAX(visible, 0.0);
  408. if (min == m_min && max == m_max && m_visible == visible)
  409. return;
  410. m_min = min;
  411. m_max = max;
  412. m_visible = visible;
  413. SetValueDouble(m_value);
  414. // If we're currently dragging the scrollbar handle, convert the down point
  415. // to root and then back after the applying the new limit.
  416. // This prevents sudden jumps to unexpected positions when scrolling.
  417. if (captured_widget == &m_handle)
  418. m_handle.ConvertToRoot(pointer_down_widget_x, pointer_down_widget_y);
  419. UpdateHandle();
  420. if (captured_widget == &m_handle)
  421. m_handle.ConvertFromRoot(pointer_down_widget_x, pointer_down_widget_y);
  422. }
  423. void TBScrollBar::SetValueDouble(double value)
  424. {
  425. value = CLAMP(value, m_min, m_max);
  426. if (value == m_value)
  427. return;
  428. m_value = value;
  429. UpdateHandle();
  430. TBWidgetEvent ev(EVENT_TYPE_CHANGED);
  431. InvokeEvent(ev);
  432. }
  433. bool TBScrollBar::OnEvent(const TBWidgetEvent &ev)
  434. {
  435. if (ev.type == EVENT_TYPE_POINTER_MOVE && captured_widget == &m_handle)
  436. {
  437. if (m_to_pixel_factor > 0)
  438. {
  439. int dx = ev.target_x - pointer_down_widget_x;
  440. int dy = ev.target_y - pointer_down_widget_y;
  441. double delta_val = (m_axis == AXIS_X ? dx : dy) / m_to_pixel_factor;
  442. SetValueDouble(m_value + delta_val);
  443. }
  444. return true;
  445. }
  446. else if (ev.type == EVENT_TYPE_POINTER_MOVE && ev.target == this)
  447. return true;
  448. else if (ev.type == EVENT_TYPE_POINTER_DOWN && ev.target == this)
  449. {
  450. bool after_handle = (m_axis == AXIS_X ? ev.target_x > m_handle.GetRect().x : ev.target_y > m_handle.GetRect().y);
  451. SetValueDouble(m_value + (after_handle ? m_visible : -m_visible));
  452. return true;
  453. }
  454. else if (ev.type == EVENT_TYPE_WHEEL)
  455. {
  456. double old_val = m_value;
  457. SetValueDouble(m_value + ev.delta_y * TBSystem::GetPixelsPerLine());
  458. return m_value != old_val;
  459. }
  460. return false;
  461. }
  462. void TBScrollBar::UpdateHandle()
  463. {
  464. // Calculate the mover size and position
  465. bool horizontal = m_axis == AXIS_X;
  466. int available_pixels = horizontal ? GetRect().w : GetRect().h;
  467. int min_thickness_pixels = MIN(GetRect().h, GetRect().w);
  468. int visible_pixels = available_pixels;
  469. if (m_max - m_min > 0 && m_visible > 0)
  470. {
  471. double visible_proportion = m_visible / (m_visible + m_max - m_min);
  472. visible_pixels = (int)(visible_proportion * available_pixels);
  473. // Limit the size of the indicator to the slider thickness so that it doesn't
  474. // become too tiny when the visible proportion is very small.
  475. visible_pixels = MAX(visible_pixels, min_thickness_pixels);
  476. m_to_pixel_factor = (double)(available_pixels - visible_pixels) / (m_max - m_min)/*+ 0.5*/;
  477. }
  478. else
  479. {
  480. m_to_pixel_factor = 0;
  481. // If we can't scroll anything, make the handle invisible
  482. visible_pixels = 0;
  483. }
  484. int pixel_pos = (int)(m_value * m_to_pixel_factor);
  485. TBRect rect;
  486. if (horizontal)
  487. rect.Set(pixel_pos, 0, visible_pixels, GetRect().h);
  488. else
  489. rect.Set(0, pixel_pos, GetRect().w, visible_pixels);
  490. m_handle.SetRect(rect);
  491. }
  492. void TBScrollBar::OnResized(int old_w, int old_h)
  493. {
  494. UpdateHandle();
  495. }
  496. // == TBSlider ============================================
  497. TBSlider::TBSlider()
  498. : m_axis(AXIS_Y) ///< Make SetAxis below always succeed and set the skin
  499. , m_value(0)
  500. , m_min(0)
  501. , m_max(1)
  502. , m_to_pixel_factor(0)
  503. {
  504. SetIsFocusable(true);
  505. SetAxis(AXIS_X);
  506. AddChild(&m_handle);
  507. }
  508. TBSlider::~TBSlider()
  509. {
  510. RemoveChild(&m_handle);
  511. }
  512. void TBSlider::SetAxis(AXIS axis)
  513. {
  514. if (axis == m_axis)
  515. return;
  516. m_axis = axis;
  517. if (axis == AXIS_X)
  518. {
  519. SetSkinBg(TBIDC("TBSliderBgX"), WIDGET_INVOKE_INFO_NO_CALLBACKS);
  520. m_handle.SetSkinBg(TBIDC("TBSliderFgX"), WIDGET_INVOKE_INFO_NO_CALLBACKS);
  521. }
  522. else
  523. {
  524. SetSkinBg(TBIDC("TBSliderBgY"), WIDGET_INVOKE_INFO_NO_CALLBACKS);
  525. m_handle.SetSkinBg(TBIDC("TBSliderFgY"), WIDGET_INVOKE_INFO_NO_CALLBACKS);
  526. }
  527. Invalidate();
  528. }
  529. void TBSlider::SetLimits(double min, double max)
  530. {
  531. min = MIN(min, max);
  532. if (min == m_min && max == m_max)
  533. return;
  534. m_min = min;
  535. m_max = max;
  536. SetValueDouble(m_value);
  537. UpdateHandle();
  538. }
  539. void TBSlider::SetValueDouble(double value)
  540. {
  541. value = CLAMP(value, m_min, m_max);
  542. if (value == m_value)
  543. return;
  544. m_value = value;
  545. UpdateHandle();
  546. TBWidgetEvent ev(EVENT_TYPE_CHANGED);
  547. InvokeEvent(ev);
  548. }
  549. bool TBSlider::OnEvent(const TBWidgetEvent &ev)
  550. {
  551. if (ev.type == EVENT_TYPE_POINTER_MOVE && captured_widget == &m_handle)
  552. {
  553. if (m_to_pixel_factor > 0)
  554. {
  555. int dx = ev.target_x - pointer_down_widget_x;
  556. int dy = ev.target_y - pointer_down_widget_y;
  557. double delta_val = (m_axis == AXIS_X ? dx : -dy) / m_to_pixel_factor;
  558. SetValueDouble(m_value + delta_val);
  559. }
  560. return true;
  561. }
  562. else if (ev.type == EVENT_TYPE_WHEEL)
  563. {
  564. double old_val = m_value;
  565. double step = (m_axis == AXIS_X ? GetSmallStep() : -GetSmallStep());
  566. SetValueDouble(m_value + step * ev.delta_y);
  567. return m_value != old_val;
  568. }
  569. else if (ev.type == EVENT_TYPE_KEY_DOWN)
  570. {
  571. double step = (m_axis == AXIS_X ? GetSmallStep() : -GetSmallStep());
  572. if (ev.special_key == TB_KEY_LEFT || ev.special_key == TB_KEY_UP)
  573. SetValueDouble(GetValueDouble() - step);
  574. else if (ev.special_key == TB_KEY_RIGHT || ev.special_key == TB_KEY_DOWN)
  575. SetValueDouble(GetValueDouble() + step);
  576. else
  577. return false;
  578. return true;
  579. }
  580. else if (ev.type == EVENT_TYPE_KEY_UP)
  581. {
  582. if (ev.special_key == TB_KEY_LEFT || ev.special_key == TB_KEY_UP ||
  583. ev.special_key == TB_KEY_RIGHT || ev.special_key == TB_KEY_DOWN)
  584. return true;
  585. }
  586. return false;
  587. }
  588. void TBSlider::UpdateHandle()
  589. {
  590. // Calculate the handle position
  591. bool horizontal = m_axis == AXIS_X;
  592. int available_pixels = horizontal ? GetRect().w : GetRect().h;
  593. TBRect rect;
  594. if (m_max - m_min > 0)
  595. {
  596. PreferredSize ps = m_handle.GetPreferredSize();
  597. int handle_pixels = horizontal ? ps.pref_w : ps.pref_h;
  598. m_to_pixel_factor = (double)(available_pixels - handle_pixels) / (m_max - m_min)/*+ 0.5*/;
  599. int pixel_pos = (int)((m_value - m_min) * m_to_pixel_factor);
  600. if (horizontal)
  601. rect.Set(pixel_pos, (GetRect().h - ps.pref_h) / 2, ps.pref_w, ps.pref_h);
  602. else
  603. rect.Set((GetRect().w - ps.pref_w) / 2, GetRect().h - handle_pixels - pixel_pos, ps.pref_w, ps.pref_h);
  604. }
  605. else
  606. m_to_pixel_factor = 0;
  607. m_handle.SetRect(rect);
  608. }
  609. void TBSlider::OnResized(int old_w, int old_h)
  610. {
  611. UpdateHandle();
  612. }
  613. // == TBContainer ===================================
  614. TBContainer::TBContainer()
  615. {
  616. SetSkinBg(TBIDC("TBContainer"), WIDGET_INVOKE_INFO_NO_CALLBACKS);
  617. }
  618. // == TBMover =======================================
  619. TBMover::TBMover()
  620. {
  621. SetSkinBg(TBIDC("TBMover"), WIDGET_INVOKE_INFO_NO_CALLBACKS);
  622. }
  623. bool TBMover::OnEvent(const TBWidgetEvent &ev)
  624. {
  625. TBWidget *target = GetParent();
  626. if (!target)
  627. return false;
  628. if (ev.type == EVENT_TYPE_POINTER_MOVE && captured_widget == this)
  629. {
  630. int dx = ev.target_x - pointer_down_widget_x;
  631. int dy = ev.target_y - pointer_down_widget_y;
  632. TBRect rect = target->GetRect().Offset(dx, dy);
  633. if (target->GetParent())
  634. {
  635. // Apply limit.
  636. rect.x = CLAMP(rect.x, -pointer_down_widget_x, target->GetParent()->GetRect().w - pointer_down_widget_x);
  637. rect.y = CLAMP(rect.y, -pointer_down_widget_y, target->GetParent()->GetRect().h - pointer_down_widget_y);
  638. }
  639. target->SetRect(rect);
  640. return true;
  641. }
  642. return false;
  643. }
  644. // == TBResizer =======================================
  645. TBResizer::TBResizer()
  646. {
  647. SetSkinBg(TBIDC("TBResizer"), WIDGET_INVOKE_INFO_NO_CALLBACKS);
  648. }
  649. WIDGET_HIT_STATUS TBResizer::GetHitStatus(int x, int y)
  650. {
  651. // Shave off some of the upper left diagonal half from the hit area.
  652. const int extra_hit_area = 3;
  653. if (x < GetRect().w - y - extra_hit_area)
  654. return WIDGET_HIT_STATUS_NO_HIT;
  655. return TBWidget::GetHitStatus(x, y);
  656. }
  657. bool TBResizer::OnEvent(const TBWidgetEvent &ev)
  658. {
  659. TBWidget *target = GetParent();
  660. if (!target)
  661. return false;
  662. if (ev.type == EVENT_TYPE_POINTER_MOVE && captured_widget == this)
  663. {
  664. int dx = ev.target_x - pointer_down_widget_x;
  665. int dy = ev.target_y - pointer_down_widget_y;
  666. TBRect rect = target->GetRect();
  667. rect.w += dx;
  668. rect.h += dy;
  669. // Apply limit. We should not use minimum size since we can squeeze
  670. // the layout much more, and provide scroll/pan when smaller.
  671. rect.w = MAX(rect.w, 50);
  672. rect.h = MAX(rect.h, 50);
  673. target->SetRect(rect);
  674. }
  675. else
  676. return false;
  677. return true;
  678. }
  679. // == TBDimmer =======================================
  680. TBDimmer::TBDimmer()
  681. {
  682. SetSkinBg(TBIDC("TBDimmer"), WIDGET_INVOKE_INFO_NO_CALLBACKS);
  683. SetGravity(WIDGET_GRAVITY_ALL);
  684. }
  685. void TBDimmer::OnAdded()
  686. {
  687. SetRect(TBRect(0, 0, GetParent()->GetRect().w, GetParent()->GetRect().h));
  688. }
  689. }; // namespace tb