From 8c25e04e3986009072b53684ab2281cdc7f76280 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emil=20Seger=C3=A5s?= Date: Tue, 21 May 2013 19:00:38 +0200 Subject: [PATCH] Major improvements to pointer/touch input: List of changes: -Refactorized virtual scroll methods and introduced virtual GetScrollInfo. -Added TBScroller to do slow down scroll after panning. -Added TBScrollerSnapListener for snapped smooth scrolling. -Added keyboard scroll support in TBScrollContainer and TBSelectList. -Added possibility to cancel EVENT_TYPE_CLICK for current touch (stopping scroll should not cause click) -Added flag to TBWidgetEvent to distinguish cursor base pointer events from touch events (Fixes issue #2). --Pan TBEditField on touch pointer events instead of doing selection scroll. --Touch events should not result in setting the hover state automatically. --- Demo/VisualStudio/tinkerbell_static.vcxproj | 2 + .../tinkerbell_static.vcxproj.filters | 6 + Demo/demo01/Demo.h | 9 + Demo/demo01/Demo01.cpp | 28 ++ .../ui_resources/test_scroller_snap.tb.txt | 22 + Demo/demo01/ui_resources/test_ui.tb.txt | 1 + Demo/platform/port_glfw.cpp | 16 +- DemoAndroid/assets/layout/main_layout.tb.txt | 13 + DemoAndroid/jni/Android.mk | 1 + DemoAndroid/jni/jni_glue.cpp | 8 +- Makefile | 1 + tinkerbell/src/tb_editfield.cpp | 29 +- tinkerbell/src/tb_editfield.h | 6 +- tinkerbell/src/tb_inline_select.cpp | 2 +- tinkerbell/src/tb_layout.cpp | 36 +- tinkerbell/src/tb_layout.h | 4 +- tinkerbell/src/tb_scroll_container.cpp | 79 ++-- tinkerbell/src/tb_scroll_container.h | 8 +- tinkerbell/src/tb_scroller.cpp | 380 ++++++++++++++++++ tinkerbell/src/tb_scroller.h | 133 ++++++ tinkerbell/src/tb_select.cpp | 16 +- tinkerbell/src/tb_style_edit.cpp | 21 +- tinkerbell/src/tb_style_edit.h | 4 +- tinkerbell/src/tb_widgets.cpp | 200 +++++++-- tinkerbell/src/tb_widgets.h | 97 ++++- tinkerbell/src/tb_widgets_common.cpp | 15 +- 26 files changed, 988 insertions(+), 149 deletions(-) create mode 100644 Demo/demo01/ui_resources/test_scroller_snap.tb.txt create mode 100644 tinkerbell/src/tb_scroller.cpp create mode 100644 tinkerbell/src/tb_scroller.h diff --git a/Demo/VisualStudio/tinkerbell_static.vcxproj b/Demo/VisualStudio/tinkerbell_static.vcxproj index e1abcd9566..2058ed9ec9 100644 --- a/Demo/VisualStudio/tinkerbell_static.vcxproj +++ b/Demo/VisualStudio/tinkerbell_static.vcxproj @@ -35,6 +35,7 @@ + @@ -92,6 +93,7 @@ + diff --git a/Demo/VisualStudio/tinkerbell_static.vcxproj.filters b/Demo/VisualStudio/tinkerbell_static.vcxproj.filters index e5bdea374e..839cf4bcae 100644 --- a/Demo/VisualStudio/tinkerbell_static.vcxproj.filters +++ b/Demo/VisualStudio/tinkerbell_static.vcxproj.filters @@ -198,6 +198,9 @@ Source Files + + Source Files + @@ -338,6 +341,9 @@ Source Files + + Source Files + diff --git a/Demo/demo01/Demo.h b/Demo/demo01/Demo.h index 29e75eec09..076b50ec9c 100644 --- a/Demo/demo01/Demo.h +++ b/Demo/demo01/Demo.h @@ -7,6 +7,7 @@ #include "tb_widgets_listener.h" #include "tb_message_window.h" #include "tb_msg.h" +#include "tb_scroller.h" #include "platform/Application.h" using namespace tinkerbell; @@ -49,6 +50,14 @@ class ImageWindow : public DemoWindow virtual bool OnEvent(const TBWidgetEvent &ev); }; +class PageWindow : public DemoWindow, public TBScrollerSnapListener +{ +public: + PageWindow(); + virtual bool OnEvent(const TBWidgetEvent &ev); + virtual void OnScrollSnap(TBWidget *target_widget, int &target_x, int &target_y); +}; + class AnimationsWindow : public DemoWindow { public: diff --git a/Demo/demo01/Demo01.cpp b/Demo/demo01/Demo01.cpp index 638c1e60a6..4225555766 100644 --- a/Demo/demo01/Demo01.cpp +++ b/Demo/demo01/Demo01.cpp @@ -488,6 +488,29 @@ bool ImageWindow::OnEvent(const TBWidgetEvent &ev) return DemoWindow::OnEvent(ev); } +// == PageWindow ============================================================= + +PageWindow::PageWindow() +{ + LoadResourceFile("Demo/demo01/ui_resources/test_scroller_snap.tb.txt"); + + // Listen to the pagers scroller + if (TBWidget *pager = GetWidgetByID(TBIDC("page-scroller"))) + pager->GetScroller()->SetSnapListener(this); +} + +bool PageWindow::OnEvent(const TBWidgetEvent &ev) +{ + return DemoWindow::OnEvent(ev); +} + +void PageWindow::OnScrollSnap(TBWidget *target_widget, int &target_x, int &target_y) +{ + int page_w = target_widget->GetPaddingRect().w; + int target_page = (target_x + page_w / 2) / page_w; + target_x = target_page * page_w; +} + // == AnimationsWindow ======================================================== AnimationsWindow::AnimationsWindow() @@ -640,6 +663,11 @@ bool MainWindow::OnEvent(const TBWidgetEvent &ev) new ImageWindow(); return true; } + else if (ev.target->GetID() == TBIDC("test-page")) + { + new PageWindow(); + return true; + } else if (ev.target->GetID() == TBIDC("test-animations")) { new AnimationsWindow(); diff --git a/Demo/demo01/ui_resources/test_scroller_snap.tb.txt b/Demo/demo01/ui_resources/test_scroller_snap.tb.txt new file mode 100644 index 0000000000..31d0c0f0fc --- /dev/null +++ b/Demo/demo01/ui_resources/test_scroller_snap.tb.txt @@ -0,0 +1,22 @@ +WindowInfo + title TBScrollerSnapListener +TBScrollContainer: id: "page-scroller" + lp: width: 200, height: 300 + scroll-mode off + TBLayout + spacing 0 + TBContainer + lp: width: 200, height: 300 + TBTextField: text: "One - Swipe to next page" + TBContainer + lp: width: 200, height: 300 + TBTextField: text: "Two" + TBContainer + lp: width: 200, height: 300 + TBTextField: text: "Three" + TBContainer + lp: width: 200, height: 300 + TBTextField: text: "Four" + TBContainer + lp: width: 200, height: 300 + TBTextField: text: "Five - Last page" diff --git a/Demo/demo01/ui_resources/test_ui.tb.txt b/Demo/demo01/ui_resources/test_ui.tb.txt index f96ff47256..3c0f36c5ff 100644 --- a/Demo/demo01/ui_resources/test_ui.tb.txt +++ b/Demo/demo01/ui_resources/test_ui.tb.txt @@ -11,6 +11,7 @@ TBLayout: axis: y, distribution-position: left top TBButton: text: "ScrollContainer & misc.", id: "test-scroll-container" TBButton: text: "TBSelectList", id: "test-list" TBButton: text: "TBImage", id: "test-image" + TBButton: text: "TBScrollerSnapListener", id: "test-page" TBButton: text: "Animations", id: "test-animations" TBButton: text: "Skin conditions", id: "test-skin-conditions" TBButton: text: "TBToggleContainer", id: "test-toggle-container" diff --git a/Demo/platform/port_glfw.cpp b/Demo/platform/port_glfw.cpp index 76a55f447f..57451c8b07 100644 --- a/Demo/platform/port_glfw.cpp +++ b/Demo/platform/port_glfw.cpp @@ -107,7 +107,8 @@ static bool InvokeShortcut(int key, SPECIAL_KEY special_key, MODIFIER_KEYS modif else return false; - TBWidgetEvent ev(EVENT_TYPE_SHORTCUT, 0, 0, modifierkeys); + TBWidgetEvent ev(EVENT_TYPE_SHORTCUT); + ev.modifierkeys = modifierkeys; ev.ref_id = id; return TBWidget::focused_widget->InvokeEvent(ev); } @@ -161,7 +162,8 @@ static void key_callback(GLFWwindow window, int key, int action) case GLFW_KEY_MENU: if (TBWidget::focused_widget && !down) { - TBWidgetEvent ev(EVENT_TYPE_CONTEXT_MENU, 0, 0, GetModifierKeys()); + TBWidgetEvent ev(EVENT_TYPE_CONTEXT_MENU); + ev.modifierkeys = modifier; TBWidget::focused_widget->InvokeEvent(ev); } break; @@ -211,18 +213,18 @@ static void mouse_button_callback(GLFWwindow window, int button, int action) last_y = y; last_time = time; - g_backend->GetRoot()->InvokePointerDown(x, y, counter, GetModifierKeys()); + g_backend->GetRoot()->InvokePointerDown(x, y, counter, GetModifierKeys(), false); } else - g_backend->GetRoot()->InvokePointerUp(x, y, GetModifierKeys()); + g_backend->GetRoot()->InvokePointerUp(x, y, GetModifierKeys(), false); } else if (button == GLFW_MOUSE_BUTTON_RIGHT && action == GLFW_RELEASE) { - g_backend->GetRoot()->InvokePointerMove(x, y, GetModifierKeys()); + g_backend->GetRoot()->InvokePointerMove(x, y, GetModifierKeys(), false); if (TBWidget::hovered_widget) { TBWidget::hovered_widget->ConvertFromRoot(x, y); - TBWidgetEvent ev(EVENT_TYPE_CONTEXT_MENU, x, y, GetModifierKeys()); + TBWidgetEvent ev(EVENT_TYPE_CONTEXT_MENU, x, y, false, GetModifierKeys()); TBWidget::hovered_widget->InvokeEvent(ev); } } @@ -233,7 +235,7 @@ void cursor_position_callback(GLFWwindow window, int x, int y) mouse_x = x; mouse_y = y; if (g_backend->GetRoot()) - g_backend->GetRoot()->InvokePointerMove(x, y, GetModifierKeys()); + g_backend->GetRoot()->InvokePointerMove(x, y, GetModifierKeys(), false); } static void scroll_callback(GLFWwindow window, double x, double y) diff --git a/DemoAndroid/assets/layout/main_layout.tb.txt b/DemoAndroid/assets/layout/main_layout.tb.txt index f66e190ec7..02cba43b7a 100644 --- a/DemoAndroid/assets/layout/main_layout.tb.txt +++ b/DemoAndroid/assets/layout/main_layout.tb.txt @@ -65,6 +65,19 @@ TBLayout styling 1 text: "Android test stuff\n" \ "This is a read-only multiline textfield with styling enabled.\n" + TBEditField + multiline 1 + gravity all + text: "Row 1\n" \ + "Row 2\n" \ + "Row 3\n" \ + "Row 4\n" \ + "Row 5\n" \ + "Row 6\n" \ + "Row 7\n" \ + "Row 8\n" \ + "Row 9\n" \ + "Row 10" TBLayout: axis: y TBEditField: gravity: all, skin: 0, multiline: 1, readonly: 1, adapt-to-content: 1 text: "Some speed tests on the layout used in this demo:" diff --git a/DemoAndroid/jni/Android.mk b/DemoAndroid/jni/Android.mk index 04867480ac..eb5c93ccc3 100644 --- a/DemoAndroid/jni/Android.mk +++ b/DemoAndroid/jni/Android.mk @@ -27,6 +27,7 @@ LOCAL_SRC_FILES := jni_glue.cpp \ ../../tinkerbell/src/tb_msg.cpp \ ../../tinkerbell/src/tb_object.cpp \ ../../tinkerbell/src/tb_renderer.cpp \ + ../../tinkerbell/src/tb_scroller.cpp \ ../../tinkerbell/src/tb_scroll_container.cpp \ ../../tinkerbell/src/tb_select.cpp \ ../../tinkerbell/src/tb_select_item.cpp \ diff --git a/DemoAndroid/jni/jni_glue.cpp b/DemoAndroid/jni/jni_glue.cpp index bc328b7e83..89ff0b3004 100644 --- a/DemoAndroid/jni/jni_glue.cpp +++ b/DemoAndroid/jni/jni_glue.cpp @@ -185,11 +185,11 @@ JNI_VOID_TINKERBELL(OnPointer)(JNIEnv *env, jobject obj, jfloat x, jfloat y, jin set_jnienv(env); //TBDebugOut("OnPointer"); - int counter = 0; + int counter = 1; if (down) - root->InvokePointerDown(x, y, counter, TB_MODIFIER_NONE); + root->InvokePointerDown(x, y, counter, TB_MODIFIER_NONE, true); else - root->InvokePointerUp(x, y, TB_MODIFIER_NONE); + root->InvokePointerUp(x, y, TB_MODIFIER_NONE, true); } JNI_VOID_TINKERBELL(OnPointer2)(JNIEnv *env, jobject obj, jfloat x, jfloat y, jint down) @@ -203,5 +203,5 @@ JNI_VOID_TINKERBELL(OnPointerMove)(JNIEnv *env, jobject obj, jfloat x, jfloat y, set_jnienv(env); //TBDebugOut("OnPointerMove"); - root->InvokePointerMove(x, y, TB_MODIFIER_NONE); + root->InvokePointerMove(x, y, TB_MODIFIER_NONE, true); } diff --git a/Makefile b/Makefile index 57d9d134aa..f91fa22821 100644 --- a/Makefile +++ b/Makefile @@ -25,6 +25,7 @@ endif TARGET = RunDemo SRC = tinkerbell/src/tb_layout.cpp \ + tinkerbell/src/tb_scroller.cpp \ tinkerbell/src/tb_scroll_container.cpp \ tinkerbell/src/tb_skin.cpp \ tinkerbell/src/tb_skin_util.cpp \ diff --git a/tinkerbell/src/tb_editfield.cpp b/tinkerbell/src/tb_editfield.cpp index 9ad7e66711..110080bb9d 100644 --- a/tinkerbell/src/tb_editfield.cpp +++ b/tinkerbell/src/tb_editfield.cpp @@ -161,6 +161,28 @@ bool TBEditField::GetCustomSkinCondition(const TBSkinCondition::CONDITION_INFO & return false; } +void TBEditField::ScrollTo(int x, int y) +{ + int old_x = m_scrollbar_x.GetValue(); + int old_y = m_scrollbar_y.GetValue(); + m_style_edit.SetScrollPos(x, y); + if (old_x != m_scrollbar_x.GetValue() || + old_y != m_scrollbar_y.GetValue()) + TBWidget::Invalidate(); +} + +TBWidget::ScrollInfo TBEditField::GetScrollInfo() +{ + ScrollInfo info; + info.min_x = static_cast(m_scrollbar_x.GetMinValue()); + info.min_y = static_cast(m_scrollbar_y.GetMinValue()); + info.max_x = static_cast(m_scrollbar_x.GetMaxValue()); + info.max_y = static_cast(m_scrollbar_y.GetMaxValue()); + info.x = m_scrollbar_x.GetValue(); + info.y = m_scrollbar_y.GetValue(); + return info; +} + bool TBEditField::OnEvent(const TBWidgetEvent &ev) { if (ev.type == EVENT_TYPE_CHANGED && ev.target == &m_scrollbar_x) @@ -184,7 +206,7 @@ bool TBEditField::OnEvent(const TBWidgetEvent &ev) TBRect padding_rect = GetPaddingRect(); if (m_style_edit.MouseDown( TBPoint(ev.target_x - padding_rect.x, ev.target_y - padding_rect.y), - 1, ev.count, TB_MODIFIER_NONE)) + 1, ev.count, TB_MODIFIER_NONE, ev.touch)) { // Post a message to start selection scroll PostMessageDelayed(TBIDC("selscroll"), nullptr, SELECTION_SCROLL_DELAY); @@ -199,7 +221,8 @@ bool TBEditField::OnEvent(const TBWidgetEvent &ev) else if (ev.type == EVENT_TYPE_POINTER_UP && ev.target == this) { TBRect padding_rect = GetPaddingRect(); - return m_style_edit.MouseUp(TBPoint(ev.target_x - padding_rect.x, ev.target_y - padding_rect.y), 1, TB_MODIFIER_NONE); + return m_style_edit.MouseUp(TBPoint(ev.target_x - padding_rect.x, ev.target_y - padding_rect.y), + 1, TB_MODIFIER_NONE, ev.touch); } else if (ev.type == EVENT_TYPE_KEY_DOWN) { @@ -407,7 +430,7 @@ void TBEditField::OnChange() //FIX: some of theese in tinkerbell doesn't check if the widget is removed afterwards! // it's not unlikely that it might result in the widget to be removed. - TBWidgetEvent ev(EVENT_TYPE_CHANGED, 0, 0); + TBWidgetEvent ev(EVENT_TYPE_CHANGED); InvokeEvent(ev); } diff --git a/tinkerbell/src/tb_editfield.h b/tinkerbell/src/tb_editfield.h index e03aa06f25..450d85bfe6 100644 --- a/tinkerbell/src/tb_editfield.h +++ b/tinkerbell/src/tb_editfield.h @@ -65,7 +65,7 @@ class TBEditFieldScrollRoot : public TBWidget /** TBEditField is a one line or multi line textfield that is editable or read-only. It can also be a passwordfield by calling SetEditType(EDIT_TYPE_PASSWORD). - + It may perform styling of text and contain custom embedded content, if enabled by SetStyling(true). Disabled by default. */ @@ -152,6 +152,10 @@ class TBEditField : public TBWidget, private TBStyleEditListener, public TBMessa virtual bool SetPlaceholderText(const char *text) { return m_placeholder.SetText(text); } virtual bool GetPlaceholderText(TBStr &text) { return m_placeholder.GetText(text); } + virtual void ScrollTo(int x, int y); + virtual TBWidget::ScrollInfo GetScrollInfo(); + virtual TBWidget *GetScrollRoot() { return &m_root; } + virtual bool OnEvent(const TBWidgetEvent &ev); virtual void OnPaint(const PaintProps &paint_props); virtual void OnPaintChildren(const PaintProps &paint_props); diff --git a/tinkerbell/src/tb_inline_select.cpp b/tinkerbell/src/tb_inline_select.cpp index e9294337c5..833bbcd5ad 100644 --- a/tinkerbell/src/tb_inline_select.cpp +++ b/tinkerbell/src/tb_inline_select.cpp @@ -72,7 +72,7 @@ void TBInlineSelect::SetValueInternal(int value, bool update_text) m_editfield.SetText(strval); } - TBWidgetEvent ev(EVENT_TYPE_CHANGED, 0, 0); + TBWidgetEvent ev(EVENT_TYPE_CHANGED); InvokeEvent(ev); // Warning: Do nothing here since the event might have deleted us. diff --git a/tinkerbell/src/tb_layout.cpp b/tinkerbell/src/tb_layout.cpp index a206a48277..36f13185b9 100644 --- a/tinkerbell/src/tb_layout.cpp +++ b/tinkerbell/src/tb_layout.cpp @@ -483,35 +483,25 @@ void TBLayout::GetChildTranslation(int &x, int &y) const } } -void TBLayout::ScrollIntoView(const TBRect &rect) +void TBLayout::ScrollTo(int x, int y) { - TBRect m_rect = GetRect(); - TBRect visible_rect = m_axis == AXIS_X ? TBRect(m_overflow_scroll, 0, m_rect.w, m_rect.h) : - TBRect(0, m_overflow_scroll, m_rect.w, m_rect.h); - int new_x = visible_rect.x; - int new_y = visible_rect.y; - - if (rect.y <= visible_rect.y) - new_y = rect.y; - else if (rect.y + rect.h > visible_rect.y + visible_rect.h) - new_y = rect.y + rect.h - visible_rect.h; - - if (rect.x <= visible_rect.x) - new_x = rect.x; - else if (rect.x + rect.w > visible_rect.x + visible_rect.w) - new_x = rect.x + rect.w - visible_rect.w; - - SetOverflowScroll(m_axis == AXIS_X ? new_x : new_y); + SetOverflowScroll(m_axis == AXIS_X ? x : y); } -void TBLayout::ScrollBy(int &dx, int &dy) +TBWidget::ScrollInfo TBLayout::GetScrollInfo() { - int old_overflow_scroll = m_overflow_scroll; - SetOverflowScroll(m_axis == AXIS_X ? m_overflow_scroll + dx : m_overflow_scroll + dy); + ScrollInfo info; if (m_axis == AXIS_X) - dx -= m_overflow_scroll - old_overflow_scroll; + { + info.max_x = m_overflow; + info.x = m_overflow_scroll; + } else - dy -= m_overflow_scroll - old_overflow_scroll; + { + info.max_y = m_overflow; + info.y = m_overflow_scroll; + } + return info; } }; // namespace tinkerbell diff --git a/tinkerbell/src/tb_layout.h b/tinkerbell/src/tb_layout.h index ba2ac625b8..c27e7548f0 100644 --- a/tinkerbell/src/tb_layout.h +++ b/tinkerbell/src/tb_layout.h @@ -129,8 +129,8 @@ class TBLayout : public TBWidget virtual void OnResized(int old_w, int old_h); virtual void OnInflateChild(TBWidget *child); virtual void GetChildTranslation(int &x, int &y) const; - virtual void ScrollIntoView(const TBRect &rect); - virtual void ScrollBy(int &dx, int &dy); + virtual void ScrollTo(int x, int y); + virtual TBWidget::ScrollInfo GetScrollInfo(); protected: AXIS m_axis; int m_spacing; diff --git a/tinkerbell/src/tb_scroll_container.cpp b/tinkerbell/src/tb_scroll_container.cpp index f7b6afe818..674bf5d0b5 100644 --- a/tinkerbell/src/tb_scroll_container.cpp +++ b/tinkerbell/src/tb_scroll_container.cpp @@ -129,17 +129,6 @@ void TBScrollContainer::SetScrollMode(SCROLL_MODE mode) InvalidateLayout(INVALIDATE_LAYOUT_TARGET_ONLY); } -TBRect TBScrollContainer::GetVisibleRect() -{ - int visible_w = GetRect().w; - int visible_h = GetRect().h; - if (m_scrollbar_x.GetOpacity()) - visible_h -= m_scrollbar_x.GetPreferredSize().pref_h; - if (m_scrollbar_y.GetOpacity()) - visible_w -= m_scrollbar_y.GetPreferredSize().pref_w; - return TBRect(m_scrollbar_x.GetValue(), m_scrollbar_y.GetValue(), visible_w, visible_h); -} - void TBScrollContainer::ScrollTo(int x, int y) { int old_x = m_scrollbar_x.GetValue(); @@ -151,33 +140,16 @@ void TBScrollContainer::ScrollTo(int x, int y) Invalidate(); } -void TBScrollContainer::ScrollIntoView(const TBRect &rect) -{ - TBRect visible_rect = GetVisibleRect(); - int new_x = m_scrollbar_x.GetValue(); - int new_y = m_scrollbar_y.GetValue(); - - if (rect.y <= visible_rect.y) - new_y = rect.y; - else if (rect.y + rect.h > visible_rect.y + visible_rect.h) - new_y = rect.y + rect.h - visible_rect.h; - - if (rect.x <= visible_rect.x) - new_x = rect.x; - else if (rect.x + rect.w > visible_rect.x + visible_rect.w) - new_x = rect.x + rect.w - visible_rect.w; - - ScrollTo(new_x, new_y); -} - -void TBScrollContainer::ScrollBy(int &dx, int &dy) +TBWidget::ScrollInfo TBScrollContainer::GetScrollInfo() { - int old_x = m_scrollbar_x.GetValue(); - int old_y = m_scrollbar_y.GetValue(); - ScrollTo(m_scrollbar_x.GetValue() + dx, - m_scrollbar_y.GetValue() + dy); - dx -= m_scrollbar_x.GetValue() - old_x; - dy -= m_scrollbar_y.GetValue() - old_y; + ScrollInfo info; + info.min_x = static_cast(m_scrollbar_x.GetMinValue()); + info.min_y = static_cast(m_scrollbar_y.GetMinValue()); + info.max_x = static_cast(m_scrollbar_x.GetMaxValue()); + info.max_y = static_cast(m_scrollbar_y.GetMaxValue()); + info.x = m_scrollbar_x.GetValue(); + info.y = m_scrollbar_y.GetValue(); + return info; } void TBScrollContainer::InvalidateLayout(INVALIDATE_LAYOUT il) @@ -186,6 +158,17 @@ void TBScrollContainer::InvalidateLayout(INVALIDATE_LAYOUT il) // No recursion up to parents here. } +TBRect TBScrollContainer::GetPaddingRect() +{ + int visible_w = GetRect().w; + int visible_h = GetRect().h; + if (m_scrollbar_x.GetOpacity()) + visible_h -= m_scrollbar_x.GetPreferredSize().pref_h; + if (m_scrollbar_y.GetOpacity()) + visible_w -= m_scrollbar_y.GetPreferredSize().pref_w; + return TBRect(0, 0, visible_w, visible_h); +} + PreferredSize TBScrollContainer::OnCalculatePreferredContentSize() { PreferredSize ps; @@ -226,6 +209,28 @@ bool TBScrollContainer::OnEvent(const TBWidgetEvent &ev) m_scrollbar_y.SetValueDouble(old_val + ev.delta_y * TBSystem::GetPixelsPerLine()); return m_scrollbar_y.GetValueDouble() != old_val; } + else if (ev.type == EVENT_TYPE_KEY_DOWN) + { + if (ev.special_key == TB_KEY_LEFT && m_scrollbar_x.CanScrollNegative()) + ScrollBySmooth(-TBSystem::GetPixelsPerLine(), 0); + else if (ev.special_key == TB_KEY_RIGHT && m_scrollbar_x.CanScrollPositive()) + ScrollBySmooth(TBSystem::GetPixelsPerLine(), 0); + else if (ev.special_key == TB_KEY_UP && m_scrollbar_y.CanScrollNegative()) + ScrollBySmooth(0, -TBSystem::GetPixelsPerLine()); + else if (ev.special_key == TB_KEY_DOWN && m_scrollbar_y.CanScrollPositive()) + ScrollBySmooth(0, TBSystem::GetPixelsPerLine()); + else if (ev.special_key == TB_KEY_PAGE_UP && m_scrollbar_y.CanScrollNegative()) + ScrollBySmooth(0, -GetPaddingRect().h); + else if (ev.special_key == TB_KEY_PAGE_DOWN && m_scrollbar_y.CanScrollPositive()) + ScrollBySmooth(0, GetPaddingRect().h); + else if (ev.special_key == TB_KEY_HOME) + ScrollToSmooth(m_scrollbar_x.GetValue(), 0); + else if (ev.special_key == TB_KEY_END) + ScrollToSmooth(m_scrollbar_x.GetValue(), (int)m_scrollbar_y.GetMaxValue()); + else + return false; + return true; + } return false; } diff --git a/tinkerbell/src/tb_scroll_container.h b/tinkerbell/src/tb_scroll_container.h index f84d2c5e58..b1ea1bc69e 100644 --- a/tinkerbell/src/tb_scroll_container.h +++ b/tinkerbell/src/tb_scroll_container.h @@ -70,13 +70,13 @@ friend class TBScrollContainerRoot; void SetScrollMode(SCROLL_MODE mode); SCROLL_MODE GetScrollMode() { return m_mode; } - TBRect GetVisibleRect(); - void ScrollTo(int x, int y); - virtual void ScrollIntoView(const TBRect &rect); - virtual void ScrollBy(int &dx, int &dy); + virtual void ScrollTo(int x, int y); + virtual TBWidget::ScrollInfo GetScrollInfo(); + virtual TBWidget *GetScrollRoot() { return &m_root; } virtual void InvalidateLayout(INVALIDATE_LAYOUT il); + virtual TBRect GetPaddingRect(); virtual PreferredSize OnCalculatePreferredContentSize(); virtual bool OnEvent(const TBWidgetEvent &ev); diff --git a/tinkerbell/src/tb_scroller.cpp b/tinkerbell/src/tb_scroller.cpp new file mode 100644 index 0000000000..4f9f04ac09 --- /dev/null +++ b/tinkerbell/src/tb_scroller.cpp @@ -0,0 +1,380 @@ +// ================================================================================ +// == This file is a part of TinkerBell UI Toolkit. (C) 2011-2013, Emil Segerås == +// == See tinkerbell.h for more information. == +// ================================================================================ + +#include "tb_scroller.h" +#include "tb_widgets.h" +#include "tb_system.h" +#include + +namespace tinkerbell { + +// == Misc constants ==================================================================== + +#define PAN_TARGET_FPS 60 +#define PAN_MSG_DELAY_MS ((double)(1000.0 / PAN_TARGET_FPS)) + +#define PAN_START_THRESHOLD_MS 50 +#define PAN_POWER_ACC_THRESHOLD_MS 600 + +#define PAN_POWER_MULTIPLIER 1.3f + +#define SCROLL_DECAY 200.0f + +#define SF_GATE_THRESHOLD 0.01f + +// == TBScrollerFunction ================================================================ + +// Lab: http://www.madtealab.com/?V=1&C=6&F=5&G=1&O=1&W=774&GW=720&GH=252&GX=13.389616776278201&GY=4.790704772336853&GS=0.13102127484993598&EH=189&a=3.6666666666666665&aMa=20&aN=OrgSpeed&bMa=3&bN=CurPos&c=8&cMa=60&cI=1&cN=FrameRate&d=16&dMa=16&dI=1&dN=numSimulatedSeconds&l=2.388888888888889&lMa=5&lN=Decay&m=0.1&mMa=0.1&mN=GateThreshold&f1=OrgSpeed+%2A+exp%28-x+%2F+Decay%29&f1N=Speed&f2=CurPos+%2B+OrgSpeed+%2A+%281-exp%28-x+%2F+Decay%29%29%2A+Decay&f2N=Pos&f3=marker%28x%2C+predictGatedPoint%29&f3N=GatePoint&f4=aToF%28simulatedPoints%2Cnearest%2C0%2CnumSimulatedSeconds%29%28x%29&f4N=Iterated&f5=OrgSpeed+%2A+x&f5N=Linear1&Expr=%0ApredictGatedPoint+%3D+-log%28GateThreshold+%2F+%28OrgSpeed%29%29+%2A+Decay%0A%0Avar+cur+%3D+OrgSpeed%0AsimulatedPoints+%3D+sample%28function%28%29+%7B%0A+++cur+%3D+cur+%2A+%281+-+0.05%29%3B%0A+++return+cur%0A+%7D%2C+%5BnumSimulatedSeconds+%2A+FrameRate%5D%29%3B%0A%0ApredictGatedPoint + +float TBScrollerFunction::GetDurationFromSpeed(float start_speed) +{ + float abs_start_speed = ABS(start_speed); + if (abs_start_speed <= SF_GATE_THRESHOLD) + return 0; + return -log(SF_GATE_THRESHOLD / abs_start_speed) * m_decay; +} + +float TBScrollerFunction::GetSpeedFromDistance(float distance) +{ + float speed = distance / m_decay; + if (distance > SF_GATE_THRESHOLD) + return speed + SF_GATE_THRESHOLD; + else if (distance < -SF_GATE_THRESHOLD) + return speed - SF_GATE_THRESHOLD; + return speed; +} + +float TBScrollerFunction::GetDistanceAtTime(float start_speed, float elapsed_time_ms) +{ + assert(elapsed_time_ms >= 0); + return start_speed * (1 - exp(-elapsed_time_ms / m_decay)) * m_decay; +} + +int TBScrollerFunction::GetDistanceAtTimeInt(float start_speed, float elapsed_time_ms) +{ + float distance = GetDistanceAtTime(start_speed, elapsed_time_ms); + return (int)(distance < 0 ? distance - 0.5f : distance + 0.5f); +} + +// == TBScroller ======================================================================== + +TBScroller::TBScroller(TBWidget *target) + : m_target(target) + , m_snap_listener(nullptr) + , m_func(SCROLL_DECAY) + , m_previous_pan_dx(0) + , m_previous_pan_dy(0) + , m_scroll_start_ms(0) + , m_scroll_duration_x_ms(0) + , m_scroll_duration_y_ms(0) + , m_pan_power_multiplier_x(1) + , m_pan_power_multiplier_y(1) +{ + Reset(); +} + +TBScroller::~TBScroller() +{ +} + +void TBScroller::Reset() +{ + m_is_started = false; + m_pan_dx = m_pan_dy = 0; + m_pan_time_ms = 0; + m_pan_delta_time_ms = 0; + m_scroll_start_speed_ppms_x = m_scroll_start_speed_ppms_y = 0; + m_scroll_start_scroll_x = m_scroll_start_scroll_y = 0; + // don't reset m_previous_pan_dx and m_previous_pan_dy here. + // don't reset m_pan_power here. It's done on start since it's needed for next pan! + m_expected_scroll_x = m_expected_scroll_y = 0; +} + +void TBScroller::OnScrollBy(int dx, int dy, bool accumulative) +{ + if (!IsStarted()) + Start(); + + float ppms_x = m_func.GetSpeedFromDistance((float)dx); + float ppms_y = m_func.GetSpeedFromDistance((float)dy); + + if (accumulative && IsScrolling()) + { + TBWidget::ScrollInfo info = m_target->GetScrollInfo(); + // If new direction is the same as the current direction, + // calculate the speed needed for the remaining part and + // add that to the new scroll speed. + if (ppms_x < 0 == m_scroll_start_speed_ppms_x < 0) + { + int distance_x = m_func.GetDistanceAtTimeInt(m_scroll_start_speed_ppms_x, + m_func.GetDurationFromSpeed(m_scroll_start_speed_ppms_x)); + int distance_remaining_x = m_scroll_start_scroll_x + distance_x - info.x; + distance_remaining_x += m_func.GetDistanceAtTimeInt(ppms_x, m_func.GetDurationFromSpeed(ppms_x)); + ppms_x = m_func.GetSpeedFromDistance((float)distance_remaining_x); + } + if (ppms_y < 0 == m_scroll_start_speed_ppms_y < 0) + { + int distance_y = m_func.GetDistanceAtTimeInt(m_scroll_start_speed_ppms_y, + m_func.GetDurationFromSpeed(m_scroll_start_speed_ppms_y)); + int distance_remaining_y = m_scroll_start_scroll_y + distance_y - info.y; + distance_remaining_y += m_func.GetDistanceAtTimeInt(ppms_y, m_func.GetDurationFromSpeed(ppms_y)); + ppms_y = m_func.GetSpeedFromDistance((float)distance_remaining_y); + } + } + + AdjustToSnappingAndScroll(ppms_x, ppms_y); +} + +bool TBScroller::OnPan(int dx, int dy) +{ + if (!IsStarted()) + Start(); + + // Pan the target + const int in_dx = dx, in_dy = dy; + m_target->ScrollByRecursive(dx, dy); + + // Calculate the pan speed. Smooth it out with the + // previous pan speed to reduce fluctuation a little. + double now_ms = TBSystem::GetTimeMS(); + if (m_pan_time_ms) + { + if (m_pan_delta_time_ms) + m_pan_delta_time_ms = (now_ms - m_pan_time_ms + m_pan_delta_time_ms) / 2.0f; + else + m_pan_delta_time_ms = now_ms - m_pan_time_ms; + } + + m_pan_time_ms = now_ms; + m_pan_dx = (m_pan_dx + in_dx) / 2.0f; + m_pan_dy = (m_pan_dy + in_dy) / 2.0f; + + // If we change direction, reset the pan power multiplier in that axis. + if (m_pan_dx != 0 && (m_previous_pan_dx < 0) != (m_pan_dx < 0)) + m_pan_power_multiplier_x = 1; + if (m_pan_dy != 0 && (m_previous_pan_dy < 0) != (m_pan_dy < 0)) + m_pan_power_multiplier_y = 1; + m_previous_pan_dx = m_pan_dx; + m_previous_pan_dy = m_pan_dy; + + return in_dx != dx || in_dy != dy; +} + +void TBScroller::OnPanReleased() +{ + if (TBSystem::GetTimeMS() < m_pan_time_ms + PAN_START_THRESHOLD_MS) + { + // Don't start scroll if we have too little speed. + // This will prevent us from scrolling accidently. + float pan_start_distance_threshold_px = 2 * TBSystem::GetDPI() / 100.0f; + if (ABS(m_pan_dx) < pan_start_distance_threshold_px && ABS(m_pan_dy) < pan_start_distance_threshold_px) + { + StopOrSnapScroll(); + return; + } + + if (m_pan_delta_time_ms == 0) + { + StopOrSnapScroll(); + return; + } + + float ppms_x = (float)m_pan_dx / (float)m_pan_delta_time_ms; + float ppms_y = (float)m_pan_dy / (float)m_pan_delta_time_ms; + ppms_x *= m_pan_power_multiplier_x; + ppms_y *= m_pan_power_multiplier_y; + + AdjustToSnappingAndScroll(ppms_x, ppms_y); + } + else + StopOrSnapScroll(); +} + +void TBScroller::Start() +{ + if (IsStarted()) + return; + m_is_started = true; + double now_ms = TBSystem::GetTimeMS(); + if (now_ms < m_scroll_start_ms + PAN_POWER_ACC_THRESHOLD_MS) + { + m_pan_power_multiplier_x *= PAN_POWER_MULTIPLIER; + m_pan_power_multiplier_y *= PAN_POWER_MULTIPLIER; + } + else + { + m_pan_power_multiplier_x = m_pan_power_multiplier_y = 1; + } +} + +void TBScroller::Stop() +{ + DeleteAllMessages(); + Reset(); +} + +bool TBScroller::StopIfAlmostStill() +{ + double now_ms = TBSystem::GetTimeMS(); + if (now_ms > m_scroll_start_ms + (double)m_scroll_duration_x_ms && + now_ms > m_scroll_start_ms + (double)m_scroll_duration_y_ms) + { + Stop(); + return true; + } + return false; +} + +void TBScroller::StopOrSnapScroll() +{ + AdjustToSnappingAndScroll(0, 0); + if (!IsScrolling()) + Stop(); +} + +void TBScroller::AdjustToSnappingAndScroll(float ppms_x, float ppms_y) +{ + if (m_snap_listener) + { + // Calculate the distance + int distance_x = m_func.GetDistanceAtTimeInt(ppms_x, m_func.GetDurationFromSpeed(ppms_x)); + int distance_y = m_func.GetDistanceAtTimeInt(ppms_y, m_func.GetDurationFromSpeed(ppms_y)); + + // Let the snap listener modify the distance + TBWidget::ScrollInfo info = m_target->GetScrollInfo(); + int target_x = distance_x + info.x; + int target_y = distance_y + info.y; + m_snap_listener->OnScrollSnap(m_target, target_x, target_y); + distance_x = target_x - info.x; + distance_y = target_y - info.y; + + // Get the start speed from the new distance + ppms_x = m_func.GetSpeedFromDistance((float)distance_x); + ppms_y = m_func.GetSpeedFromDistance((float)distance_y); + } + + Scroll(ppms_x, ppms_y); +} + +void TBScroller::Scroll(float start_speed_ppms_x, float start_speed_ppms_y) +{ + // Set start values + m_scroll_start_ms = TBSystem::GetTimeMS(); + GetTargetScrollXY(m_scroll_start_scroll_x, m_scroll_start_scroll_y); + m_scroll_start_speed_ppms_x = start_speed_ppms_x; + m_scroll_start_speed_ppms_y = start_speed_ppms_y; + + // Calculate duration for the scroll (each axis independently) + m_scroll_duration_x_ms = m_func.GetDurationFromSpeed(m_scroll_start_speed_ppms_x); + m_scroll_duration_y_ms = m_func.GetDurationFromSpeed(m_scroll_start_speed_ppms_y); + + if (StopIfAlmostStill()) + return; + + // Post the pan message if we don't already have one + if (!GetMessageByID(TBIDC("scroll"))) + { + // Update expected translation + GetTargetChildTranslation(m_expected_scroll_x, m_expected_scroll_y); + + PostMessageDelayed(TBIDC("scroll"), nullptr, (uint32)PAN_MSG_DELAY_MS); + } +} + +bool TBScroller::IsScrolling() +{ + return GetMessageByID(TBIDC("scroll")) ? true : false; +} + +void TBScroller::GetTargetChildTranslation(int &x, int &y) const +{ + int root_x = 0, root_y = 0; + int child_translation_x = 0, child_translation_y = 0; + TBWidget *scroll_root = m_target->GetScrollRoot(); + scroll_root->ConvertToRoot(root_x, root_y); + scroll_root->GetChildTranslation(child_translation_x, child_translation_y); + x = root_x + child_translation_x; + y = root_y + child_translation_y; +} + +void TBScroller::GetTargetScrollXY(int &x, int &y) const +{ + x = 0; + y = 0; + TBWidget *tmp = m_target->GetScrollRoot(); + while (tmp) + { + TBWidget::ScrollInfo info = tmp->GetScrollInfo(); + x += info.x; + y += info.y; + tmp = tmp->GetParent(); + } +} + +void TBScroller::OnMessageReceived(TBMessage *msg) +{ + if (msg->message == TBIDC("scroll")) + { + int actual_scroll_x = 0, actual_scroll_y = 0; + GetTargetChildTranslation(actual_scroll_x, actual_scroll_y); + if (actual_scroll_x != m_expected_scroll_x || + actual_scroll_y != m_expected_scroll_y) + { + // Something else has affected the target child translation. + // This should abort the scroll. + // This could happen f.ex if something shrunk the scroll limits, + // some other action changed scroll position, or if another + // scroller started operating on a sub child that when reacing + // its scroll limit, started scrolling its chain of parents. + Stop(); + return; + } + + // Calculate the time elapsed from scroll start. Clip within the + // duration for each axis. + double now_ms = TBSystem::GetTimeMS(); + float elapsed_time_x = (float)(now_ms - m_scroll_start_ms); + float elapsed_time_y = elapsed_time_x; + elapsed_time_x = MIN(elapsed_time_x, m_scroll_duration_x_ms); + elapsed_time_y = MIN(elapsed_time_y, m_scroll_duration_y_ms); + + // Get the new scroll position from the current distance in each axis. + int scroll_x = m_func.GetDistanceAtTimeInt(m_scroll_start_speed_ppms_x, elapsed_time_x); + int scroll_y = m_func.GetDistanceAtTimeInt(m_scroll_start_speed_ppms_y, elapsed_time_y); + scroll_x += m_scroll_start_scroll_x; + scroll_y += m_scroll_start_scroll_y; + + // Get the scroll delta and invoke ScrollByRecursive. + int curr_scroll_x, curr_scroll_y; + GetTargetScrollXY(curr_scroll_x, curr_scroll_y); + const int dx = scroll_x - curr_scroll_x; + const int dy = scroll_y - curr_scroll_y; + + int idx = dx, idy = dy; + m_target->ScrollByRecursive(idx, idy); + + // Update expected translation + GetTargetChildTranslation(m_expected_scroll_x, m_expected_scroll_y); + + if ((dx && actual_scroll_x == m_expected_scroll_x) && + (dy && actual_scroll_y == m_expected_scroll_y)) + { + // We didn't get anywhere despite we tried, + // so we're done (reached the end). + Stop(); + return; + } + + if (!StopIfAlmostStill()) + { + double next_fire_time = msg->GetFireTime() + PAN_MSG_DELAY_MS; + // avoid timer catch-up if program went sleeping for a while. + next_fire_time = MAX(next_fire_time, now_ms); + PostMessageOnTime(TBIDC("scroll"), nullptr, next_fire_time); + } + } +} + +}; // namespace tinkerbell diff --git a/tinkerbell/src/tb_scroller.h b/tinkerbell/src/tb_scroller.h new file mode 100644 index 0000000000..74881043c0 --- /dev/null +++ b/tinkerbell/src/tb_scroller.h @@ -0,0 +1,133 @@ +// ================================================================================ +// == This file is a part of TinkerBell UI Toolkit. (C) 2011-2013, Emil Segerås == +// == See tinkerbell.h for more information. == +// ================================================================================ + +#ifndef TB_SCROLLER_H +#define TB_SCROLLER_H + +#include "tinkerbell.h" +#include "tb_msg.h" + +namespace tinkerbell { + +class TBWidget; + +/** TBScrollerFunction does the calculations of time, speed and distance + that decides how the slow down of a scroll will happen. + + Note: Speed is in pixels per millisecond. Duration is in milliseconds + and distance is in pixels. Distance and speed may be negative! */ + +class TBScrollerFunction +{ +public: + TBScrollerFunction(float decay) : m_decay(decay) {} + + /** Calculate the duration needed until the end distance is reached + from the given start speed. */ + float GetDurationFromSpeed(float start_speed); + + /** Calculate the start speed needed to reach the given distance. */ + float GetSpeedFromDistance(float distance); + + /** Calculate the distance reached at the given elapsed_time_ms with the given start_speed. */ + float GetDistanceAtTime(float start_speed, float elapsed_time_ms); + + /** Same as GetDistanceAtTime but rounded to integer. */ + int GetDistanceAtTimeInt(float start_speed, float elapsed_time_ms); +private: + float m_decay; +}; + +/** TBScrollerSnapListener may override the target scroll position of a TBScroller. */ + +class TBScrollerSnapListener +{ +public: + virtual ~TBScrollerSnapListener() {}; + + /** Called when the target scroll position is calculated. + + target_widget is the widget being scroller. + target_x, target_y is the suggested target scroll position which may be changed + to something else in this call. + + Note: The scroll positions are relative to the target widget (inner scrolled TBWidget). + If there's nested scrollable widgets, only the inner scrolled widget applies snapping. */ + virtual void OnScrollSnap(TBWidget *target_widget, int &target_x, int &target_y) = 0; +}; + +/** TBScroller handles panning while the pointer is down and measure the pan + speed over time. It also handles continued scrolling when the pointer has + been released with a flick. */ +class TBScroller : private TBMessageHandler +{ +public: + TBScroller(TBWidget *target); + ~TBScroller(); + + /** Set the listener that may override the target scroll position. */ + void SetSnapListener(TBScrollerSnapListener *listener) { m_snap_listener = listener; } + + /** Start tracking pan movement from calls to OnPan. */ + void Start(); + + /** Stop tracking pan movement from calls to OnPan, + or stop any ongoing scrolling. */ + void Stop(); + + /** Return true if the pan tracking is started or. */ + bool IsStarted() const { return m_is_started; } + + /** Get the widget that will be panned/scrolled. Any parent of this + widget may also be panned/scrolled. */ + TBWidget *GetTarget() const { return m_target; } + + /** Pan the target widget (or any parent) with the given deltas. + Should be called while the pointer is down. + This will track the pan speed over time. */ + bool OnPan(int dx, int dy); + + /** The panning ends and the scroller should start scrolling. + Should be called when the pointer is released. */ + void OnPanReleased(); + + /** Start the scroller based on the given delta. Doesn't + require previous calls to OnPan or OnPanReleased. + + If accumulative is true, the given delta will be + added to any on going scroll. If it's false, any + ongoing scroll will be canceled. */ + void OnScrollBy(int dx, int dy, bool accumulative); +private: + virtual void OnMessageReceived(TBMessage *msg); + bool IsScrolling(); + bool StopIfAlmostStill(); + void StopOrSnapScroll(); + void Reset(); + void AdjustToSnappingAndScroll(float ppms_x, float ppms_y); + void Scroll(float start_speed_ppms_x, float start_speed_ppms_y); + void GetTargetChildTranslation(int &x, int &y) const; + void GetTargetScrollXY(int &x, int &y) const; + TBWidget *m_target; + TBScrollerSnapListener *m_snap_listener; + TBScrollerFunction m_func; + bool m_is_started; + float m_pan_dx, m_pan_dy; + float m_previous_pan_dx, m_previous_pan_dy; + double m_pan_time_ms; + double m_pan_delta_time_ms; + float m_scroll_start_speed_ppms_x, m_scroll_start_speed_ppms_y; + double m_scroll_start_ms; + float m_scroll_duration_x_ms, m_scroll_duration_y_ms; + int m_scroll_start_scroll_x, m_scroll_start_scroll_y; + float m_pan_power_multiplier_x; + float m_pan_power_multiplier_y; + int m_expected_scroll_x; + int m_expected_scroll_y; +}; + +}; // namespace tinkerbell + +#endif // TB_SCROLLER_H diff --git a/tinkerbell/src/tb_select.cpp b/tinkerbell/src/tb_select.cpp index 2221773ec8..d8c15a88f7 100644 --- a/tinkerbell/src/tb_select.cpp +++ b/tinkerbell/src/tb_select.cpp @@ -239,7 +239,7 @@ void TBSelectList::SetValue(int value) SelectItem(m_value, true); ScrollToSelectedItem(); - TBWidgetEvent ev(EVENT_TYPE_CHANGED, 0, 0); + TBWidgetEvent ev(EVENT_TYPE_CHANGED); if (TBWidget *widget = GetItemWidget(m_value)) ev.ref_id = widget->GetID(); InvokeEvent(ev); @@ -325,7 +325,7 @@ bool TBSelectList::OnEvent(const TBWidgetEvent &ev) } // Invoke the click event on the target list - TBWidgetEvent ev(EVENT_TYPE_CLICK, 0, 0); + TBWidgetEvent ev(EVENT_TYPE_CLICK); if (TBWidget *widget = GetItemWidget(m_value)) ev.ref_id = widget->GetID(); target_list->InvokeEvent(ev); @@ -334,7 +334,15 @@ bool TBSelectList::OnEvent(const TBWidgetEvent &ev) } else if (ev.type == EVENT_TYPE_KEY_DOWN) { - return ChangeValue(ev.special_key); + if (ChangeValue(ev.special_key)) + return true; + + // Give the scroll container a chance to handle the key so it may + // scroll. This matters if the list itself is focused instead of + // some child view of any select item (since that would have passed + // the container already) + if (GetScrollContainer()->OnEvent(ev)) + return true; } return false; } @@ -416,7 +424,7 @@ void TBSelectDropdown::SetValue(int value) else if (m_value < m_source->GetNumItems()) SetText(m_source->GetItemString(m_value)); - TBWidgetEvent ev(EVENT_TYPE_CHANGED, 0, 0); + TBWidgetEvent ev(EVENT_TYPE_CHANGED); InvokeEvent(ev); } diff --git a/tinkerbell/src/tb_style_edit.cpp b/tinkerbell/src/tb_style_edit.cpp index d4bf93303f..9d24ae027d 100644 --- a/tinkerbell/src/tb_style_edit.cpp +++ b/tinkerbell/src/tb_style_edit.cpp @@ -1720,12 +1720,16 @@ void TBStyleEdit::Redo() } } -bool TBStyleEdit::MouseDown(const TBPoint &point, int button, int clicks, MODIFIER_KEYS modifierkeys) +bool TBStyleEdit::MouseDown(const TBPoint &point, int button, int clicks, MODIFIER_KEYS modifierkeys, bool touch) { if (button != 1) return false; - if (packed.selection_on) + if (touch) + { + mousedown_point = TBPoint(point.x + scroll_x, point.y + scroll_y); + } + else if (packed.selection_on) { //if (modifierkeys & P_SHIFT) // Select to new caretpos //{ @@ -1748,12 +1752,21 @@ bool TBStyleEdit::MouseDown(const TBPoint &point, int button, int clicks, MODIFI return true; } -bool TBStyleEdit::MouseUp(const TBPoint &point, int button, MODIFIER_KEYS modifierkeys) +bool TBStyleEdit::MouseUp(const TBPoint &point, int button, MODIFIER_KEYS modifierkeys, bool touch) { if (button != 1) return false; + + if (touch && !TBWidget::cancel_click) + { + selection.SelectNothing(); + caret.Place(mousedown_point); + caret.UpdateWantedX(); + caret.ResetBlink(); + } + select_state = 0; - if (caret.pos.block) + if (caret.pos.block && !TBWidget::cancel_click) { TBTextFragment *fragment = caret.pos.block->FindFragment(point.x + scroll_x, point.y + scroll_y - caret.pos.block->ypos); if (fragment && fragment == mousedown_fragment) diff --git a/tinkerbell/src/tb_style_edit.h b/tinkerbell/src/tb_style_edit.h index add8c19107..ed82b6d5c2 100644 --- a/tinkerbell/src/tb_style_edit.h +++ b/tinkerbell/src/tb_style_edit.h @@ -317,8 +317,8 @@ class TBStyleEdit void Paint(const TBRect &rect, const TBFontDescription &font_desc, const TBColor &text_color); bool KeyDown(int key, SPECIAL_KEY special_key, MODIFIER_KEYS modifierkeys); - bool MouseDown(const TBPoint &point, int button, int clicks, MODIFIER_KEYS modifierkeys); - bool MouseUp(const TBPoint &point, int button, MODIFIER_KEYS modifierkeys); + bool MouseDown(const TBPoint &point, int button, int clicks, MODIFIER_KEYS modifierkeys, bool touch); + bool MouseUp(const TBPoint &point, int button, MODIFIER_KEYS modifierkeys, bool touch); bool MouseMove(const TBPoint &point); void Focus(bool focus); diff --git a/tinkerbell/src/tb_widgets.cpp b/tinkerbell/src/tb_widgets.cpp index 46f252b0ce..cd510220ac 100644 --- a/tinkerbell/src/tb_widgets.cpp +++ b/tinkerbell/src/tb_widgets.cpp @@ -10,6 +10,7 @@ #include "tb_widgets_common.h" #include "tb_widget_skin_condition_context.h" #include "tb_system.h" +#include "tb_scroller.h" #include "tb_font_renderer.h" #include #ifdef TB_ALWAYS_SHOW_EDIT_FOCUS @@ -26,7 +27,7 @@ int TBWidget::pointer_down_widget_x = 0; int TBWidget::pointer_down_widget_y = 0; int TBWidget::pointer_move_widget_x = 0; int TBWidget::pointer_move_widget_y = 0; -bool TBWidget::is_panning = false; +bool TBWidget::cancel_click = false; bool TBWidget::update_widget_states = true; bool TBWidget::show_focus_state = false; @@ -47,6 +48,7 @@ TBWidget::TBWidget() , m_state(WIDGET_STATE_NONE) , m_gravity(WIDGET_GRAVITY_DEFAULT) , m_layout_params(nullptr) + , m_scroller(nullptr) , m_packed_init(0) { #ifdef TB_RUNTIME_DEBUG_INFO @@ -75,6 +77,7 @@ TBWidget::~TBWidget() delete child; } + delete m_scroller; delete m_layout_params; } @@ -166,9 +169,10 @@ void TBWidget::SetState(WIDGET_STATE state, bool on) WIDGET_STATE TBWidget::GetAutoState() const { WIDGET_STATE state = m_state; - if (!is_panning && this == captured_widget && this == hovered_widget) + bool add_pressed_state = !cancel_click && this == captured_widget && this == hovered_widget; + if (add_pressed_state) state |= WIDGET_STATE_PRESSED; - if (this == hovered_widget) + if (this == hovered_widget && (!m_packed.no_automatic_hover_state || add_pressed_state)) state |= WIDGET_STATE_HOVERED; if (this == focused_widget && show_focus_state) state |= WIDGET_STATE_FOCUSED; @@ -324,12 +328,89 @@ TBSkinElement *TBWidget::GetSkinBgElement() return g_tb_skin->GetSkinElementStrongOverride(m_skin_bg, static_cast(state), context); } +TBWidget *TBWidget::FindScrollableWidget(bool scroll_x, bool scroll_y) +{ + TBWidget *candidate = this; + while (candidate) + { + ScrollInfo scroll_info = candidate->GetScrollInfo(); + if ((scroll_x && scroll_info.CanScrollX()) || + (scroll_y && scroll_info.CanScrollY())) + return candidate; + candidate = candidate->GetParent(); + } + return nullptr; +} + +TBScroller *TBWidget::FindStartedScroller() +{ + TBWidget *candidate = this; + while (candidate) + { + if (candidate->m_scroller && candidate->m_scroller->IsStarted()) + return candidate->m_scroller; + candidate = candidate->GetParent(); + } + return nullptr; +} + +TBScroller *TBWidget::GetReadyScroller(bool scroll_x, bool scroll_y) +{ + if (TBScroller *scroller = FindStartedScroller()) + return scroller; + // We didn't have any active scroller, so create one for the nearest scrollable parent. + if (TBWidget *scrollable_widget = FindScrollableWidget(scroll_x, scroll_y)) + return scrollable_widget->GetScroller(); + return nullptr; +} + +TBScroller *TBWidget::GetScroller() +{ + if (!m_scroller) + m_scroller = new TBScroller(this); + return m_scroller; +} + +void TBWidget::ScrollToSmooth(int x, int y) +{ + ScrollInfo info = GetScrollInfo(); + int dx = x - info.x; + int dy = y - info.y; + if (TBScroller *scroller = GetReadyScroller(dx != 0, dy != 0)) + scroller->OnScrollBy(dx, dy, false); +} + +void TBWidget::ScrollBySmooth(int dx, int dy) +{ + // Clip the values to the scroll limits, so we don't + // scroll any parents. + //int x = CLAMP(info.x + dx, info.min_x, info.max_x); + //int y = CLAMP(info.y + dy, info.min_y, info.max_y); + //dx = x - info.x; + //dy = y - info.y; + if (!dx && !dy) + return; + + if (TBScroller *scroller = GetReadyScroller(dx != 0, dy != 0)) + scroller->OnScrollBy(dx, dy, true); +} + +void TBWidget::ScrollBy(int dx, int dy) +{ + ScrollInfo info = GetScrollInfo(); + ScrollTo(info.x + dx, info.y + dy); +} + void TBWidget::ScrollByRecursive(int &dx, int &dy) { TBWidget *tmp = this; while (tmp) { - tmp->ScrollBy(dx, dy); + ScrollInfo old_info = tmp->GetScrollInfo(); + tmp->ScrollTo(old_info.x + dx, old_info.y + dy); + ScrollInfo new_info = tmp->GetScrollInfo(); + dx -= new_info.x - old_info.x; + dy -= new_info.y - old_info.y; if (!dx && !dy) break; tmp = tmp->m_parent; @@ -349,6 +430,27 @@ void TBWidget::ScrollIntoViewRecursive() } } +void TBWidget::ScrollIntoView(const TBRect &rect) +{ + const ScrollInfo info = GetScrollInfo(); + int new_x = info.x; + int new_y = info.y; + + const TBRect visible_rect = GetPaddingRect().Offset(info.x, info.y); + + if (rect.y <= visible_rect.y) + new_y = rect.y; + else if (rect.y + rect.h > visible_rect.y + visible_rect.h) + new_y = rect.y + rect.h - visible_rect.h; + + if (rect.x <= visible_rect.x) + new_x = rect.x; + else if (rect.x + rect.w > visible_rect.x + visible_rect.w) + new_x = rect.x + rect.w - visible_rect.w; + + ScrollTo(new_x, new_y); +} + bool TBWidget::SetFocus(WIDGET_FOCUS_REASON reason, WIDGET_INVOKE_INFO info) { if (focused_widget == this) @@ -960,12 +1062,12 @@ bool TBWidget::InvokeEvent(TBWidgetEvent &ev) return handled; } -void TBWidget::InvokePointerDown(int x, int y, int click_count, MODIFIER_KEYS modifierkeys) +void TBWidget::InvokePointerDown(int x, int y, int click_count, MODIFIER_KEYS modifierkeys, bool touch) { if (!captured_widget) { SetCapturedWidget(GetWidgetAt(x, y, true)); - SetHoveredWidget(captured_widget); + SetHoveredWidget(captured_widget, touch); //captured_button = button; // Hide focus when we use the pointer, if it's not on the focused widget. @@ -979,6 +1081,21 @@ void TBWidget::InvokePointerDown(int x, int y, int click_count, MODIFIER_KEYS mo } if (captured_widget) { + // Check if there's any started scroller that should be stopped. + TBWidget *tmp = captured_widget; + while (tmp) + { + if (tmp->m_scroller && tmp->m_scroller->IsStarted()) + { + // When we touch down to stop a scroller, we don't + // want the touch to end up causing a click. + cancel_click = true; + tmp->m_scroller->Stop(); + break; + } + tmp = tmp->GetParent(); + } + // Focus the captured widget or the closest // focusable parent if it isn't focusable. TBWidget *focus_target = captured_widget; @@ -994,30 +1111,30 @@ void TBWidget::InvokePointerDown(int x, int y, int click_count, MODIFIER_KEYS mo captured_widget->ConvertFromRoot(x, y); pointer_move_widget_x = pointer_down_widget_x = x; pointer_move_widget_y = pointer_down_widget_y = y; - TBWidgetEvent ev(EVENT_TYPE_POINTER_DOWN, x, y, modifierkeys); + TBWidgetEvent ev(EVENT_TYPE_POINTER_DOWN, x, y, touch, modifierkeys); ev.count = click_count; captured_widget->InvokeEvent(ev); } } -void TBWidget::InvokePointerUp(int x, int y, MODIFIER_KEYS modifierkeys) +void TBWidget::InvokePointerUp(int x, int y, MODIFIER_KEYS modifierkeys, bool touch) { if (captured_widget) { captured_widget->ConvertFromRoot(x, y); - TBWidgetEvent ev_up(EVENT_TYPE_POINTER_UP, x, y, modifierkeys); - TBWidgetEvent ev_click(EVENT_TYPE_CLICK, x, y, modifierkeys); + TBWidgetEvent ev_up(EVENT_TYPE_POINTER_UP, x, y, touch, modifierkeys); + TBWidgetEvent ev_click(EVENT_TYPE_CLICK, x, y, touch, modifierkeys); captured_widget->InvokeEvent(ev_up); - if (!is_panning && captured_widget && captured_widget->GetHitStatus(x, y)) + if (!cancel_click && captured_widget && captured_widget->GetHitStatus(x, y)) captured_widget->InvokeEvent(ev_click); if (captured_widget) // && button == captured_button captured_widget->ReleaseCapture(); } } -void TBWidget::InvokePointerMove(int x, int y, MODIFIER_KEYS modifierkeys) +void TBWidget::InvokePointerMove(int x, int y, MODIFIER_KEYS modifierkeys, bool touch) { - SetHoveredWidget(GetWidgetAt(x, y, true)); + SetHoveredWidget(GetWidgetAt(x, y, true), touch); TBWidget *target = captured_widget ? captured_widget : hovered_widget; if (target) { @@ -1025,7 +1142,7 @@ void TBWidget::InvokePointerMove(int x, int y, MODIFIER_KEYS modifierkeys) pointer_move_widget_x = x; pointer_move_widget_y = y; - TBWidgetEvent ev(EVENT_TYPE_POINTER_MOVE, x, y, modifierkeys); + TBWidgetEvent ev(EVENT_TYPE_POINTER_MOVE, x, y, touch, modifierkeys); if (target->InvokeEvent(ev)) return; @@ -1046,21 +1163,26 @@ void TBWidget::HandlePanningOnMove(int x, int y) bool maybe_start_panning = (ABS(dx) >= threshold || ABS(dy) >= threshold); // Do panning, or attempt starting panning (we don't know if any widget is scrollable yet) - if (is_panning || maybe_start_panning) + if (captured_widget->m_packed.is_panning || maybe_start_panning) { + // Get any active scroller and feed it with pan actions. + TBScroller *scroller = captured_widget->GetReadyScroller(dx != 0, dy != 0); + if (!scroller) + return; + int old_translation_x = 0, old_translation_y = 0; - captured_widget->GetChildTranslation(old_translation_x, old_translation_y); + captured_widget->GetScrollRoot()->GetChildTranslation(old_translation_x, old_translation_y); - int old_dx = dx, old_dy = dy; - captured_widget->ScrollByRecursive(dx, dy); - if (old_dx != dx || old_dy != dy) + if (scroller->OnPan(dx, dy)) { // Scroll delta changed, so we are now panning! - is_panning = true; - // If the captured widget has panned too, we have to compensate the pointer down - // coordinates so next pan handling isn't off. + captured_widget->m_packed.is_panning = true; + cancel_click = true; + + // If the captured widget (or its scroll root) has panned, we have to compensate the + // pointer down coordinates so we won't accumulate the difference the following pan. int new_translation_x = 0, new_translation_y = 0; - captured_widget->GetChildTranslation(new_translation_x, new_translation_y); + captured_widget->GetScrollRoot()->GetChildTranslation(new_translation_x, new_translation_y); pointer_down_widget_x += new_translation_x - old_translation_x; pointer_down_widget_y += new_translation_y - old_translation_y; } @@ -1069,14 +1191,14 @@ void TBWidget::HandlePanningOnMove(int x, int y) void TBWidget::InvokeWheel(int x, int y, int delta_x, int delta_y, MODIFIER_KEYS modifierkeys) { - SetHoveredWidget(GetWidgetAt(x, y, true)); + SetHoveredWidget(GetWidgetAt(x, y, true), true); TBWidget *target = captured_widget ? captured_widget : hovered_widget; if (target) { target->ConvertFromRoot(x, y); pointer_move_widget_x = x; pointer_move_widget_y = y; - TBWidgetEvent ev(EVENT_TYPE_WHEEL, x, y, modifierkeys); + TBWidgetEvent ev(EVENT_TYPE_WHEEL, x, y, true, modifierkeys); ev.delta_x = delta_x; ev.delta_y = delta_y; target->InvokeEvent(ev); @@ -1115,7 +1237,7 @@ bool TBWidget::InvokeKey(int key, SPECIAL_KEY special_key, MODIFIER_KEYS modifie // Invoke the click event if (!down) { - TBWidgetEvent ev(EVENT_TYPE_CLICK, m_rect.w / 2, m_rect.h / 2); + TBWidgetEvent ev(EVENT_TYPE_CLICK, m_rect.w / 2, m_rect.h / 2, true); focused_widget->InvokeEvent(ev); } handled = true; @@ -1187,23 +1309,31 @@ void TBWidget::ConvertFromRoot(int &x, int &y) const } } -void TBWidget::SetHoveredWidget(TBWidget *widget) +// static +void TBWidget::SetHoveredWidget(TBWidget *widget, bool touch) { if (TBWidget::hovered_widget == widget) return; if (widget && widget->GetState(WIDGET_STATE_DISABLED)) return; - // We apply hover state automatically so the widget might need to be updated. + // We may apply hover state automatically so the widget might need to be updated. if (TBWidget::hovered_widget) TBWidget::hovered_widget->Invalidate(); TBWidget::hovered_widget = widget; if (TBWidget::hovered_widget) + { TBWidget::hovered_widget->Invalidate(); + + // Cursor based movement should set hover state automatically, but touch + // events should not (since touch doesn't really move unless pressed). + TBWidget::hovered_widget->m_packed.no_automatic_hover_state = touch; + } } +// static void TBWidget::SetCapturedWidget(TBWidget *widget) { if (TBWidget::captured_widget == widget) @@ -1212,7 +1342,19 @@ void TBWidget::SetCapturedWidget(TBWidget *widget) return; // Stop panning when capture change (most likely changing to nullptr because of InvokePointerUp) - is_panning = false; + // Notify any active scroller so it may begin scrolling. + if (TBWidget::captured_widget) + { + if (TBScroller *scroller = TBWidget::captured_widget->FindStartedScroller()) + { + if (TBWidget::captured_widget->m_packed.is_panning) + scroller->OnPanReleased(); + else + scroller->Stop(); + } + TBWidget::captured_widget->m_packed.is_panning = false; + } + cancel_click = false; TBWidget *old_capture = TBWidget::captured_widget; diff --git a/tinkerbell/src/tb_widgets.h b/tinkerbell/src/tb_widgets.h index 19bfd1368f..0a45d866c7 100644 --- a/tinkerbell/src/tb_widgets.h +++ b/tinkerbell/src/tb_widgets.h @@ -17,6 +17,7 @@ namespace tinkerbell { class TBWindow; class TBWidget; class TBFontFace; +class TBScroller; // == Generic widget stuff ================================================= @@ -93,13 +94,16 @@ class TBWidgetEvent SPECIAL_KEY special_key; MODIFIER_KEYS modifierkeys; TBID ref_id; ///< Sometimes (when documented) events have a ref_id (The id that caused this event) + bool touch; ///< Set for pointer events. True if the event is a touch event (finger or pen on screen) + ///< False if mouse or other cursor input. TBWidgetEvent(EVENT_TYPE type) : target(nullptr), type(type), target_x(0), target_y(0), delta_x(0), delta_y(0), count(1), - key(0), special_key(TB_KEY_UNDEFINED), modifierkeys(TB_MODIFIER_NONE) {} + key(0), special_key(TB_KEY_UNDEFINED), modifierkeys(TB_MODIFIER_NONE), touch(false) {} - TBWidgetEvent(EVENT_TYPE type, int x, int y, MODIFIER_KEYS modifierkeys = TB_MODIFIER_NONE) : + TBWidgetEvent(EVENT_TYPE type, int x, int y, bool touch, MODIFIER_KEYS modifierkeys = TB_MODIFIER_NONE) : target(nullptr), type(type), target_x(x), target_y(y), delta_x(0), delta_y(0), - count(1), key(0), special_key(TB_KEY_UNDEFINED), modifierkeys(modifierkeys) {} + count(1), key(0), special_key(TB_KEY_UNDEFINED), modifierkeys(modifierkeys), + touch(touch) {} /** The count value may be 1 to infinity. If you f.ex want to see which count it is for something handling click and double click, call GetCountCycle(2). If you also handle triple click, call @@ -568,27 +572,67 @@ class TBWidget : public TBTypedObject, public TBLinkOf /** Return translation the children should have. Any scrolling of child widgets should be done with this method, by returning the wanted translation. - When implementing this, also consider implementing ScrollIntoView so it will - scroll automatically f.ex when focusing widgets that's not in view, and - ScrollBy so dragging will automatically scroll this view. */ + + When implementing this, you must also implement ScrollTo and GetScrollInfo + so focus-scroll and panning will work automatically when dragging this or + any child widget. Note: You can apply the translation on one widget and + implement those methods on a parent, by returning this widget from the + parents GetScrollRoot(). */ virtual void GetChildTranslation(int &x, int &y) const { x = y = 0; } /** If this is a widget that scroll children (see GetChildTranslation), it should - scroll so that rect is visible. Rect is relative to this view. */ - virtual void ScrollIntoView(const TBRect &rect) {} + scroll to the coordinates x, y. */ + virtual void ScrollTo(int x, int y) {} - /** If this is a widget that scroll children (see GetChildTranslation), it should - scroll by delta dx, dy relative to its current position. - It must also deduct dx and dy with the amount scrolled, so the remaining - delta (if any) can be scrolled in another view. */ - virtual void ScrollBy(int &dx, int &dy) {} + /** Start the TBScroller for this widget and scroll it to the given position. + Will cancel any on going smooth scroll operation. */ + void ScrollToSmooth(int x, int y); + + /** If this is a widget that scroll children (see GetChildTranslation), it will + scroll by delta dx, dy relative to its current position. */ + void ScrollBy(int dx, int dy); + + /** Start the TBScroller for this widget and scroll it by the given delta. + Consecutive calls will accumulate the scroll speed. */ + void ScrollBySmooth(int dx, int dy); - /** Scroll this widget and/or any parent widgets by the given delta. */ + /** Information about scrolling for a widget at the time of calling GetScrollInfo. */ + class ScrollInfo + { + public: + ScrollInfo() : min_x(0), min_y(0), max_x(0), max_y(0), x(0), y(0) {} + bool CanScrollX() const { return max_x > min_x; } + bool CanScrollY() const { return max_y > min_y; } + bool CanScroll() const { return CanScrollX() || CanScrollY(); } + int min_x, min_y; ///< Minimum x and y scroll position. + int max_x, max_y; ///< Maximum x and y scroll position. + int x, y; ///< Current x and y scroll position. + }; + + /** If this is a widget that scroll children (see GetChildTranslation), + it should return the current scroll information. */ + virtual ScrollInfo GetScrollInfo() { return ScrollInfo(); } + + /** If this widget is implementing ScrollTo and GetScrollInfo but + the corresponding GetChildTranslation is implemented on a child, + you should return that child from this method. */ + virtual TBWidget *GetScrollRoot() { return this; } + + /** Scroll this widget and/or any parent widgets by the given delta. + dx and dy will be reduced by the amount that was successfully + scrolled. */ void ScrollByRecursive(int &dx, int &dy); /** Make this widget visible by calling ScrollIntoView on all parent widgets */ void ScrollIntoViewRecursive(); + /** If this is a widget that scroll children (see GetChildTranslation), it will + scroll so that rect is visible. Rect is relative to this widget. */ + void ScrollIntoView(const TBRect &rect); + + /** Return the TBScroller set up for this widget, or nullptr if creation failed. */ + TBScroller *GetScroller(); + // == Setter shared for many types of widgets ============ /** Set along which axis the content should be layouted. */ @@ -631,7 +675,10 @@ class TBWidget : public TBTypedObject, public TBLinkOf void Unconnect() { m_connection.Unconnect(); } /** Get the rectangle inside any padding, relative to this widget. This is the - rectangle in which the content should be rendered. */ + rectangle in which the content should be rendered. + + This may be overridden to f.ex deduct space allocated by visible scrollbars + managed by this widget. Anything that removes space from the content area. */ virtual TBRect GetPaddingRect(); /** Calculate the preferred content size for this widget. This is the size of the actual @@ -706,9 +753,9 @@ class TBWidget : public TBTypedObject, public TBLinkOf this call and are not sure what the event will cause, use TBWidgetSafePointer to detect self deletion. */ bool InvokeEvent(TBWidgetEvent &ev); - void InvokePointerDown(int x, int y, int click_count, MODIFIER_KEYS modifierkeys); - void InvokePointerUp(int x, int y, MODIFIER_KEYS modifierkeys); - void InvokePointerMove(int x, int y, MODIFIER_KEYS modifierkeys); + void InvokePointerDown(int x, int y, int click_count, MODIFIER_KEYS modifierkeys, bool touch); + void InvokePointerUp(int x, int y, MODIFIER_KEYS modifierkeys, bool touch); + void InvokePointerMove(int x, int y, MODIFIER_KEYS modifierkeys, bool touch); void InvokeWheel(int x, int y, int delta_x, int delta_y, MODIFIER_KEYS modifierkeys); /** Invoke the EVENT_TYPE_KEY_DOWN and EVENT_TYPE_KEY_UP events on the currently focused widget. @@ -766,6 +813,7 @@ class TBWidget : public TBTypedObject, public TBLinkOf TBFontDescription m_font_desc; ///< The font description. PreferredSize m_cached_ps; ///< Cached preferred size. LayoutParams *m_layout_params; ///< Layout params, or nullptr. + TBScroller *m_scroller; union { struct { uint16 is_group_root : 1; @@ -775,6 +823,8 @@ class TBWidget : public TBTypedObject, public TBLinkOf uint16 ignore_input : 1; uint16 is_dying : 1; uint16 is_cached_ps_valid : 1; + uint16 no_automatic_hover_state : 1; + uint16 is_panning : 1; } m_packed; uint16 m_packed_init; }; @@ -796,15 +846,20 @@ class TBWidget : public TBTypedObject, public TBLinkOf static int pointer_down_widget_y; ///< Pointer y position on down event, relative to the captured widget. static int pointer_move_widget_x; ///< Pointer x position on last pointer event, relative to the captured widget (if any) or hovered widget. static int pointer_move_widget_y; ///< Pointer y position on last pointer event, relative to the captured widget (if any) or hovered widget. - static bool is_panning; ///< true if currently panning scrollable widgets. Pointer up should not generate a click event. + static bool cancel_click; ///< true if the pointer up event should not generate a click event. static bool update_widget_states; ///< true if something has called InvalidateStates() and it still hasn't been updated. static bool show_focus_state; ///< true if the focused state should be painted automatically. private: + /** Return this widget or the nearest parent that is scrollable + in the given axis, or nullptr if there is none. */ + TBWidget *FindScrollableWidget(bool scroll_x, bool scroll_y); + TBScroller *FindStartedScroller(); + TBScroller *GetReadyScroller(bool scroll_x, bool scroll_y); TBWidget *GetWidgetByIDInternal(const TBID &id, const TB_TYPE_ID type_id = nullptr); void InvokeSkinUpdatesInternal(); void InvokeProcessInternal(); - void SetHoveredWidget(TBWidget *widget); - void SetCapturedWidget(TBWidget *widget); + static void SetHoveredWidget(TBWidget *widget, bool touch); + static void SetCapturedWidget(TBWidget *widget); void HandlePanningOnMove(int x, int y); }; diff --git a/tinkerbell/src/tb_widgets_common.cpp b/tinkerbell/src/tb_widgets_common.cpp index fda576655c..20f74b4085 100644 --- a/tinkerbell/src/tb_widgets_common.cpp +++ b/tinkerbell/src/tb_widgets_common.cpp @@ -178,7 +178,7 @@ bool TBButton::OnEvent(const TBWidgetEvent &ev) return true; // We got removed so we actually handled this event. // Invoke a changed event. - TBWidgetEvent ev(EVENT_TYPE_CHANGED, 0, 0); + TBWidgetEvent ev(EVENT_TYPE_CHANGED); InvokeEvent(ev); if (!this_widget.Get()) @@ -194,9 +194,9 @@ void TBButton::OnMessageReceived(TBMessage *msg) if (msg->message == TBIDC("auto_click")) { assert(captured_widget == this); - if (!is_panning && GetHitStatus(pointer_move_widget_x, pointer_move_widget_y)) + if (!cancel_click && GetHitStatus(pointer_move_widget_x, pointer_move_widget_y)) { - TBWidgetEvent ev(EVENT_TYPE_CLICK, pointer_move_widget_x, pointer_move_widget_y); + TBWidgetEvent ev(EVENT_TYPE_CLICK, pointer_move_widget_x, pointer_move_widget_y, true); captured_widget->InvokeEvent(ev); } if (auto_click_repeat_delay) @@ -249,7 +249,8 @@ bool TBClickLabel::OnEvent(const TBWidgetEvent &ev) click_target->SetState(WIDGET_STATE_PRESSED, pressed_state); - TBWidgetEvent target_ev(ev.type, ev.target_x - click_target->GetRect().x, ev.target_y - click_target->GetRect().y); + TBWidgetEvent target_ev(ev.type, ev.target_x - click_target->GetRect().x, ev.target_y - click_target->GetRect().y, + ev.touch, ev.modifierkeys); return click_target->InvokeEvent(target_ev); } return false; @@ -358,7 +359,7 @@ void TBRadioCheckBox::SetValue(int value) SetState(WIDGET_STATE_SELECTED, value ? true : false); Invalidate(); - TBWidgetEvent ev(EVENT_TYPE_CHANGED, 0, 0); + TBWidgetEvent ev(EVENT_TYPE_CHANGED); InvokeEvent(ev); if (!value || !GetGroupID()) @@ -463,7 +464,7 @@ void TBScrollBar::SetValueDouble(double value) m_value = value; UpdateHandle(); - TBWidgetEvent ev(EVENT_TYPE_CHANGED, 0, 0); + TBWidgetEvent ev(EVENT_TYPE_CHANGED); InvokeEvent(ev); } @@ -597,7 +598,7 @@ void TBSlider::SetValueDouble(double value) m_value = value; UpdateHandle(); - TBWidgetEvent ev(EVENT_TYPE_CHANGED, 0, 0); + TBWidgetEvent ev(EVENT_TYPE_CHANGED); InvokeEvent(ev); }