Re: [Banshee-List] More ListView Performance Goodness!
- From: "Scott Peterson" <lunchtimemama gmail com>
- To: banshee-list gnome org
- Subject: Re: [Banshee-List] More ListView Performance Goodness!
- Date: Tue, 12 Feb 2008 01:30:22 -0500
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]