Jelajahi Sumber

LibGUI: Fix flickering scrollbars in AbstractScrollableWidget

The new min_content_size value is to be set by the subclasses, it is
then used to determine if the scrollbars should be shown after a
resize, but before the content size will be calculated by the following
layout pass.
FrHun 3 tahun lalu
induk
melakukan
d0a418540e

+ 45 - 17
Userland/Libraries/LibGUI/AbstractScrollableWidget.cpp

@@ -106,6 +106,7 @@ void AbstractScrollableWidget::custom_layout()
 void AbstractScrollableWidget::resize_event(ResizeEvent& event)
 {
     Frame::resize_event(event);
+    update_scrollbar_visibility();
     update_scrollbar_ranges();
 }
 
@@ -125,26 +126,22 @@ Gfx::IntSize AbstractScrollableWidget::excess_size() const
     return { excess_width, excess_height };
 }
 
-void AbstractScrollableWidget::update_scrollbar_ranges()
+void AbstractScrollableWidget::set_should_hide_unnecessary_scrollbars(bool should_hide_unnecessary_scrollbars)
 {
-    if (should_hide_unnecessary_scrollbars()) {
-        if (excess_size().height() - height_occupied_by_horizontal_scrollbar() <= 0 && excess_size().width() - width_occupied_by_vertical_scrollbar() <= 0) {
-            m_horizontal_scrollbar->set_visible(false);
-            m_vertical_scrollbar->set_visible(false);
-        } else {
-            auto vertical_initial_visibility = m_vertical_scrollbar->is_visible();
-            auto horizontal_initial_visibility = m_horizontal_scrollbar->is_visible();
-
-            m_vertical_scrollbar->set_visible(excess_size().height() > 0);
-            m_horizontal_scrollbar->set_visible(excess_size().width() > 0);
-
-            if (m_vertical_scrollbar->is_visible() != vertical_initial_visibility)
-                m_horizontal_scrollbar->set_visible(excess_size().width() > 0);
-            if (m_horizontal_scrollbar->is_visible() != horizontal_initial_visibility)
-                m_vertical_scrollbar->set_visible(excess_size().height() > 0);
-        }
+    if (m_should_hide_unnecessary_scrollbars == should_hide_unnecessary_scrollbars)
+        return;
+
+    m_should_hide_unnecessary_scrollbars = should_hide_unnecessary_scrollbars;
+    if (should_hide_unnecessary_scrollbars)
+        update_scrollbar_ranges();
+    else {
+        m_horizontal_scrollbar->set_visible(true);
+        m_vertical_scrollbar->set_visible(true);
     }
+}
 
+void AbstractScrollableWidget::update_scrollbar_ranges()
+{
     m_horizontal_scrollbar->set_range(0, excess_size().width());
     m_horizontal_scrollbar->set_page_step(visible_content_rect().width() - m_horizontal_scrollbar->step());
 
@@ -152,6 +149,29 @@ void AbstractScrollableWidget::update_scrollbar_ranges()
     m_vertical_scrollbar->set_page_step(visible_content_rect().height() - m_vertical_scrollbar->step());
 }
 
+void AbstractScrollableWidget::update_scrollbar_visibility()
+{
+    if (should_hide_unnecessary_scrollbars()) {
+        // If there has not been a min_size set, the content_size can be used as a substitute
+        auto effective_min_content_size = m_min_content_size;
+        if (m_min_content_size == Gfx::IntSize {})
+            effective_min_content_size = m_content_size;
+        int horizontal_buffer = rect().width() - 2 * frame_thickness() - effective_min_content_size.width();
+        int vertical_buffer = rect().height() - 2 * frame_thickness() - effective_min_content_size.height();
+        bool horizontal_scrollbar_should_be_visible = false, vertical_scrollbar_should_be_visible = false;
+        vertical_scrollbar_should_be_visible = vertical_buffer < 0;
+        if (vertical_scrollbar_should_be_visible)
+            horizontal_buffer -= m_vertical_scrollbar->width();
+        horizontal_scrollbar_should_be_visible = horizontal_buffer < 0;
+        if (horizontal_scrollbar_should_be_visible)
+            vertical_buffer -= m_horizontal_scrollbar->height();
+        vertical_scrollbar_should_be_visible = vertical_buffer < 0;
+
+        m_horizontal_scrollbar->set_visible(horizontal_scrollbar_should_be_visible);
+        m_vertical_scrollbar->set_visible(vertical_scrollbar_should_be_visible);
+    }
+}
+
 void AbstractScrollableWidget::set_content_size(Gfx::IntSize const& size)
 {
     if (m_content_size == size)
@@ -160,6 +180,14 @@ void AbstractScrollableWidget::set_content_size(Gfx::IntSize const& size)
     update_scrollbar_ranges();
 }
 
+void AbstractScrollableWidget::set_min_content_size(Gfx::IntSize const& min_size)
+{
+    if (m_min_content_size == min_size)
+        return;
+    m_min_content_size = min_size;
+    update_scrollbar_ranges();
+}
+
 void AbstractScrollableWidget::set_size_occupied_by_fixed_elements(Gfx::IntSize const& size)
 {
     if (m_size_occupied_by_fixed_elements == size)

+ 6 - 2
Userland/Libraries/LibGUI/AbstractScrollableWidget.h

@@ -21,6 +21,7 @@ public:
     Gfx::IntSize content_size() const { return m_content_size; }
     int content_width() const { return m_content_size.width(); }
     int content_height() const { return m_content_size.height(); }
+    Gfx::IntSize min_content_size() const { return m_min_content_size; }
 
     Gfx::IntRect visible_content_rect() const;
 
@@ -60,7 +61,7 @@ public:
 
     virtual Margins content_margins() const override;
 
-    void set_should_hide_unnecessary_scrollbars(bool b) { m_should_hide_unnecessary_scrollbars = b; }
+    void set_should_hide_unnecessary_scrollbars(bool);
     bool should_hide_unnecessary_scrollbars() const { return m_should_hide_unnecessary_scrollbars; }
 
     Gfx::IntPoint to_content_position(Gfx::IntPoint const& widget_position) const;
@@ -76,9 +77,12 @@ protected:
     virtual void mousewheel_event(MouseEvent&) override;
     virtual void did_scroll() { }
     void set_content_size(Gfx::IntSize const&);
+    void set_min_content_size(Gfx::IntSize const&);
     void set_size_occupied_by_fixed_elements(Gfx::IntSize const&);
     virtual void on_automatic_scrolling_timer_fired() {};
     int autoscroll_threshold() const { return m_autoscroll_threshold; }
+    void update_scrollbar_visibility();
+    void update_scrollbar_ranges();
 
 private:
     class AbstractScrollableWidgetScrollbar final : public Scrollbar {
@@ -100,13 +104,13 @@ private:
     };
     friend class ScrollableWidgetScrollbar;
 
-    void update_scrollbar_ranges();
     void handle_wheel_event(MouseEvent&, Widget&);
 
     RefPtr<AbstractScrollableWidgetScrollbar> m_vertical_scrollbar;
     RefPtr<AbstractScrollableWidgetScrollbar> m_horizontal_scrollbar;
     RefPtr<Widget> m_corner_widget;
     Gfx::IntSize m_content_size;
+    Gfx::IntSize m_min_content_size;
     Gfx::IntSize m_size_occupied_by_fixed_elements;
     bool m_scrollbars_enabled { true };
     bool m_should_hide_unnecessary_scrollbars { false };