tb_widgets_common.cpp 23 KB

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