tb_widgets_common.cpp 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798
  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. }
  150. void TBButton::OnSkinChanged()
  151. {
  152. m_layout.SetRect(GetPaddingRect());
  153. }
  154. bool TBButton::OnEvent(const TBWidgetEvent &ev)
  155. {
  156. if (m_toggle_mode && ev.type == EVENT_TYPE_CLICK && ev.target == this)
  157. {
  158. TBWidgetSafePointer this_widget(this);
  159. SetValue(!GetValue());
  160. if (!this_widget.Get())
  161. return true; // We got removed so we actually handled this event.
  162. // Invoke a changed event.
  163. TBWidgetEvent ev(EVENT_TYPE_CHANGED);
  164. InvokeEvent(ev);
  165. if (!this_widget.Get())
  166. return true; // We got removed so we actually handled this event.
  167. // Intentionally don't return true for this event. We want it to continue propagating.
  168. }
  169. return TBWidget::OnEvent(ev);
  170. }
  171. void TBButton::OnMessageReceived(TBMessage *msg)
  172. {
  173. if (msg->message == TBIDC("auto_click"))
  174. {
  175. assert(captured_widget == this);
  176. if (!cancel_click && GetHitStatus(pointer_move_widget_x, pointer_move_widget_y))
  177. {
  178. TBWidgetEvent ev(EVENT_TYPE_CLICK, pointer_move_widget_x, pointer_move_widget_y, true);
  179. captured_widget->InvokeEvent(ev);
  180. }
  181. if (auto_click_repeat_delay)
  182. PostMessageDelayed(TBIDC("auto_click"), nullptr, auto_click_repeat_delay);
  183. }
  184. }
  185. WIDGET_HIT_STATUS TBButton::GetHitStatus(int x, int y)
  186. {
  187. // Never hit any of the children to the button. We always want to the button itself.
  188. return TBWidget::GetHitStatus(x, y) ? WIDGET_HIT_STATUS_HIT_NO_CHILDREN : WIDGET_HIT_STATUS_NO_HIT;
  189. }
  190. void TBButton::UpdateTextFieldVisibility()
  191. {
  192. // Auto-collapse the textfield if the text is empty and there are other
  193. // widgets added apart from the textfield. This removes the extra spacing
  194. // added between the textfield and the other widget.
  195. bool collapse_textfield = m_textfield.IsEmpty() && m_layout.GetFirstChild() != m_layout.GetLastChild();
  196. m_textfield.SetVisibilility(collapse_textfield ? WIDGET_VISIBILITY_GONE : WIDGET_VISIBILITY_VISIBLE);
  197. }
  198. void TBButton::ButtonLayout::OnChildAdded(TBWidget *child)
  199. {
  200. static_cast<TBButton*>(GetParent())->UpdateTextFieldVisibility();
  201. }
  202. void TBButton::ButtonLayout::OnChildRemove(TBWidget *child)
  203. {
  204. static_cast<TBButton*>(GetParent())->UpdateTextFieldVisibility();
  205. }
  206. // == TBClickLabel ==========================================================================================
  207. TBClickLabel::TBClickLabel()
  208. {
  209. AddChild(&m_layout);
  210. m_layout.AddChild(&m_textfield);
  211. m_layout.SetRect(GetPaddingRect());
  212. m_layout.SetGravity(WIDGET_GRAVITY_ALL);
  213. m_layout.SetLayoutDistributionPosition(LAYOUT_DISTRIBUTION_POSITION_LEFT_TOP);
  214. }
  215. TBClickLabel::~TBClickLabel()
  216. {
  217. m_layout.RemoveChild(&m_textfield);
  218. RemoveChild(&m_layout);
  219. }
  220. bool TBClickLabel::OnEvent(const TBWidgetEvent &ev)
  221. {
  222. // Get a widget from the layout that isn't the textfield, or just bail out
  223. // if we only have the textfield.
  224. if (m_layout.GetFirstChild() == m_layout.GetLastChild())
  225. return false;
  226. TBWidget *click_target = (m_layout.GetFirstChild() == &m_textfield ? m_layout.GetLastChild() : m_layout.GetFirstChild());
  227. // Invoke the event on it, as if it was invoked on the target itself.
  228. if (click_target && ev.target != click_target)
  229. {
  230. // Focus the target if we clicked the label.
  231. if (ev.type == EVENT_TYPE_CLICK)
  232. click_target->SetFocus(WIDGET_FOCUS_REASON_POINTER);
  233. // Sync our pressed state with the click target. Special case for when we're just about to
  234. // lose it ourself (pointer is being released).
  235. bool pressed_state = (ev.target->GetAutoState() & WIDGET_STATE_PRESSED) ? true : false;
  236. if (ev.type == EVENT_TYPE_POINTER_UP || ev.type == EVENT_TYPE_CLICK)
  237. pressed_state = false;
  238. click_target->SetState(WIDGET_STATE_PRESSED, pressed_state);
  239. TBWidgetEvent target_ev(ev.type, ev.target_x - click_target->GetRect().x, ev.target_y - click_target->GetRect().y,
  240. ev.touch, ev.modifierkeys);
  241. return click_target->InvokeEvent(target_ev);
  242. }
  243. return false;
  244. }
  245. // == TBSkinImage =======================================
  246. PreferredSize TBSkinImage::OnCalculatePreferredSize(const SizeConstraints &constraints)
  247. {
  248. PreferredSize ps = TBWidget::OnCalculatePreferredSize(constraints);
  249. // FIX: Make it stretched proportionally if shrunk.
  250. ps.max_w = ps.pref_w;
  251. ps.max_h = ps.pref_h;
  252. return ps;
  253. }
  254. // == TBSeparator ===========================================
  255. TBSeparator::TBSeparator()
  256. {
  257. SetSkinBg(TBIDC("TBSeparator"), WIDGET_INVOKE_INFO_NO_CALLBACKS);
  258. SetState(WIDGET_STATE_DISABLED, true);
  259. }
  260. // == TBProgressSpinner =====================================
  261. // FIX: Add spin_speed to skin!
  262. // FIX: Make it post messages only if visible
  263. const int spin_speed = 1000/30; ///< How fast should the spinner animation animate.
  264. TBProgressSpinner::TBProgressSpinner()
  265. : m_value(0)
  266. , m_frame(0)
  267. {
  268. SetSkinBg(TBIDC("TBProgressSpinner"), WIDGET_INVOKE_INFO_NO_CALLBACKS);
  269. m_skin_fg.Set(TBIDC("TBProgressSpinner.fg"));
  270. }
  271. void TBProgressSpinner::SetValue(int value)
  272. {
  273. if (value == m_value)
  274. return;
  275. InvalidateSkinStates();
  276. assert(value >= 0); // If this happens, you probably have unballanced Begin/End calls.
  277. m_value = value;
  278. if (value > 0)
  279. {
  280. // Start animation
  281. if (!GetMessageByID(TBID(1)))
  282. {
  283. m_frame = 0;
  284. PostMessageDelayed(TBID(1), nullptr, spin_speed);
  285. }
  286. }
  287. else
  288. {
  289. // Stop animation
  290. if (TBMessage *msg = GetMessageByID(TBID(1)))
  291. DeleteMessage(msg);
  292. }
  293. }
  294. void TBProgressSpinner::OnPaint(const PaintProps &paint_props)
  295. {
  296. if (IsRunning())
  297. {
  298. TBSkinElement *e = g_tb_skin->GetSkinElement(m_skin_fg);
  299. if (e && e->bitmap)
  300. {
  301. int size = e->bitmap->Height();
  302. int num_frames = e->bitmap->Width() / e->bitmap->Height();
  303. int current_frame = m_frame % num_frames;
  304. g_renderer->DrawBitmap(GetPaddingRect(), TBRect(current_frame * size, 0, size, size), e->bitmap);
  305. }
  306. }
  307. }
  308. void TBProgressSpinner::OnMessageReceived(TBMessage *msg)
  309. {
  310. m_frame++;
  311. Invalidate();
  312. // Keep animation running
  313. PostMessageDelayed(TBID(1), nullptr, spin_speed);
  314. }
  315. // == TBRadioCheckBox =======================================
  316. TBRadioCheckBox::TBRadioCheckBox()
  317. : m_value(0)
  318. {
  319. SetIsFocusable(true);
  320. SetClickByKey(true);
  321. }
  322. //static
  323. void TBRadioCheckBox::ToggleGroup(TBWidget *root, TBWidget *toggled)
  324. {
  325. if (root != toggled && root->GetGroupID() == toggled->GetGroupID())
  326. root->SetValue(0);
  327. for (TBWidget *child = root->GetFirstChild(); child; child = child->GetNext())
  328. ToggleGroup(child, toggled);
  329. }
  330. void TBRadioCheckBox::SetValue(int value)
  331. {
  332. if (m_value == value)
  333. return;
  334. m_value = value;
  335. SetState(WIDGET_STATE_SELECTED, value ? true : false);
  336. Invalidate();
  337. TBWidgetEvent ev(EVENT_TYPE_CHANGED);
  338. InvokeEvent(ev);
  339. if (!value || !GetGroupID())
  340. return;
  341. // Toggle all other widgets in the same group. First get a root widget
  342. // for the search.
  343. TBWidget *group = this;
  344. while (group && !group->GetIsGroupRoot())
  345. group = group->GetParent();
  346. if (group)
  347. {
  348. ToggleGroup(group, this);
  349. }
  350. }
  351. PreferredSize TBRadioCheckBox::OnCalculatePreferredSize(const SizeConstraints &constraints)
  352. {
  353. PreferredSize ps = TBWidget::OnCalculatePreferredSize(constraints);
  354. ps.min_w = ps.max_w = ps.pref_w;
  355. ps.min_h = ps.max_h = ps.pref_h;
  356. return ps;
  357. }
  358. bool TBRadioCheckBox::OnEvent(const TBWidgetEvent &ev)
  359. {
  360. if (ev.target == this && ev.type == EVENT_TYPE_CLICK)
  361. {
  362. // Toggle the value, if it's not a grouped widget with value on.
  363. if (!(GetGroupID() && GetValue()))
  364. {
  365. SetValue(!GetValue());
  366. }
  367. }
  368. return TBWidget::OnEvent(ev);
  369. }
  370. // == TBScrollBar =======================================
  371. TBScrollBar::TBScrollBar()
  372. : m_axis(AXIS_Y) ///< Make SetAxis below always succeed and set the skin
  373. , m_value(0)
  374. , m_min(0)
  375. , m_max(1)
  376. , m_visible(1)
  377. , m_to_pixel_factor(0)
  378. {
  379. SetAxis(AXIS_X);
  380. AddChild(&m_handle);
  381. }
  382. TBScrollBar::~TBScrollBar()
  383. {
  384. RemoveChild(&m_handle);
  385. }
  386. void TBScrollBar::SetAxis(AXIS axis)
  387. {
  388. if (axis == m_axis)
  389. return;
  390. m_axis = axis;
  391. if (axis == AXIS_X)
  392. {
  393. SetSkinBg(TBIDC("TBScrollBarBgX"), WIDGET_INVOKE_INFO_NO_CALLBACKS);
  394. m_handle.SetSkinBg(TBIDC("TBScrollBarFgX"), WIDGET_INVOKE_INFO_NO_CALLBACKS);
  395. }
  396. else
  397. {
  398. SetSkinBg(TBIDC("TBScrollBarBgY"), WIDGET_INVOKE_INFO_NO_CALLBACKS);
  399. m_handle.SetSkinBg(TBIDC("TBScrollBarFgY"), WIDGET_INVOKE_INFO_NO_CALLBACKS);
  400. }
  401. Invalidate();
  402. }
  403. void TBScrollBar::SetLimits(double min, double max, double visible)
  404. {
  405. max = MAX(min, max);
  406. visible = MAX(visible, 0.0);
  407. if (min == m_min && max == m_max && m_visible == visible)
  408. return;
  409. m_min = min;
  410. m_max = max;
  411. m_visible = visible;
  412. SetValueDouble(m_value);
  413. // If we're currently dragging the scrollbar handle, convert the down point
  414. // to root and then back after the applying the new limit.
  415. // This prevents sudden jumps to unexpected positions when scrolling.
  416. if (captured_widget == &m_handle)
  417. m_handle.ConvertToRoot(pointer_down_widget_x, pointer_down_widget_y);
  418. UpdateHandle();
  419. if (captured_widget == &m_handle)
  420. m_handle.ConvertFromRoot(pointer_down_widget_x, pointer_down_widget_y);
  421. }
  422. void TBScrollBar::SetValueDouble(double value)
  423. {
  424. value = CLAMP(value, m_min, m_max);
  425. if (value == m_value)
  426. return;
  427. m_value = value;
  428. UpdateHandle();
  429. TBWidgetEvent ev(EVENT_TYPE_CHANGED);
  430. InvokeEvent(ev);
  431. }
  432. bool TBScrollBar::OnEvent(const TBWidgetEvent &ev)
  433. {
  434. if (ev.type == EVENT_TYPE_POINTER_MOVE && captured_widget == &m_handle)
  435. {
  436. if (m_to_pixel_factor > 0)
  437. {
  438. int dx = ev.target_x - pointer_down_widget_x;
  439. int dy = ev.target_y - pointer_down_widget_y;
  440. double delta_val = (m_axis == AXIS_X ? dx : dy) / m_to_pixel_factor;
  441. SetValueDouble(m_value + delta_val);
  442. }
  443. return true;
  444. }
  445. else if (ev.type == EVENT_TYPE_POINTER_MOVE && ev.target == this)
  446. return true;
  447. else if (ev.type == EVENT_TYPE_POINTER_DOWN && ev.target == this)
  448. {
  449. bool after_handle = (m_axis == AXIS_X ? ev.target_x > m_handle.GetRect().x : ev.target_y > m_handle.GetRect().y);
  450. SetValueDouble(m_value + (after_handle ? m_visible : -m_visible));
  451. return true;
  452. }
  453. else if (ev.type == EVENT_TYPE_WHEEL)
  454. {
  455. double old_val = m_value;
  456. SetValueDouble(m_value + ev.delta_y * TBSystem::GetPixelsPerLine());
  457. return m_value != old_val;
  458. }
  459. return false;
  460. }
  461. void TBScrollBar::UpdateHandle()
  462. {
  463. // Calculate the mover size and position
  464. bool horizontal = m_axis == AXIS_X;
  465. int available_pixels = horizontal ? GetRect().w : GetRect().h;
  466. int min_thickness_pixels = MIN(GetRect().h, GetRect().w);
  467. int visible_pixels = available_pixels;
  468. if (m_max - m_min > 0 && m_visible > 0)
  469. {
  470. double visible_proportion = m_visible / (m_visible + m_max - m_min);
  471. visible_pixels = (int)(visible_proportion * available_pixels);
  472. // Limit the size of the indicator to the slider thickness so that it doesn't
  473. // become too tiny when the visible proportion is very small.
  474. visible_pixels = MAX(visible_pixels, min_thickness_pixels);
  475. m_to_pixel_factor = (double)(available_pixels - visible_pixels) / (m_max - m_min)/*+ 0.5*/;
  476. }
  477. else
  478. {
  479. m_to_pixel_factor = 0;
  480. // If we can't scroll anything, make the handle invisible
  481. visible_pixels = 0;
  482. }
  483. int pixel_pos = (int)(m_value * m_to_pixel_factor);
  484. TBRect rect;
  485. if (horizontal)
  486. rect.Set(pixel_pos, 0, visible_pixels, GetRect().h);
  487. else
  488. rect.Set(0, pixel_pos, GetRect().w, visible_pixels);
  489. m_handle.SetRect(rect);
  490. }
  491. void TBScrollBar::OnResized(int old_w, int old_h)
  492. {
  493. UpdateHandle();
  494. }
  495. // == TBSlider ============================================
  496. TBSlider::TBSlider()
  497. : m_axis(AXIS_Y) ///< Make SetAxis below always succeed and set the skin
  498. , m_value(0)
  499. , m_min(0)
  500. , m_max(1)
  501. , m_to_pixel_factor(0)
  502. {
  503. SetIsFocusable(true);
  504. SetAxis(AXIS_X);
  505. AddChild(&m_handle);
  506. }
  507. TBSlider::~TBSlider()
  508. {
  509. RemoveChild(&m_handle);
  510. }
  511. void TBSlider::SetAxis(AXIS axis)
  512. {
  513. if (axis == m_axis)
  514. return;
  515. m_axis = axis;
  516. if (axis == AXIS_X)
  517. {
  518. SetSkinBg(TBIDC("TBSliderBgX"), WIDGET_INVOKE_INFO_NO_CALLBACKS);
  519. m_handle.SetSkinBg(TBIDC("TBSliderFgX"), WIDGET_INVOKE_INFO_NO_CALLBACKS);
  520. }
  521. else
  522. {
  523. SetSkinBg(TBIDC("TBSliderBgY"), WIDGET_INVOKE_INFO_NO_CALLBACKS);
  524. m_handle.SetSkinBg(TBIDC("TBSliderFgY"), WIDGET_INVOKE_INFO_NO_CALLBACKS);
  525. }
  526. Invalidate();
  527. }
  528. void TBSlider::SetLimits(double min, double max)
  529. {
  530. min = MIN(min, max);
  531. if (min == m_min && max == m_max)
  532. return;
  533. m_min = min;
  534. m_max = max;
  535. SetValueDouble(m_value);
  536. UpdateHandle();
  537. }
  538. void TBSlider::SetValueDouble(double value)
  539. {
  540. value = CLAMP(value, m_min, m_max);
  541. if (value == m_value)
  542. return;
  543. m_value = value;
  544. UpdateHandle();
  545. TBWidgetEvent ev(EVENT_TYPE_CHANGED);
  546. InvokeEvent(ev);
  547. }
  548. bool TBSlider::OnEvent(const TBWidgetEvent &ev)
  549. {
  550. if (ev.type == EVENT_TYPE_POINTER_MOVE && captured_widget == &m_handle)
  551. {
  552. if (m_to_pixel_factor > 0)
  553. {
  554. int dx = ev.target_x - pointer_down_widget_x;
  555. int dy = ev.target_y - pointer_down_widget_y;
  556. double delta_val = (m_axis == AXIS_X ? dx : -dy) / m_to_pixel_factor;
  557. SetValueDouble(m_value + delta_val);
  558. }
  559. return true;
  560. }
  561. else if (ev.type == EVENT_TYPE_WHEEL)
  562. {
  563. double old_val = m_value;
  564. double step = (m_axis == AXIS_X ? GetSmallStep() : -GetSmallStep());
  565. SetValueDouble(m_value + step * ev.delta_y);
  566. return m_value != old_val;
  567. }
  568. else if (ev.type == EVENT_TYPE_KEY_DOWN)
  569. {
  570. double step = (m_axis == AXIS_X ? GetSmallStep() : -GetSmallStep());
  571. if (ev.special_key == TB_KEY_LEFT || ev.special_key == TB_KEY_UP)
  572. SetValueDouble(GetValueDouble() - step);
  573. else if (ev.special_key == TB_KEY_RIGHT || ev.special_key == TB_KEY_DOWN)
  574. SetValueDouble(GetValueDouble() + step);
  575. else
  576. return false;
  577. return true;
  578. }
  579. else if (ev.type == EVENT_TYPE_KEY_UP)
  580. {
  581. if (ev.special_key == TB_KEY_LEFT || ev.special_key == TB_KEY_UP ||
  582. ev.special_key == TB_KEY_RIGHT || ev.special_key == TB_KEY_DOWN)
  583. return true;
  584. }
  585. return false;
  586. }
  587. void TBSlider::UpdateHandle()
  588. {
  589. // Calculate the handle position
  590. bool horizontal = m_axis == AXIS_X;
  591. int available_pixels = horizontal ? GetRect().w : GetRect().h;
  592. TBRect rect;
  593. if (m_max - m_min > 0)
  594. {
  595. PreferredSize ps = m_handle.GetPreferredSize();
  596. int handle_pixels = horizontal ? ps.pref_w : ps.pref_h;
  597. m_to_pixel_factor = (double)(available_pixels - handle_pixels) / (m_max - m_min)/*+ 0.5*/;
  598. int pixel_pos = (int)((m_value - m_min) * m_to_pixel_factor);
  599. if (horizontal)
  600. rect.Set(pixel_pos, (GetRect().h - ps.pref_h) / 2, ps.pref_w, ps.pref_h);
  601. else
  602. rect.Set((GetRect().w - ps.pref_w) / 2, GetRect().h - handle_pixels - pixel_pos, ps.pref_w, ps.pref_h);
  603. }
  604. else
  605. m_to_pixel_factor = 0;
  606. m_handle.SetRect(rect);
  607. }
  608. void TBSlider::OnResized(int old_w, int old_h)
  609. {
  610. UpdateHandle();
  611. }
  612. // == TBContainer ===================================
  613. TBContainer::TBContainer()
  614. {
  615. SetSkinBg(TBIDC("TBContainer"), WIDGET_INVOKE_INFO_NO_CALLBACKS);
  616. }
  617. // == TBMover =======================================
  618. TBMover::TBMover()
  619. {
  620. SetSkinBg(TBIDC("TBMover"), WIDGET_INVOKE_INFO_NO_CALLBACKS);
  621. }
  622. bool TBMover::OnEvent(const TBWidgetEvent &ev)
  623. {
  624. TBWidget *target = GetParent();
  625. if (!target)
  626. return false;
  627. if (ev.type == EVENT_TYPE_POINTER_MOVE && captured_widget == this)
  628. {
  629. int dx = ev.target_x - pointer_down_widget_x;
  630. int dy = ev.target_y - pointer_down_widget_y;
  631. TBRect rect = target->GetRect().Offset(dx, dy);
  632. if (target->GetParent())
  633. {
  634. // Apply limit.
  635. rect.x = CLAMP(rect.x, -pointer_down_widget_x, target->GetParent()->GetRect().w - pointer_down_widget_x);
  636. rect.y = CLAMP(rect.y, -pointer_down_widget_y, target->GetParent()->GetRect().h - pointer_down_widget_y);
  637. }
  638. target->SetRect(rect);
  639. return true;
  640. }
  641. return false;
  642. }
  643. // == TBResizer =======================================
  644. TBResizer::TBResizer()
  645. {
  646. SetSkinBg(TBIDC("TBResizer"), WIDGET_INVOKE_INFO_NO_CALLBACKS);
  647. }
  648. WIDGET_HIT_STATUS TBResizer::GetHitStatus(int x, int y)
  649. {
  650. // Shave off some of the upper left diagonal half from the hit area.
  651. const int extra_hit_area = 3;
  652. if (x < GetRect().w - y - extra_hit_area)
  653. return WIDGET_HIT_STATUS_NO_HIT;
  654. return TBWidget::GetHitStatus(x, y);
  655. }
  656. bool TBResizer::OnEvent(const TBWidgetEvent &ev)
  657. {
  658. TBWidget *target = GetParent();
  659. if (!target)
  660. return false;
  661. if (ev.type == EVENT_TYPE_POINTER_MOVE && captured_widget == this)
  662. {
  663. int dx = ev.target_x - pointer_down_widget_x;
  664. int dy = ev.target_y - pointer_down_widget_y;
  665. TBRect rect = target->GetRect();
  666. rect.w += dx;
  667. rect.h += dy;
  668. // Apply limit. We should not use minimum size since we can squeeze
  669. // the layout much more, and provide scroll/pan when smaller.
  670. rect.w = MAX(rect.w, 50);
  671. rect.h = MAX(rect.h, 50);
  672. target->SetRect(rect);
  673. }
  674. else
  675. return false;
  676. return true;
  677. }
  678. // == TBDimmer =======================================
  679. TBDimmer::TBDimmer()
  680. {
  681. SetSkinBg(TBIDC("TBDimmer"), WIDGET_INVOKE_INFO_NO_CALLBACKS);
  682. SetGravity(WIDGET_GRAVITY_ALL);
  683. }
  684. void TBDimmer::OnAdded()
  685. {
  686. SetRect(TBRect(0, 0, GetParent()->GetRect().w, GetParent()->GetRect().h));
  687. }
  688. }; // namespace tb