Re: [Banshee-List] More ListView Performance Goodness!



Thanks Bertrand. OK, new patch. Some beautification and explanatory
comments. Get here: http://homepages.nyu.edu/~stp225/listview2.patch

I've also attached the patch to this email - let's home the mailing
list likes me :)
Index: src/Core/Hyena.Gui/Hyena.Data.Gui/ListView/ListView_Windowing.cs
===================================================================
--- src/Core/Hyena.Gui/Hyena.Data.Gui/ListView/ListView_Windowing.cs	(revision 3222)
+++ src/Core/Hyena.Gui/Hyena.Data.Gui/ListView/ListView_Windowing.cs	(working copy)
@@ -159,6 +159,8 @@
             graphics = new ListViewGraphics (this);
             graphics.RefreshColors ();
             
+            CreateCanvases ();
+            
             OnDragSourceSet ();
         }
         
@@ -184,6 +186,11 @@
             list_window.Destroy ();
             list_window = null;
             
+            list_canvas.Dispose ();
+            list_canvas = null;
+            list_canvas_b.Dispose ();
+            list_canvas_b = null;
+            
             base.OnUnrealized ();
         }
         
@@ -233,6 +240,12 @@
             list_alloc.Width = allocation.Width - 2 * left_border_alloc.Width;
             list_alloc.Height = allocation.Height - header_alloc.Height - footer_alloc.Height; 
             list_window.MoveResize (left_border_alloc.Width, header_alloc.Height, list_alloc.Width, list_alloc.Height);
+        
+            list_canvas_alloc.Width = list_alloc.Width;
+            list_canvas_size = (int)Math.Ceiling ((list_alloc.Height + RowHeight) / (double)RowHeight);
+            list_canvas_alloc.Height = list_canvas_size * RowHeight;
+            
+            CreateCanvases ();
         }
         
         protected override void OnSizeRequested (ref Requisition requisition)
Index: src/Core/Hyena.Gui/Hyena.Data.Gui/ListView/ListView_Rendering.cs
===================================================================
--- src/Core/Hyena.Gui/Hyena.Data.Gui/ListView/ListView_Rendering.cs	(revision 3222)
+++ src/Core/Hyena.Gui/Hyena.Data.Gui/ListView/ListView_Rendering.cs	(working copy)
@@ -3,6 +3,7 @@
 //
 // Author:
 //   Aaron Bockover <abockover novell com>
+//   Scott Peterson <lunchtimemama gmail com>
 //
 // Copyright (C) 2007-2008 Novell, Inc.
 //
@@ -52,6 +53,20 @@
         private Pango.Layout header_pango_layout;
         private Pango.Layout list_pango_layout;
         
+        // The list is rendered to an off-screen Drawable; the "canvas". The canvas is large enough to hold
+        // the maximum number of rows that can be seen in the list. When new rows comes into view b/c of
+        // scrolling, the contents of the canvas are shifted up or down (depending on whether the user is
+        // scrolling up or down) to accommodate the new rows. The new rows are then painted in situ on the
+        // canvas. The canvas is then painted onto the list_window, offset properly. This means that a row
+        // is only rendered once when it comes into view and that rendering persists on the canvas for as
+        // long as that row remains in view.
+        private Gdk.Drawable list_canvas;           // The primary off-screen Drawable canvas
+        private Gdk.Drawable list_canvas_b;         // The secondary off-screen Drawable canvas (used for shifting the canvas up and down)
+        private Gdk.Rectangle list_canvas_alloc;    // The bounds of the canvas
+        private int list_canvas_size;               // The number of rows the canvas can hold
+        private int list_canvas_index;              // The number of the row at the top of the canvas
+        private int list_canvas_offset;             // The amount by which to offset the canvas when it is copied to the list_window
+        
         public new void QueueDraw ()
         {
             base.QueueDraw ();
@@ -60,6 +75,16 @@
             InvalidateListWindow ();
             InvalidateFooterWindow ();
         }
+        
+        private void CreateCanvases ()
+        {
+            if (list_canvas != null) {
+                list_canvas.Dispose ();
+                list_canvas_b.Dispose ();
+            }
+            list_canvas = new Gdk.Pixmap (GdkWindow, list_canvas_alloc.Width, list_canvas_alloc.Height);
+            list_canvas_b = new Gdk.Pixmap (GdkWindow, list_canvas_alloc.Width, list_canvas_alloc.Height);
+        }
          
         protected override bool OnExposeEvent (Gdk.EventExpose evnt)
         {            
@@ -69,41 +94,53 @@
             
             return true;
         }
+        
+        private delegate void Painter (Gdk.EventExpose evnt, Gdk.Rectangle clip);
+        
+        private static Pango.Layout default_pango_layout;
+        
+        private static void Paint (Painter code, Gdk.EventExpose evnt, Gdk.Rectangle clip, ref Cairo.Context context)
+        {
+            Paint (code, evnt, clip, evnt.Window, clip, ref context, ref default_pango_layout, false);
+        }
+        
+        private static void Paint (Painter code, Gdk.EventExpose evnt, Gdk.Rectangle clip, ref Cairo.Context context, ref Pango.Layout layout)
+        {
+            Paint (code, evnt, clip, evnt.Window, clip, ref context, ref layout, true);
+        }
+        
+        private static void Paint (Painter code, Gdk.EventExpose evnt, Gdk.Rectangle clip, Gdk.Drawable canvas, Gdk.Rectangle canvas_clip, ref Cairo.Context context, ref Pango.Layout layout, bool has_layout)
+        {
+            context = CairoHelper.CreateCairoDrawable (canvas);
+            context.Rectangle (canvas_clip.X, canvas_clip.Y, canvas_clip.Width, canvas_clip.Height);
+            context.Clip ();
+            
+            if (has_layout && layout == null) {
+                layout = Pango.CairoHelper.CreateLayout (context);
+            }
+            
+            code (evnt, clip);
+            
+            ((IDisposable)context.Target).Dispose ();
+            ((IDisposable)context).Dispose ();
+        }
                 
         private void PaintRegion (Gdk.EventExpose evnt, Gdk.Rectangle clip)
         {
-            Cairo.Context cr = CairoHelper.CreateCairoDrawable (evnt.Window);
-            cr.Rectangle (clip.X, clip.Y, clip.Width, clip.Height);
-            cr.Clip ();
-            
             if (evnt.Window == header_window) {
-                header_cr = cr;
-                if (header_pango_layout == null) {
-                    header_pango_layout = Pango.CairoHelper.CreateLayout (header_cr);
-                }
-                PaintHeader (evnt.Area);
+                Paint (PaintHeader, evnt, clip, ref header_cr, ref header_pango_layout);
             } else if (evnt.Window == footer_window) {
-                footer_cr = cr;
-                PaintFooter (evnt, clip);
+                Paint (PaintFooter, evnt, clip, ref footer_cr);
             } else if (evnt.Window == left_border_window) {
-                left_border_cr = cr;
-                PaintLeftBorder(evnt, clip);
+                Paint (PaintLeftBorder, evnt, clip, ref left_border_cr);
             } else if (evnt.Window == right_border_window) {
-                right_border_cr = cr;
-                PaintRightBorder(evnt, clip);
+                Paint (PaintRightBorder, evnt, clip, ref right_border_cr);
             } else if (evnt.Window == list_window) {
-                list_cr = cr;
-                if (list_pango_layout == null) {
-                    list_pango_layout = Pango.CairoHelper.CreateLayout (list_cr);
-                }
                 PaintList (evnt, clip);
             }
-            
-            ((IDisposable)cr.Target).Dispose ();
-            ((IDisposable)cr).Dispose ();
         }
         
-        private void PaintHeader (Gdk.Rectangle clip)
+        private void PaintHeader (Gdk.EventExpose evnt, Gdk.Rectangle clip)
         {
             graphics.DrawHeaderBackground (header_cr, header_alloc, 2, header_visible);
             
@@ -185,22 +222,110 @@
             if (model == null) {
                 return;
             }
-
+            
+            bool paint = false;
             int vadjustment_value = (int)vadjustment.Value;
-            int first_row = vadjustment_value / RowHeight;
-            int last_row = Math.Min (model.Count, first_row + RowsInView);     
+            list_canvas_offset = vadjustment_value % RowHeight;
+            clip = list_canvas_alloc;
+            
+            // If we're not scrolling, paint all rows
+            if (!scrolled) {
+                list_canvas.DrawRectangle (Style.WhiteGC, true, list_canvas_alloc);
+                paint = true;
+            } else {
+                int index = vadjustment_value / RowHeight;
+                
+                // If new rows have come into view at the bottom...
+                if (index > list_canvas_index) {
+                    int delta = index - list_canvas_index;
+                    
+                    // If some of the same rows are still visible, shift the contents of the canvas up
+                    if (delta < list_canvas_size) { 
+                        clip.Height = delta * RowHeight;
+                        clip.Y = list_canvas_alloc.Height - clip.Height;
+                        int height = list_canvas_alloc.Height - clip.Height;
+                        
+                        // Copy the offset contents of the canvas to the secondary canvas and make the
+                        // secondary canvas the primary canvas. This is just about the fastest way of
+                        // doing this (Pixbuf.GetFromDrawable is heinously slow)
+                        list_canvas_b.DrawRectangle (Style.WhiteGC, true, list_canvas_alloc);
+                        list_canvas_b.DrawDrawable (Style.WhiteGC, list_canvas, 0, clip.Height, 0, 0, clip.Width, height);
+                        
+                        Gdk.Drawable tmp = list_canvas;
+                        list_canvas = list_canvas_b;
+                        list_canvas_b = tmp;
+                    } else {
+                        list_canvas.DrawRectangle (Style.WhiteGC, true, list_canvas_alloc);
+                    }
+                    
+                    // If everything is selected, paint all rows (it's faster this way)
+                    if (Selection.Count == model.Count) {
+                        clip = list_canvas_alloc;
+                    }
+                    
+                    paint = true;
+                }
+                // If new rows have come into view at the top...
+                else if (index < list_canvas_index) {
+                    int delta = list_canvas_index - index;
+                    
+                    // If some of the same rows are still visible, shift the contents of the canvas down
+                    if (delta < list_canvas_size) {
+                        clip.Height = delta * RowHeight;
+                        int height = list_canvas_alloc.Height - clip.Height;
+                        
+                        // Copy the offset contents of the canvas to the secondary canvas and make the
+                        // secondary canvas the primary canvas. This is just about the fastest way of
+                        // doing this (Pixbuf.GetFromDrawable is heinously slow)
+                        list_canvas_b.DrawRectangle (Style.WhiteGC, true, list_canvas_alloc);
+                        list_canvas_b.DrawDrawable (Style.WhiteGC, list_canvas, 0, 0, 0, clip.Height, clip.Width, height);
+                        
+                        Gdk.Drawable tmp = list_canvas;
+                        list_canvas = list_canvas_b;
+                        list_canvas_b = tmp;
+                    } else {
+                        list_canvas.DrawRectangle (Style.WhiteGC, true, list_canvas_alloc);
+                    }
+                    
+                    // If everything is selected, paint all rows (it's faster this way)
+                    if (Selection.Count == model.Count) {
+                        clip = list_canvas_alloc;
+                    }
+                    
+                    paint = true;
+                }
+                
+                list_canvas_index = index;
+                scrolled = false;
+            }
+            
+            // Paint the rows within the bounds of 'clip'
+            if (paint) {
+                Paint (PaintRows, evnt, clip, list_canvas, list_canvas_alloc, ref list_cr, ref list_pango_layout, true);
+            }
+            
+            // Copy the canvas to the list_window, accounting for the offset
+            list_window.DrawDrawable (Style.WhiteGC, list_canvas, 0, list_canvas_offset, 0, 0, list_alloc.Width, list_alloc.Height);
+        }
 
-            Gdk.Rectangle selected_focus_alloc = Gdk.Rectangle.Zero;
+        private void PaintRows (Gdk.EventExpose evnt, Gdk.Rectangle clip)
+        {
+            int first_row = list_canvas_index + clip.Y / RowHeight;
+            int last_row = Math.Min (model.Count, first_row + clip.Height / RowHeight);
+
             Gdk.Rectangle single_list_alloc = new Gdk.Rectangle ();
             
-            single_list_alloc.Width = list_alloc.Width;
+            single_list_alloc.Width = clip.Width;
             single_list_alloc.Height = RowHeight;
-            single_list_alloc.X = list_alloc.X;
-            single_list_alloc.Y = list_alloc.Y - vadjustment_value + (first_row * single_list_alloc.Height);
+            single_list_alloc.X = clip.X;
+            single_list_alloc.Y = clip.Y;
             
             int selection_height = 0;
             int selection_y = 0;
             List<int> selected_rows = new List<int> ();
+            
+            bool paint_all = last_row - first_row == list_canvas_size;
+            bool focused_row_in_view = false;
 
             for (int ri = first_row; ri < last_row; ri++) {
                 if (Selection.Contains (ri)) {
@@ -211,9 +336,50 @@
                     selection_height += single_list_alloc.Height;
                     selected_rows.Add (ri);
                     
-                    if (focused_row_index == ri) {
-                        selected_focus_alloc = single_list_alloc;
+                    if (ri == focused_row_index) {
+                        focused_row_in_view = true;
                     }
+                    
+                    // If a new row has come into view and it's part of a block selection which
+                    // includes rows that we're not scheduled to paint, then we need to make sure
+                    // the contiguous selection is drawn properly.
+                    if (!paint_all) {
+                        
+                        // If this is the last row we're rendering at the top of the list...
+                        if (ri == last_row - 1 && first_row == list_canvas_index) {
+                            
+                            // ...extend the selection block down until we get to the bottom of
+                            // the canvas, or the selection block terminates
+                            while (Selection.Contains (++ri) && ri < list_canvas_index + list_canvas_size) {
+                                selection_height += single_list_alloc.Height;
+                                selected_rows.Add (ri);
+                                
+                                // And if the selection block covers the focused row, we'll
+                                // want to re-render the focus visual
+                                if (ri == focused_row_index) {
+                                    focused_row_in_view = true;
+                                }
+                            }
+                        }
+                        // If this is the first row we're rending at the bottom of the list...
+                        else if (ri == first_row && (last_row == list_canvas_index + list_canvas_size || last_row == model.Count)) {
+                            int i = ri;
+                            
+                            // ...extend the selection block up until we get to the top of the
+                            // canvas, or the selection block terminates
+                            while (Selection.Contains (--i) && i >= list_canvas_index) {
+                                selection_height += single_list_alloc.Height;
+                                selection_y -= single_list_alloc.Height;
+                                selected_rows.Add (i);
+                                
+                                // And if the selection block covers the focused row, we'll
+                                // want to re-render the focus visual
+                                if (i == focused_row_index) {
+                                    focused_row_in_view = true;
+                                }
+                            }
+                        }
+                    }
                 } else {
                     if (rules_hint && ri % 2 != 0) {
                         graphics.DrawRowRule (list_cr, single_list_alloc.X, single_list_alloc.Y, 
@@ -238,7 +404,7 @@
                     
                     if (selection_height > 0) {
                         graphics.DrawRowSelection (
-                            list_cr, list_alloc.X, list_alloc.Y + selection_y, list_alloc.Width, selection_height);
+                            list_cr, list_canvas_alloc.X, list_canvas_alloc.Y + selection_y, list_canvas_alloc.Width, selection_height);
                         selection_height = 0;
                     }
                     
@@ -249,18 +415,19 @@
             }
             
             if (selection_height > 0) {
-                graphics.DrawRowSelection (list_cr, list_alloc.X, list_alloc.Y + selection_y, 
-                    list_alloc.Width, selection_height);
+                graphics.DrawRowSelection (list_cr, list_canvas_alloc.X, selection_y, 
+                    list_canvas_alloc.Width, selection_height);
             }
             
-            if (Selection.Count > 1 && !selected_focus_alloc.Equals (Gdk.Rectangle.Zero)) {
-                graphics.DrawRowSelection (list_cr, selected_focus_alloc.X, selected_focus_alloc.Y, 
-                    selected_focus_alloc.Width, selected_focus_alloc.Height, false, true, 
+            if (focused_row_in_view) {
+                int focused_y = (focused_row_index - list_canvas_index) * single_list_alloc.Height;
+                graphics.DrawRowSelection (list_cr, single_list_alloc.X, focused_y, 
+                    single_list_alloc.Width, single_list_alloc.Height, false, true, 
                     graphics.GetWidgetColor (GtkColorClass.Dark, StateType.Selected));
             }
             
             foreach (int ri in selected_rows) {
-                single_list_alloc.Y = ri * single_list_alloc.Height - vadjustment_value;
+                single_list_alloc.Y = (ri - list_canvas_index) * single_list_alloc.Height;
                 PaintRow (ri, clip, single_list_alloc, StateType.Selected);
             }
             
@@ -312,7 +479,7 @@
             
             list_cr.Save ();
             list_cr.Translate (clip.X, clip.Y);
-            cell.Render (new CellContext (list_cr, list_pango_layout, this, list_window, graphics, area), 
+            cell.Render (new CellContext (list_cr, list_pango_layout, this, list_canvas, graphics, area), 
                 dragging? StateType.Normal : state, area.Width, area.Height);
             list_cr.Restore ();
         }
@@ -326,6 +493,7 @@
             CachedColumn column = column_cache[pressed_column_index];
             
             int x = pressed_column_x_drag;
+            int y = list_alloc.Y + list_canvas_offset;
             
             Cairo.Color fill_color = graphics.GetWidgetColor (GtkColorClass.Base, StateType.Normal);
             fill_color.A = 0.45;
@@ -334,14 +502,14 @@
                 GtkColorClass.Base, StateType.Normal), 0.0);
             stroke_color.A = 0.3;
             
-            list_cr.Rectangle (x, list_alloc.Y, column.Width, list_alloc.Height);
+            list_cr.Rectangle (x, y, column.Width, list_alloc.Height);
             list_cr.Color = fill_color;
             list_cr.Fill ();
             
-            list_cr.MoveTo (x, list_alloc.Y);
-            list_cr.LineTo (x, list_alloc.Y + list_alloc.Height - 1.0);
-            list_cr.LineTo (x + column.Width, list_alloc.Y + list_alloc.Height - 1.0);
-            list_cr.LineTo (x + column.Width, list_alloc.Y);
+            list_cr.MoveTo (x, y);
+            list_cr.LineTo (x, y + list_alloc.Height - 1.0);
+            list_cr.LineTo (x + column.Width, y + list_alloc.Height - 1.0);
+            list_cr.LineTo (x + column.Width, y);
             
             list_cr.Color = stroke_color;
             list_cr.Antialias = Cairo.Antialias.None;
Index: src/Core/Hyena.Gui/Hyena.Data.Gui/ListView/ListView_Interaction.cs
===================================================================
--- src/Core/Hyena.Gui/Hyena.Data.Gui/ListView/ListView_Interaction.cs	(revision 3222)
+++ src/Core/Hyena.Gui/Hyena.Data.Gui/ListView/ListView_Interaction.cs	(working copy)
@@ -458,8 +458,11 @@
             vadjustment.Change ();
         }
         
+        private bool scrolled;
+        
         private void OnAdjustmentChanged (object o, EventArgs args)
         {
+            scrolled = true;
             InvalidateListWindow ();
         }
         


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]