[gitg/vala] Added various classes for rendering lanes



commit 0a8d1985ebbc1cbfd912eac962d034cb3bcb50ea
Author: Jesse van den Kieboom <jesse vandenkieboom epfl ch>
Date:   Sun Apr 22 15:13:02 2012 +0200

    Added various classes for rendering lanes

 libgitg-gtk/Makefile.am                       |    8 +-
 libgitg-gtk/gitg-gtk-cell-renderer-lanes.vala |  214 ++++++++++++++++
 libgitg-gtk/gitg-gtk-commit-list-view.vala    |   74 ++++++
 libgitg-gtk/gitg-gtk-label-renderer.vala      |  332 +++++++++++++++++++++++++
 4 files changed, 625 insertions(+), 3 deletions(-)
---
diff --git a/libgitg-gtk/Makefile.am b/libgitg-gtk/Makefile.am
index 1972003..6aeafcc 100644
--- a/libgitg-gtk/Makefile.am
+++ b/libgitg-gtk/Makefile.am
@@ -33,9 +33,11 @@ VALAFLAGS = 				\
 
 GitgGtk-1.0.gir: libgitg-gtk-1.0.la
 
-VALA_FILES =				\
-	gitg-gtk-commit-list-view.vala	\
-	gitg-gtk-commit-model.vala
+VALA_FILES =					\
+	gitg-gtk-commit-model.vala		\
+	gitg-gtk-label-renderer.vala		\
+	gitg-gtk-cell-renderer-lanes.vala	\
+	gitg-gtk-commit-list-view.vala
 
 # Ignore all warnings for vala code...
 libgitg_gtk_1_0_la_CFLAGS =		\
diff --git a/libgitg-gtk/gitg-gtk-cell-renderer-lanes.vala b/libgitg-gtk/gitg-gtk-cell-renderer-lanes.vala
new file mode 100644
index 0000000..c2b080a
--- /dev/null
+++ b/libgitg-gtk/gitg-gtk-cell-renderer-lanes.vala
@@ -0,0 +1,214 @@
+namespace GitgGtk
+{
+	class CellRendererLanes : Gtk.CellRendererText
+	{
+		public Gitg.Commit? commit { get; set; }
+		public Gitg.Commit? next_commit { get; set; }
+		public uint lane_width { get; set; default = 16; }
+		public uint dot_width { get; set; default = 10; }
+		public unowned SList<Gitg.Ref> labels { get; set; }
+
+		private int d_last_height;
+
+		private uint num_lanes
+		{
+			get { return commit.lanes.length(); }
+		}
+
+		private uint total_width(Gtk.Widget widget)
+		{
+			return num_lanes * lane_width +
+			       LabelRenderer.width(widget, font_desc, labels);
+		}
+
+		public override void get_size(Gtk.Widget     widget,
+		                              Gdk.Rectangle? area,
+		                              out int        xoffset,
+		                              out int        yoffset,
+		                              out int        width,
+		                              out int        height)
+		{
+			xoffset = 0;
+			yoffset = 0;
+
+			width = (int)total_width(widget);
+			height = area != null ? area.height : 1;
+		}
+
+		private void draw_arrow(Cairo.Context context,
+		                        Gdk.Rectangle area,
+		                        uint          laneidx,
+		                        bool          top)
+		{
+			double cw = lane_width;
+			double xpos = area.x + laneidx * cw + cw / 2.0;
+			double df = (top ? -1 : 1) * 0.25 * area.height;
+			double ypos = area.y + area.height / 2.0 + df;
+			double q = cw / 4.0;
+
+			context.move_to(xpos - q, ypos + (top ? q : -q));
+			context.line_to(xpos, ypos);
+			context.line_to(xpos + q, ypos + (top ? q : -q));
+			context.stroke();
+
+			context.move_to(xpos, ypos);
+			context.line_to(xpos, ypos - df);
+			context.stroke();
+		}
+
+		private void draw_arrows(Cairo.Context context,
+		                         Gdk.Rectangle area)
+		{
+			uint to = 0;
+
+			foreach (Gitg.Lane lane in commit.lanes)
+			{
+				var color = lane.color;
+				context.set_source_rgb(color.r, color.g, color.b);
+
+				if (lane.tag == Gitg.LaneTag.START)
+				{
+					draw_arrow(context, area, to, true);
+				}
+				else if (lane.tag == Gitg.LaneTag.END)
+				{
+					draw_arrow(context, area, to, false);
+				}
+
+				++to;
+			}
+		}
+
+		private void draw_paths_real(Cairo.Context context,
+		                             Gdk.Rectangle area,
+		                             Gitg.Commit?  commit,
+		                             double        yoffset)
+		{
+			if (commit == null)
+			{
+				return;
+			}
+
+			int to = 0;
+			double cw = lane_width;
+			double ch = area.height / 2.0;
+
+			foreach (var lane in commit.lanes)
+			{
+				var color = lane.color;
+				context.set_source_rgb(color.r, color.g, color.b);
+
+				foreach (var from in lane.from)
+				{
+					double x1 = area.x + from * cw + cw / 2.0;
+					double x2 = area.x + to * cw + cw / 2.0;
+					double y1 = area.y + yoffset * ch;
+					double y2 = area.y + (yoffset + 1) * ch;
+					double y3 = area.y + (yoffset + 2) * ch;
+
+					context.move_to(x1, y1);
+					context.curve_to(x1, y2, x2, y2, x2, y3);
+					context.stroke();
+				}
+
+				++to;
+			}
+		}
+
+		private void draw_top_paths(Cairo.Context context,
+		                            Gdk.Rectangle area)
+		{
+			draw_paths_real(context, area, commit, -1);
+		}
+
+		private void draw_bottom_paths(Cairo.Context context,
+		                               Gdk.Rectangle area)
+		{
+			draw_paths_real(context, area, next_commit, 1);
+		}
+
+		private void draw_paths(Cairo.Context context,
+		                        Gdk.Rectangle area)
+		{
+			context.set_line_width(2.0);
+			context.set_line_cap(Cairo.LineCap.ROUND);
+
+			draw_top_paths(context, area);
+			draw_bottom_paths(context, area);
+			draw_arrows(context, area);
+		}
+
+		private void draw_indicator(Cairo.Context context,
+		                            Gdk.Rectangle area)
+		{
+			double offset;
+			double radius;
+
+			offset = commit.mylane * lane_width + (lane_width - dot_width) / 2.0;
+			radius = dot_width / 2.0;
+
+			context.set_line_width(2.0);
+
+			context.arc(area.x + offset + radius,
+			            area.y + area.height / 2.0,
+			            radius,
+			            0,
+			            2 * Math.PI);
+
+			context.set_source_rgb(0, 0, 0);
+			context.stroke_preserve();
+
+			if (commit.lane != null)
+			{
+				var color = commit.lane.color;
+				context.set_source_rgb(color.r, color.g, color.b);
+			}
+
+			context.fill();
+		}
+
+		private void draw_labels(Cairo.Context context,
+		                         Gdk.Rectangle area,
+		                         Gtk.Widget    widget)
+		{
+			uint offset;
+
+			offset = num_lanes * lane_width;
+
+			context.translate(offset, 0);
+			LabelRenderer.draw(widget, font_desc, context, labels, area);
+		}
+
+		public override void render(Cairo.Context         context,
+		                            Gtk.Widget            widget,
+		                            Gdk.Rectangle         area,
+		                            Gdk.Rectangle         cell_area,
+		                            Gtk.CellRendererState flags)
+		{
+			d_last_height = area.height;
+
+			context.save();
+
+			Gdk.cairo_rectangle(context, area);
+			context.clip();
+
+			draw_paths(context, area);
+			draw_indicator(context, area);
+			draw_labels(context, area, widget);
+
+			var narea = area;
+			var ncell_area = cell_area;
+
+			var tw = total_width(widget);
+
+			narea.x += (int)tw;
+			ncell_area.x += (int)tw;
+
+			context.restore();
+
+			base.render(context, widget, narea, ncell_area, flags);
+		}
+	}
+}
+
+// vi:ts=4
diff --git a/libgitg-gtk/gitg-gtk-commit-list-view.vala b/libgitg-gtk/gitg-gtk-commit-list-view.vala
new file mode 100644
index 0000000..96224d6
--- /dev/null
+++ b/libgitg-gtk/gitg-gtk-commit-list-view.vala
@@ -0,0 +1,74 @@
+namespace GitgGtk
+{
+	public class CommitListView : Gtk.TreeView, Gtk.Buildable
+	{
+		public CommitListView(GitgGtk.CommitModel model)
+		{
+			Object(model: model);
+		}
+
+		public CommitListView.for_repository(Gitg.Repository repository)
+		{
+			this(new GitgGtk.CommitModel(repository));
+		}
+
+		private void lanes_data_func(Gtk.CellLayout   layout,
+		                             Gtk.CellRenderer cell,
+		                             Gtk.TreeModel    model,
+		                             Gtk.TreeIter     iter)
+		{
+			CommitModel? m = model as CommitModel;
+
+			if (m == null)
+			{
+				return;
+			}
+
+			CellRendererLanes lanes = (CellRendererLanes)cell;
+			Gitg.Commit? commit = m.commit_from_iter(iter);
+
+			if (commit == null)
+			{
+				return;
+			}
+
+			var cp = iter;
+			Gitg.Commit? next_commit = null;
+
+			if (m.iter_next(ref cp))
+			{
+				next_commit = m.commit_from_iter(cp);
+			}
+
+			unowned SList<Gitg.Ref> labels = m.repository.refs_for_id(commit.get_id());
+
+			lanes.commit = commit;
+			lanes.next_commit = next_commit;
+			lanes.labels = labels;
+		}
+
+		private void parser_finished(Gtk.Builder builder)
+		{
+			base.parser_finished(builder);
+
+			// Check if there is a cell renderer
+			foreach (var column in get_columns())
+			{
+				foreach (var cell in column.get_cells())
+				{
+					CellRendererLanes? lanes = cell as CellRendererLanes;
+
+					if (lanes == null)
+					{
+						continue;
+					}
+
+					column.set_cell_data_func(lanes,
+					                          lanes_data_func);
+				}
+			}
+		}
+	}
+}
+
+// vi:ts=4
diff --git a/libgitg-gtk/gitg-gtk-label-renderer.vala b/libgitg-gtk/gitg-gtk-label-renderer.vala
new file mode 100644
index 0000000..4319acf
--- /dev/null
+++ b/libgitg-gtk/gitg-gtk-label-renderer.vala
@@ -0,0 +1,332 @@
+namespace GitgGtk
+{
+	public class LabelRenderer
+	{
+		private static const int margin = 3;
+		private static const int padding = 4;
+
+		private static string label_text(Gitg.Ref r)
+		{
+			var escaped = Markup.escape_text(r.parsed_name.shortname);
+			return "<span size='smaller'>%s</span>".printf(escaped);
+		}
+
+		private static int get_label_width(Pango.Layout layout,
+		                                   Gitg.Ref     r)
+		{
+			var smaller = label_text(r);
+
+			int w;
+
+			layout.set_markup(smaller, -1);
+			layout.get_pixel_size(out w, null);
+
+			return w + padding * 2;
+		}
+
+		public static int width(Gtk.Widget             widget,
+		                        Pango.FontDescription *font,
+		                        SList<Gitg.Ref>        labels)
+		{
+			if (labels == null)
+			{
+				return 0;
+			}
+
+			int ret = 0;
+
+			var ctx = widget.get_pango_context();
+			var layout = new Pango.Layout(ctx);
+
+			layout.set_font_description(font);
+
+			foreach (Gitg.Ref r in labels)
+			{
+				ret += get_label_width(layout, r) + margin;
+			}
+
+			return ret + margin;
+		}
+
+		private static void
+		rounded_rectangle(Cairo.Context context,
+		                  double        x,
+		                  double        y,
+		                  double        width,
+		                  double        height,
+		                  double        radius)
+		{
+			context.move_to(x + radius, y);
+			context.rel_line_to(width - 2 * radius, 0);
+			context.arc(x + width - radius, y + radius, radius, 1.5 * Math.PI, 0.0);
+
+			context.rel_line_to(0, height - 2 * radius);
+			context.arc(x + width - radius, y + height - radius, radius, 0.0, 0.5 * Math.PI);
+
+			context.rel_line_to(-(width - radius * 2), 0);
+			context.arc(x + radius, y + height - radius, radius, 0.5 * Math.PI, Math.PI);
+
+			context.rel_line_to(0, -(height - radius * 2));
+			context.arc(x + radius, y + radius, radius, Math.PI, 1.5 * Math.PI);
+		}
+
+		private static void get_type_color(Gitg.RefType type,
+		                                   out double   r,
+		                                   out double   g,
+		                                   out double   b)
+		{
+			switch (type)
+			{
+				case Gitg.RefType.NONE:
+					r = 1;
+					g = 1;
+					b = 0.8;
+				break;
+				case Gitg.RefType.BRANCH:
+					r = 0.8;
+					g = 1;
+					b = 0.5;
+				break;
+				case Gitg.RefType.REMOTE:
+					r = 0.5;
+					g = 0.8;
+					b = 1;
+				break;
+				case Gitg.RefType.TAG:
+					r = 1;
+					g = 1;
+					b = 0;
+				break;
+				case Gitg.RefType.STASH:
+					r = 1;
+					g = 0.8;
+					b = 0.5;
+				break;
+				default:
+					r = 1;
+					g = 1;
+					b = 1;
+				break;
+			}
+		}
+
+		private static void get_ref_color(Gitg.Ref   rref,
+		                                  out double r,
+		                                  out double g,
+		                                  out double b)
+		{
+			if (rref.working)
+			{
+				r = 1;
+				g = 0.7;
+				b = 0;
+			}
+			else
+			{
+				get_type_color(rref.parsed_name.rtype, out r, out g, out b);
+			}
+		}
+
+		private static void set_source_for_ref_type(Cairo.Context context,
+		                                            Gitg.Ref      rref,
+		                                            bool          use_state)
+		{
+			if (use_state)
+			{
+				switch (rref.state)
+				{
+					case Gitg.RefState.SELECTED:
+						context.set_source_rgb(1, 1, 1);
+						return;
+					case Gitg.RefState.PRELIGHT:
+					{
+						double r, g, b;
+
+						get_ref_color(rref, out r, out g, out b);
+						context.set_source_rgba(r, g, b, 0.3);
+						return;
+					}
+				}
+			}
+
+			double r, g, b;
+			get_ref_color(rref, out r, out g, out b);
+
+			context.set_source_rgb(r, g, b);
+		}
+
+		private static int render_label(Cairo.Context context,
+		                                Pango.Layout  layout,
+		                                Gitg.Ref      r,
+		                                int           x,
+		                                int           y,
+		                                int           height,
+		                                bool          use_state)
+		{
+			var smaller = label_text(r);
+
+			layout.set_markup(smaller, -1);
+
+			int w;
+			int h;
+
+			layout.get_pixel_size(out w, out h);
+
+			rounded_rectangle(context,
+			                  x + 0.5,
+			                  y + margin + 0.5,
+			                  w + padding * 2,
+			                  height - margin * 2,
+			                  5);
+
+			set_source_for_ref_type(context, r, use_state);
+			context.fill_preserve();
+
+			context.set_source_rgb(0, 0, 0);
+			context.stroke();
+
+			context.save();
+
+			context.translate(x + padding, y + (height - h) / 2.0 + 0.5);
+			Pango.cairo_show_layout(context, layout);
+
+			context.restore();
+			return w;
+		}
+
+		public static void draw(Gtk.Widget            widget,
+		                        Pango.FontDescription font,
+		                        Cairo.Context         context,
+		                        SList<Gitg.Ref>       labels,
+		                        Gdk.Rectangle         area)
+		{
+			double pos = margin + 0.5;
+
+			context.save();
+			context.set_line_width(1.0);
+
+			var ctx = widget.get_pango_context();
+			var layout = new Pango.Layout(ctx);
+
+			layout.set_font_description(font);
+
+			foreach (Gitg.Ref r in labels)
+			{
+				var w = render_label(context,
+				                     layout,
+				                     r,
+				                     (int)pos,
+				                     area.y,
+				                     area.height,
+				                     true);
+
+				pos += w + padding * 2 + margin;
+			}
+
+			context.restore();
+		}
+
+		public static Gitg.Ref? get_ref_at_pos(Gtk.Widget            widget,
+		                                       Pango.FontDescription font,
+		                                       SList<Gitg.Ref>       labels,
+		                                       int                   x,
+		                                       out int               hot_x)
+		{
+			hot_x = 0;
+
+			if (labels == null)
+			{
+				return null;
+			}
+
+			var ctx = widget.get_pango_context();
+			var layout = new Pango.Layout(ctx);
+
+			layout.set_font_description(font);
+
+			int start = margin;
+			Gitg.Ref? ret = null;
+
+			foreach (Gitg.Ref r in labels)
+			{
+				int width = get_label_width(layout, r);
+
+				if (x >= start && x <= start + width)
+				{
+					ret = r;
+					hot_x = x - start;
+
+					break;
+				}
+
+				start += width + margin;
+			}
+
+			return ret;
+		}
+
+		private static uchar convert_color_channel(uchar color,
+		                                           uchar alpha)
+		{
+			return (uchar)((alpha != 0) ? (color / (alpha / 255.0)) : 0);
+		}
+
+		private static void convert_bgra_to_rgba(uchar[] src,
+		                                         uchar[] dst,
+		                                         int     width,
+		                                         int     height)
+		{
+			int i = 0;
+
+			for (int y = 0; y < height; ++y)
+			{
+				for (int x = 0; x < width; ++x)
+				{
+					dst[i] = convert_color_channel(src[i + 2], src[i + 3]);
+					dst[i + 1] = convert_color_channel(src[i + 1], src[i + 3]);
+					dst[i + 2] = convert_color_channel(src[i], src[i + 3]);
+					dst[i + 3] = src[i + 3];
+
+					i += 4;
+				}
+			}
+		}
+
+		public static Gdk.Pixbuf render_ref(Gtk.Widget            widget,
+		                                    Pango.FontDescription font,
+		                                    Gitg.Ref              r,
+		                                    int                   height,
+		                                    int                   minwidth)
+		{
+			var ctx = widget.get_pango_context();
+			var layout = new Pango.Layout(ctx);
+
+			layout.set_font_description(font);
+
+			int width = int.max(get_label_width(layout, r), minwidth);
+
+			var surface = new Cairo.ImageSurface(Cairo.Format.ARGB32,
+			                                     width + 2,
+			                                     height + 2);
+
+			var context = new Cairo.Context(surface);
+			context.set_line_width(1);
+
+			render_label(context, layout, r, 1, 1, height, false);
+			var data = surface.get_data();
+
+			Gdk.Pixbuf ret = new Gdk.Pixbuf(Gdk.Colorspace.RGB,
+			                                true,
+			                                8,
+			                                width + 2,
+			                                height + 2);
+
+			var pixdata = ret.get_pixels();
+			convert_bgra_to_rgba(data, pixdata, width + 2, height + 2);
+
+			return ret;
+		}
+	}
+}
+
+// vi:ts=4



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