[hyena/canvas: 3/5] [Hyena.Gui.Canvas] Copy from Cubano



commit f3e3f5f5f61372595c7d8b27aff9351d7a86c6c7
Author: Gabriel Burt <gabriel burt gmail com>
Date:   Thu Oct 21 18:51:48 2010 -0500

    [Hyena.Gui.Canvas] Copy from Cubano
    
    Except Size, Rect, and Thickness which were already here and more
    updated than Cubano's version.

 Hyena.Gui/Hyena.Gui.Canvas/AnimationManager.cs     |  302 +++++++++++++++
 Hyena.Gui/Hyena.Gui.Canvas/Brush.cs                |   81 ++++
 Hyena.Gui/Hyena.Gui.Canvas/CanvasHost.cs           |  343 +++++++++++++++++
 Hyena.Gui/Hyena.Gui.Canvas/CanvasItem.cs           |  405 ++++++++++++++++++++
 Hyena.Gui/Hyena.Gui.Canvas/CanvasItemCollection.cs |  106 +++++
 Hyena.Gui/Hyena.Gui.Canvas/CanvasManager.cs        |   64 +++
 Hyena.Gui/Hyena.Gui.Canvas/FixedPanel.cs           |   37 ++
 Hyena.Gui/Hyena.Gui.Canvas/FontWeight.cs           |   36 ++
 Hyena.Gui/Hyena.Gui.Canvas/Image.cs                |   67 ++++
 Hyena.Gui/Hyena.Gui.Canvas/ImageBrush.cs           |   82 ++++
 Hyena.Gui/Hyena.Gui.Canvas/MarginStyle.cs          |   43 ++
 Hyena.Gui/Hyena.Gui.Canvas/Orientation.cs          |   36 ++
 Hyena.Gui/Hyena.Gui.Canvas/Panel.cs                |  148 +++++++
 Hyena.Gui/Hyena.Gui.Canvas/Rect.cs                 |   64 ++--
 Hyena.Gui/Hyena.Gui.Canvas/ShadowMarginStyle.cs    |   89 +++++
 Hyena.Gui/Hyena.Gui.Canvas/Size.cs                 |   28 +-
 Hyena.Gui/Hyena.Gui.Canvas/Slider.cs               |  237 ++++++++++++
 Hyena.Gui/Hyena.Gui.Canvas/StackPanel.cs           |  153 ++++++++
 Hyena.Gui/Hyena.Gui.Canvas/TestTile.cs             |   62 +++
 Hyena.Gui/Hyena.Gui.Canvas/TextBlock.cs            |  260 +++++++++++++
 Hyena.Gui/Hyena.Gui.Canvas/TextWrap.cs             |   38 ++
 Hyena.Gui/Hyena.Gui.Canvas/Thickness.cs            |   42 +-
 Hyena.Gui/Makefile.am                              |   29 ++-
 23 files changed, 2681 insertions(+), 71 deletions(-)
---
diff --git a/Hyena.Gui/Hyena.Gui.Canvas/AnimationManager.cs b/Hyena.Gui/Hyena.Gui.Canvas/AnimationManager.cs
new file mode 100644
index 0000000..1646adf
--- /dev/null
+++ b/Hyena.Gui/Hyena.Gui.Canvas/AnimationManager.cs
@@ -0,0 +1,302 @@
+//
+// AnimationManager.cs
+//
+// Author:
+//       Aaron Bockover <abockover novell com>
+//
+// Copyright 2009 Aaron Bockover
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+using Hyena.Gui.Theatrics;
+
+namespace Hyena.Gui.Canvas
+{
+    public abstract class Animation
+    {
+        private Stage<Animation> stage;
+        internal protected Stage<Animation> Stage {
+            get { return stage; }
+            set { stage = value; }
+        }
+
+        private Actor<Animation> actor;
+        internal protected Actor<Animation> Actor {
+            get { return actor; }
+            set { actor = value; }
+        }
+
+        private uint duration = 1000;
+        public uint Duration {
+            get { return duration; }
+            set {
+                duration = value;
+                if (Actor != null) {
+                    Actor.Reset (duration);
+                }
+            }
+        }
+
+        private CanvasItem item;
+        public CanvasItem Item {
+            get { return item; }
+            set { item = value; }
+        }
+
+        private string property;
+        public string Property {
+            get { return property; }
+            set { property = value; }
+        }
+
+        private bool is_expired;
+        public bool IsExpired {
+            get { return is_expired; }
+            set {
+                is_expired = value;
+                iterations = 0;
+            }
+        }
+
+        private int iterations;
+
+        private int repeat_times;
+        public int RepeatTimes {
+            get { return repeat_times; }
+            set { repeat_times = value; }
+        }
+
+        public virtual void Start ()
+        {
+            if (Stage.Contains (this)) {
+                Actor.Reset (Duration);
+                return;
+            }
+
+            Actor = Stage.Add (this, Duration);
+            IsExpired = false;
+            Actor.CanExpire = false;
+        }
+
+        public virtual bool Step (Actor<Animation> actor)
+        {
+            if (RepeatTimes > 0 && actor.Percent == 1) {
+                if (++iterations >= RepeatTimes) {
+                    IsExpired = true;
+                }
+            }
+
+            return !IsExpired;
+        }
+    }
+
+    public delegate T AnimationComposeHandler<T> (Animation<T> animation, double percent);
+
+    public abstract class Animation<T> : Animation
+    {
+        private AnimationComposeHandler<T> compose_handler;
+        protected AnimationComposeHandler<T> ComposeHandler {
+            get { return compose_handler; }
+        }
+
+        private Easing easing = Easing.Linear;
+        protected Easing Easing {
+            get { return easing; }
+        }
+
+        private bool from_set;
+        protected bool FromSet {
+            get { return from_set; }
+        }
+
+        private T from_value;
+        public T FromValue {
+            get { return from_value; }
+            set {
+                from_set = true;
+                from_value = value;
+            }
+        }
+
+        private T to_value;
+        public T ToValue {
+            get { return to_value; }
+            set { to_value = value; }
+        }
+
+        private T start_state;
+        public T StartState {
+            get { return start_state; }
+            protected set { start_state = value; }
+        }
+
+        public override void Start ()
+        {
+            T check_state = FromSet ? FromValue : (T)Item[Property];
+
+            if (!check_state.Equals (ToValue)) {
+                base.Start ();
+            }
+        }
+
+        public Animation<T> ClearFromValue ()
+        {
+            from_set = false;
+            from_value = default (T);
+            return this;
+        }
+
+        public Animation<T> Animate ()
+        {
+            StartState = FromSet ? FromValue : (T)Item[Property];
+            return this;
+        }
+
+        public Animation<T> Animate (T toValue)
+        {
+            ClearFromValue ();
+            ToValue = toValue;
+            return Animate ();
+        }
+
+        public Animation<T> Animate (T fromValue, T toValue)
+        {
+            FromValue = fromValue;
+            ToValue = toValue;
+            return Animate ();
+        }
+
+        public Animation<T> Animate (string property, T toValue)
+        {
+            Property = property;
+            return Animate (toValue);
+        }
+
+        public Animation<T> Animate (string property, T fromValue, T toValue)
+        {
+            Property = property;
+            return Animate (fromValue, toValue);
+        }
+
+        public Animation<T> Compose (AnimationComposeHandler<T> handler)
+        {
+            compose_handler = handler;
+            return this;
+        }
+
+        public Animation<T> ClearCompose ()
+        {
+            compose_handler = null;
+            return this;
+        }
+
+        public Animation<T> To (T toValue)
+        {
+            ToValue = toValue;
+            return Animate ();
+        }
+
+        public Animation<T> From (T fromValue)
+        {
+            FromValue = fromValue;
+            return Animate ();
+        }
+
+        public Animation<T> Reverse ()
+        {
+            T from = FromValue;
+            FromValue = ToValue;
+            ToValue = from;
+            return Animate ();
+        }
+
+        public Animation<T> Ease (Easing easing)
+        {
+            this.easing = easing;
+            return this;
+        }
+
+        public Animation<T> Throttle (uint duration)
+        {
+            Duration = duration;
+            return this;
+        }
+
+        public Animation<T> Repeat (int count)
+        {
+            RepeatTimes = count;
+            return this;
+        }
+
+        public Animation<T> Expire ()
+        {
+            IsExpired = true;
+            return this;
+        }
+    }
+
+    public class DoubleAnimation : Animation<double>
+    {
+        public DoubleAnimation ()
+        {
+        }
+
+        public DoubleAnimation (string property)
+        {
+            Property = property;
+        }
+
+        public override bool Step (Actor<Animation> actor)
+        {
+            double result = ComposeHandler == null
+                ? StartState + (ToValue * actor.Percent)
+                : ComposeHandler (this, actor.Percent);
+
+            result = Easing == Easing.Linear
+                ? result
+                : Choreographer.Compose (result, Easing);
+
+            Item.SetValue<double> (Property, result);
+
+            return base.Step (actor);
+        }
+    }
+
+    public class AnimationManager
+    {
+        private static AnimationManager instance;
+        public static AnimationManager Instance {
+            get { return instance ?? (instance = new AnimationManager ()); }
+        }
+
+        private Stage<Animation> stage = new Stage<Animation> ();
+
+        public AnimationManager ()
+        {
+            stage.Play ();
+            stage.ActorStep += (actor) => actor.Target.Step (actor);
+        }
+
+        public void Animate (Animation animation)
+        {
+            animation.Stage = stage;
+        }
+    }
+}
diff --git a/Hyena.Gui/Hyena.Gui.Canvas/Brush.cs b/Hyena.Gui/Hyena.Gui.Canvas/Brush.cs
new file mode 100644
index 0000000..28df7b6
--- /dev/null
+++ b/Hyena.Gui/Hyena.Gui.Canvas/Brush.cs
@@ -0,0 +1,81 @@
+//
+// Brush.cs
+//
+// Author:
+//       Aaron Bockover <abockover novell com>
+//
+// Copyright 2009 Aaron Bockover
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+
+namespace Hyena.Gui.Canvas
+{
+    public class Brush
+    {
+        private Cairo.Color color;
+
+        public Brush ()
+        {
+        }
+
+        public Brush (byte r, byte g, byte b) : this (r, g, b, 255)
+        {
+        }
+
+        public Brush (byte r, byte g, byte b, byte a)
+            : this ((double)r / 255.0, (double)g / 255.0, (double)b / 255.0, (double)a / 255.0)
+        {
+        }
+
+        public Brush (double r, double g, double b) : this (r, g, b, 1)
+        {
+        }
+
+        public Brush (double r, double g, double b, double a) : this (new Cairo.Color (r, g, b, a))
+        {
+        }
+
+        public Brush (Cairo.Color color)
+        {
+            this.color = color;
+        }
+
+        public virtual bool IsValid {
+            get { return true; }
+        }
+
+        public virtual void Apply (Cairo.Context cr)
+        {
+            cr.Color = color;
+        }
+
+        public static readonly Brush Black = new Brush (0.0, 0.0, 0.0);
+        public static readonly Brush White = new Brush (1.0, 1.0, 1.0);
+
+        public virtual double Width {
+            get { return Double.NaN; }
+        }
+
+        public virtual double Height {
+            get { return Double.NaN; }
+        }
+    }
+}
diff --git a/Hyena.Gui/Hyena.Gui.Canvas/CanvasHost.cs b/Hyena.Gui/Hyena.Gui.Canvas/CanvasHost.cs
new file mode 100644
index 0000000..db20cf5
--- /dev/null
+++ b/Hyena.Gui/Hyena.Gui.Canvas/CanvasHost.cs
@@ -0,0 +1,343 @@
+//
+// CanvasHost.cs
+//
+// Author:
+//       Aaron Bockover <abockover novell com>
+//
+// Copyright 2009 Aaron Bockover
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+using Gtk;
+using Gdk;
+
+using Hyena.Gui.Theming;
+
+namespace Hyena.Gui.Canvas
+{
+    public class FpsCalculator
+    {
+        private DateTime last_update;
+        private TimeSpan update_interval;
+        private int frame_count;
+        private double fps;
+
+        public FpsCalculator ()
+        {
+            update_interval = TimeSpan.FromSeconds (0.5);
+        }
+
+        public bool Update ()
+        {
+            bool updated = false;
+            DateTime current_time = DateTime.Now;
+            frame_count++;
+
+            if (current_time - last_update >= update_interval) {
+                fps = (double)frame_count / (current_time - last_update).TotalSeconds;
+                frame_count = 0;
+                updated = true;
+                last_update = current_time;
+            }
+
+            return updated;
+        }
+
+        public double FramesPerSecond {
+            get { return fps; }
+        }
+    }
+
+    public class CanvasHost : Widget
+    {
+        private Gdk.Window event_window;
+        private CanvasItem canvas_child;
+        private Theme theme;
+        private CanvasManager manager;
+        private bool debug = false;
+        private FpsCalculator fps = new FpsCalculator ();
+
+        public CanvasHost ()
+        {
+            WidgetFlags |= WidgetFlags.NoWindow;
+            manager = new CanvasManager (this);
+        }
+
+        protected CanvasHost (IntPtr native) : base (native)
+        {
+        }
+
+        protected override void OnRealized ()
+        {
+            base.OnRealized ();
+
+            WindowAttr attributes = new WindowAttr ();
+            attributes.WindowType = Gdk.WindowType.Child;
+            attributes.X = Allocation.X;
+            attributes.Y = Allocation.Y;
+            attributes.Width = Allocation.Width;
+            attributes.Height = Allocation.Height;
+            attributes.Wclass = WindowClass.InputOnly;
+            attributes.EventMask = (int)(
+                EventMask.PointerMotionMask |
+                EventMask.ButtonPressMask |
+                EventMask.ButtonReleaseMask |
+                EventMask.EnterNotifyMask |
+                EventMask.LeaveNotifyMask |
+                EventMask.ExposureMask);
+
+            WindowAttributesType attributes_mask =
+                WindowAttributesType.X | WindowAttributesType.Y | WindowAttributesType.Wmclass;
+
+            event_window = new Gdk.Window (GdkWindow, attributes, attributes_mask);
+            event_window.UserData = Handle;
+
+            AllocateChild ();
+            QueueResize ();
+        }
+
+        protected override void OnUnrealized ()
+        {
+            WidgetFlags ^= WidgetFlags.Realized;
+
+            event_window.UserData = IntPtr.Zero;
+            Hyena.Gui.GtkWorkarounds.WindowDestroy (event_window);
+            event_window = null;
+
+            base.OnUnrealized ();
+        }
+
+        protected override void OnMapped ()
+        {
+            event_window.Show ();
+            base.OnMapped ();
+        }
+
+        protected override void OnUnmapped ()
+        {
+            event_window.Hide ();
+            base.OnUnmapped ();
+        }
+
+        protected override void OnSizeAllocated (Gdk.Rectangle allocation)
+        {
+            base.OnSizeAllocated (allocation);
+
+            if (IsRealized) {
+                event_window.MoveResize (allocation);
+                AllocateChild ();
+            }
+        }
+
+        protected override void OnSizeRequested (ref Gtk.Requisition requisition)
+        {
+            if (canvas_child != null) {
+                Size size = canvas_child.Measure (Size.Empty);
+
+                if (size.Width > 0) {
+                    requisition.Width = (int)Math.Ceiling (size.Width);
+                }
+
+                if (size.Height > 0) {
+                    requisition.Height = (int)Math.Ceiling (size.Height);
+                }
+            }
+        }
+
+        private Random rand;
+
+        protected override bool OnExposeEvent (Gdk.EventExpose evnt)
+        {
+            if (canvas_child == null || !canvas_child.Visible || !Visible || !IsMapped) {
+                return true;
+            }
+
+            Cairo.Context cr = Gdk.CairoHelper.Create (evnt.Window);
+
+            foreach (Gdk.Rectangle damage in evnt.Region.GetRectangles ()) {
+                cr.Rectangle (damage.X, damage.Y, damage.Width, damage.Height);
+                cr.Clip ();
+
+                cr.Translate (Allocation.X, Allocation.Y);
+                canvas_child.Render (cr);
+                cr.Translate (-Allocation.X, -Allocation.Y);
+
+                if (Debug) {
+                    cr.LineWidth = 1.0;
+                    cr.Color = CairoExtensions.RgbToColor (
+                        (uint)(rand = rand ?? new Random ()).Next (0, 0xffffff));
+                    cr.Rectangle (damage.X + 0.5, damage.Y + 0.5, damage.Width - 1, damage.Height - 1);
+                    cr.Stroke ();
+                }
+
+                cr.ResetClip ();
+            }
+
+            CairoExtensions.DisposeContext (cr);
+
+            if (fps.Update ()) {
+                // Console.WriteLine ("FPS: {0}", fps.FramesPerSecond);
+            }
+
+            return true;
+        }
+
+        private void AllocateChild ()
+        {
+            if (canvas_child != null) {
+                canvas_child.Allocation = new Rect (0, 0, Allocation.Width, Allocation.Height);
+                canvas_child.Measure (new Size (Allocation.Width, Allocation.Height));
+                canvas_child.Arrange ();
+            }
+        }
+
+        public void QueueRender (CanvasItem item, Rect rect)
+        {
+            double x = Allocation.X;
+            double y = Allocation.Y;
+            double w, h;
+
+            if (rect.IsEmpty) {
+                w = item.Allocation.Width;
+                h = item.Allocation.Height;
+            } else {
+                x += rect.X;
+                y += rect.Y;
+                w = rect.Width;
+                h = rect.Height;
+            }
+
+            while (item != null) {
+                x += item.ContentAllocation.X;
+                y += item.ContentAllocation.Y;
+                item = item.Parent;
+            }
+
+            QueueDrawArea (
+                (int)Math.Floor (x),
+                (int)Math.Floor (y),
+                (int)Math.Ceiling (w),
+                (int)Math.Ceiling (h)
+            );
+        }
+
+        private bool changing_style = false;
+
+        protected override void OnStyleSet (Style old_style)
+        {
+            if (changing_style) {
+                return;
+            }
+
+            changing_style = true;
+
+            theme = new GtkTheme (this);
+            if (canvas_child != null) {
+                canvas_child.Theme = theme;
+            }
+
+            changing_style = false;
+
+            base.OnStyleSet (old_style);
+        }
+
+        protected override bool OnButtonPressEvent (Gdk.EventButton evnt)
+        {
+            if (canvas_child != null) {
+                canvas_child.ButtonPress (evnt.X, evnt.Y, evnt.Button);
+            }
+            return true;
+        }
+
+        protected override bool OnButtonReleaseEvent (Gdk.EventButton evnt)
+        {
+            if (canvas_child != null) {
+                canvas_child.ButtonRelease ();
+            }
+            return true;
+        }
+
+        protected override bool OnMotionNotifyEvent (EventMotion evnt)
+        {
+            if (canvas_child != null) {
+                canvas_child.PointerMotion (evnt.X, evnt.Y);
+            }
+            return true;
+        }
+
+        public void Add (CanvasItem child)
+        {
+            if (Child != null) {
+                throw new InvalidOperationException ("Child is already set, remove it first");
+            }
+
+            Child = child;
+        }
+
+        public void Remove (CanvasItem child)
+        {
+            if (Child != child) {
+                throw new InvalidOperationException ("child does not already belong to host");
+            }
+
+            Child = null;
+        }
+
+        private void OnCanvasChildLayoutUpdated (object o, EventArgs args)
+        {
+            QueueDraw ();
+        }
+
+        private void OnCanvasChildSizeChanged (object o, EventArgs args)
+        {
+            QueueResize ();
+        }
+
+        public CanvasItem Child {
+            get { return canvas_child; }
+            set {
+                if (canvas_child == value) {
+                    return;
+                } else if (canvas_child != null) {
+                    canvas_child.Theme = null;
+                    canvas_child.Manager = null;
+                    canvas_child.LayoutUpdated -= OnCanvasChildLayoutUpdated;
+                    canvas_child.SizeChanged -= OnCanvasChildSizeChanged;
+                }
+
+                canvas_child = value;
+
+                if (canvas_child != null) {
+                    canvas_child.Theme = theme;
+                    canvas_child.Manager = manager;
+                    canvas_child.LayoutUpdated += OnCanvasChildLayoutUpdated;
+                    canvas_child.SizeChanged += OnCanvasChildSizeChanged;
+                }
+
+                AllocateChild ();
+            }
+        }
+
+        public bool Debug {
+            get { return debug; }
+            set { debug = value; }
+        }
+    }
+}
diff --git a/Hyena.Gui/Hyena.Gui.Canvas/CanvasItem.cs b/Hyena.Gui/Hyena.Gui.Canvas/CanvasItem.cs
new file mode 100644
index 0000000..cd4121e
--- /dev/null
+++ b/Hyena.Gui/Hyena.Gui.Canvas/CanvasItem.cs
@@ -0,0 +1,405 @@
+//
+// CanvasItem.cs
+//
+// Author:
+//       Aaron Bockover <abockover novell com>
+//
+// Copyright 2009 Aaron Bockover
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+using System.Collections.Generic;
+
+using Hyena.Gui.Theming;
+
+namespace Hyena.Gui.Canvas
+{
+    public class CanvasItem
+    {
+        private CanvasManager manager;
+        private CanvasItem parent;
+        private Theme theme;
+        private Size desired_size;
+        private Dictionary<string, object> properties;
+        private Rect allocation;
+        private bool visible = true;
+
+        public event EventHandler<EventArgs> SizeChanged;
+        public event EventHandler<EventArgs> LayoutUpdated;
+
+        public CanvasItem ()
+        {
+            InstallProperty<double> ("Opacity", 1.0);
+            InstallProperty<double> ("Width", Double.NaN);
+            InstallProperty<double> ("Height", Double.NaN);
+            InstallProperty<Thickness> ("Margin", new Thickness (0));
+            InstallProperty<Brush> ("Foreground", Brush.Black);
+            InstallProperty<Brush> ("Background", Brush.White);
+            InstallProperty<MarginStyle> ("MarginStyle", MarginStyle.None);
+        }
+
+        public void InvalidateArrange ()
+        {
+            CanvasItem root = RootAncestor;
+            if (root != null && root.Manager != null) {
+                root.Manager.QueueArrange (this);
+            }
+        }
+
+        public void InvalidateMeasure ()
+        {
+            CanvasItem root = RootAncestor;
+            if (root != null && root.Manager != null) {
+                root.Manager.QueueMeasure (this);
+            }
+        }
+
+        protected void InvalidateRender ()
+        {
+            InvalidateRender (InvalidationRect);
+        }
+
+        protected void InvalidateRender (Rect area)
+        {
+            CanvasItem root = RootAncestor;
+            if (root != null && root.Manager != null) {
+                root.Manager.QueueRender (this, area);
+            }
+        }
+
+        public virtual void Arrange ()
+        {
+        }
+
+        public virtual Size Measure (Size available)
+        {
+            double m_x = Margin.Left + Margin.Right;
+            double m_y = Margin.Top + Margin.Bottom;
+
+            double a_w = available.Width - m_x;
+            double a_h = available.Height - m_y;
+
+            return DesiredSize = new Size (
+                Math.Max (0, Math.Min (a_w, Double.IsNaN (Width) ? a_w : Width + m_x)),
+                Math.Max (0, Math.Min (a_h, Double.IsNaN (Height) ? a_h : Height + m_y))
+            );
+        }
+
+        public virtual void Render (Cairo.Context cr)
+        {
+            double opacity = Opacity;
+
+            if (ContentAllocation.Width <= 0 || ContentAllocation.Height <= 0 || opacity <= 0) {
+                return;
+            }
+
+            cr.Save ();
+
+            if (opacity < 1.0) {
+                cr.PushGroup ();
+            }
+
+            MarginStyle margin_style = MarginStyle;
+            if (margin_style != null && margin_style != MarginStyle.None) {
+                cr.Translate (Math.Round (Allocation.X), Math.Round (Allocation.Y));
+                cr.Save ();
+                margin_style.Apply (this, cr);
+                cr.Restore ();
+                cr.Translate (Math.Round (Margin.Left), Math.Round (Margin.Top));
+            } else {
+                cr.Translate (Math.Round (ContentAllocation.X), Math.Round (ContentAllocation.Y));
+            }
+
+            cr.Antialias = Cairo.Antialias.Default;
+            ClippedRender (cr);
+
+            if (opacity < 1.0) {
+                cr.PopGroupToSource ();
+                cr.PaintWithAlpha (Opacity);
+            }
+
+            cr.Restore ();
+        }
+
+        protected virtual void ClippedRender (Cairo.Context cr)
+        {
+        }
+
+        protected virtual void OnSizeChanged ()
+        {
+            EventHandler<EventArgs> handler = SizeChanged;
+            if (handler != null) {
+                handler (this, EventArgs.Empty);
+            }
+        }
+
+        protected virtual void OnLayoutUpdated ()
+        {
+            EventHandler<EventArgs> handler = LayoutUpdated;
+            if (handler != null) {
+                handler (this, EventArgs.Empty);
+            }
+        }
+
+        internal CanvasManager Manager {
+            get { return manager ?? (Parent != null ? Parent.Manager : null); }
+            set { manager = value; }
+        }
+
+        public CanvasItem RootAncestor {
+            get {
+                CanvasItem root = Parent ?? this;
+                while (root != null && root.Parent != null) {
+                    root = root.Parent;
+                }
+                return root;
+            }
+        }
+
+        public CanvasItem Parent {
+            get { return parent; }
+            set { parent = value; }
+        }
+
+        public Theme Theme {
+            get { return theme ?? (Parent != null ? Parent.Theme : null); }
+            set { theme = value; }
+        }
+
+        public Size DesiredSize {
+            get { return desired_size; }
+            protected set { desired_size = value; }
+        }
+
+        public Thickness Margin {
+            get { return GetValue<Thickness> ("Margin"); }
+            set { SetValue<Thickness> ("Margin", value); }
+        }
+
+        public MarginStyle MarginStyle {
+            get { return GetValue<MarginStyle> ("MarginStyle"); }
+            set { SetValue<MarginStyle> ("MarginStyle", value); }
+        }
+
+        public double Width {
+            get { return GetValue<double> ("Width"); }
+            set { SetValue<double> ("Width", value); }
+        }
+
+        public double Height {
+            get { return GetValue<double> ("Height"); }
+            set { SetValue<double> ("Height", value); }
+        }
+
+        public double Opacity {
+            get { return GetValue<double> ("Opacity"); }
+            set { SetValue<double> ("Opacity", value); }
+        }
+
+        public Brush Foreground {
+            get { return GetValue<Brush> ("Foreground"); }
+            set { SetValue<Brush> ("Foreground", value); }
+        }
+
+        public Brush Background {
+            get { return GetValue<Brush> ("Background"); }
+            set { SetValue<Brush> ("Background", value); }
+        }
+
+        public Rect Allocation {
+            get { return allocation; }
+            set { allocation = value; }
+        }
+
+        public Rect ContentAllocation {
+            get {
+                return new Rect (
+                    Allocation.X + Margin.Left,
+                    Allocation.Y + Margin.Top,
+                    Math.Max (0, Allocation.Width - Margin.Left - Margin.Right),
+                    Math.Max (0, Allocation.Height - Margin.Top - Margin.Bottom)
+                );
+            }
+        }
+
+        protected virtual Rect InvalidationRect {
+            get { return Rect.Empty; }
+        }
+
+        public Size ContentSize {
+            get { return new Size (ContentAllocation.Width, ContentAllocation.Height); }
+        }
+
+        protected Size RenderSize {
+            get { return new Size (Math.Round (ContentAllocation.Width), Math.Round (ContentAllocation.Height)); }
+        }
+
+        public bool Visible {
+            get { return visible; }
+            set { visible = value; }
+        }
+
+        protected void InstallProperty<T> (string property)
+        {
+            InstallProperty (property, default (T));
+        }
+
+        protected void InstallProperty<T> (string property, T defaultValue)
+        {
+            if (properties == null) {
+                properties = new Dictionary<string, object> ();
+            }
+
+            if (properties.ContainsKey (property)) {
+                throw new InvalidOperationException ("Property is already installed: " + property);
+            }
+
+            properties.Add (property, defaultValue);
+        }
+
+        protected virtual bool OnPropertyChange (string property, object value)
+        {
+            switch (property) {
+                case "Foreground":
+                case "Background":
+                case "MarginStyle":
+                case "Opacity":
+                    InvalidateRender ();
+                    return true;
+                /* case "Width":
+                case "Height":
+                case "Margin":
+                    InvalidateArrange ();
+                    InvalidateMeasure ();
+                    return true; */
+            }
+
+            return false;
+        }
+
+        public object this[string property] {
+            get {
+                object result;
+                if (properties != null && properties.TryGetValue (property, out result)) {
+                    return result;
+                }
+
+                return result;
+            }
+
+            set {
+                if (properties == null || !properties.ContainsKey (property)) {
+                    throw new InvalidOperationException ("Property does not exist: " + property);
+                }
+
+                object existing = properties[property];
+
+                Type existing_type = existing.GetType ();
+                Type new_type = value.GetType ();
+
+                if (existing_type != new_type && !new_type.IsSubclassOf (existing_type)) {
+                    throw new InvalidOperationException ("Invalid value type " +
+                        value.GetType () + " for property: " + property);
+                }
+
+                if (existing != value) {
+                    properties[property] = value;
+                    OnPropertyChange (property, value);
+                }
+            }
+        }
+
+        public T GetValue<T> (string property)
+        {
+            return GetValue (property, default (T));
+        }
+
+        public T GetValue<T> (string property, T fallback)
+        {
+            object result = this[property];
+            if (result == null) {
+                return fallback;
+            }
+            return (T)result;
+        }
+
+        public void SetValue<T> (string property, T value)
+        {
+            this[property] = value;
+        }
+
+        public T Animate<T> (T animation) where T : Animation
+        {
+            animation.Item = this;
+            AnimationManager.Instance.Animate (animation);
+            return animation;
+        }
+
+        public DoubleAnimation AnimateDouble (string property)
+        {
+            return Animate (new DoubleAnimation (property));
+        }
+
+#region Input Events
+
+        public event EventHandler<EventArgs> Clicked;
+
+        private bool pointer_grabbed;
+        public virtual bool IsPointerGrabbed {
+            get { return pointer_grabbed; }
+        }
+
+        protected void GrabPointer ()
+        {
+            pointer_grabbed = true;
+        }
+
+        protected void ReleasePointer ()
+        {
+            pointer_grabbed = false;
+        }
+
+        public virtual void ButtonPress (double x, double y, uint button)
+        {
+            GrabPointer ();
+        }
+
+        public virtual void ButtonRelease ()
+        {
+            ReleasePointer ();
+            OnClicked ();
+        }
+
+        public virtual void PointerMotion (double x, double y)
+        {
+        }
+
+        protected virtual void OnClicked ()
+        {
+            var handler = Clicked;
+            if (handler != null) {
+                handler (this, EventArgs.Empty);
+            }
+        }
+
+#endregion
+
+    }
+}
diff --git a/Hyena.Gui/Hyena.Gui.Canvas/CanvasItemCollection.cs b/Hyena.Gui/Hyena.Gui.Canvas/CanvasItemCollection.cs
new file mode 100644
index 0000000..e1fa7db
--- /dev/null
+++ b/Hyena.Gui/Hyena.Gui.Canvas/CanvasItemCollection.cs
@@ -0,0 +1,106 @@
+//
+// CanvasItemCollection.cs
+//
+// Author:
+//       Aaron Bockover <abockover novell com>
+//
+// Copyright 2009 Aaron Bockover
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+using System.Collections.Generic;
+
+namespace Hyena.Gui.Canvas
+{
+    public class CanvasItemCollection : IEnumerable<CanvasItem>
+    {
+        private CanvasItem parent;
+        private List<CanvasItem> children = new List<CanvasItem> ();
+
+        public CanvasItemCollection (CanvasItem parent)
+        {
+            this.parent = parent;
+        }
+
+        public void Add (CanvasItem child)
+        {
+            if (!children.Contains (child)) {
+                children.Add (child);
+                child.Parent = parent;
+                parent.InvalidateArrange ();
+            }
+        }
+
+        private void Unparent (CanvasItem child)
+        {
+            child.Parent = null;
+        }
+
+        public void Remove (CanvasItem child)
+        {
+            if (children.Remove (child)) {
+                Unparent (child);
+                parent.InvalidateArrange ();
+            }
+        }
+
+        public void Move (CanvasItem child, int position)
+        {
+            if (children.Remove (child)) {
+                children.Insert (position, child);
+                parent.InvalidateArrange ();
+            }
+        }
+
+        public void Clear ()
+        {
+            foreach (var child in children) {
+                Unparent (child);
+            }
+            children.Clear ();
+        }
+
+        public IEnumerator<CanvasItem> GetEnumerator ()
+        {
+            foreach (var item in children) {
+                yield return item;
+            }
+        }
+
+        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator ()
+        {
+            return GetEnumerator ();
+        }
+
+        public CanvasItem this[int index] {
+            get { return children[index]; }
+        }
+
+        public CanvasItem Parent {
+            get { return parent; }
+        }
+
+        public int Count {
+            get { return children.Count; }
+        }
+    }
+}
+
+
diff --git a/Hyena.Gui/Hyena.Gui.Canvas/CanvasManager.cs b/Hyena.Gui/Hyena.Gui.Canvas/CanvasManager.cs
new file mode 100644
index 0000000..ca5badb
--- /dev/null
+++ b/Hyena.Gui/Hyena.Gui.Canvas/CanvasManager.cs
@@ -0,0 +1,64 @@
+//
+// CanvasHost.cs
+//
+// Author:
+//       Aaron Bockover <abockover novell com>
+//
+// Copyright 2009 Aaron Bockover
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+
+namespace Hyena.Gui.Canvas
+{
+    public class CanvasManager
+    {
+        private object host;
+
+        public CanvasManager (object host)
+        {
+            this.host = host;
+        }
+
+        public void QueueArrange (CanvasItem item)
+        {
+            item.Arrange ();
+        }
+
+        public void QueueMeasure (CanvasItem item)
+        {
+            item.Measure (item.ContentSize);
+        }
+
+        public void QueueRender (CanvasItem item, Rect rect)
+        {
+            CanvasHost host = Host as CanvasHost;
+            if (host == null) {
+                return;
+            }
+
+            host.QueueRender (item, rect);
+        }
+
+        public object Host {
+            get { return host; }
+        }
+    }
+}
diff --git a/Hyena.Gui/Hyena.Gui.Canvas/FixedPanel.cs b/Hyena.Gui/Hyena.Gui.Canvas/FixedPanel.cs
new file mode 100644
index 0000000..ae2aec5
--- /dev/null
+++ b/Hyena.Gui/Hyena.Gui.Canvas/FixedPanel.cs
@@ -0,0 +1,37 @@
+//
+// FixedPanel.cs
+//
+// Author:
+//       Aaron Bockover <abockover novell com>
+//
+// Copyright 2009 Aaron Bockover
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+
+namespace Hyena.Gui.Canvas
+{
+    public class FixedPanel : Panel
+    {
+        public FixedPanel ()
+        {
+        }
+    }
+}
diff --git a/Hyena.Gui/Hyena.Gui.Canvas/FontWeight.cs b/Hyena.Gui/Hyena.Gui.Canvas/FontWeight.cs
new file mode 100644
index 0000000..b4fda51
--- /dev/null
+++ b/Hyena.Gui/Hyena.Gui.Canvas/FontWeight.cs
@@ -0,0 +1,36 @@
+//
+// FontWeight.cs
+//
+// Author:
+//       Aaron Bockover <abockover novell com>
+//
+// Copyright 2009 Aaron Bockover
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+
+namespace Hyena.Gui.Canvas
+{
+    public enum FontWeight
+    {
+        Normal,
+        Bold
+    }
+}
diff --git a/Hyena.Gui/Hyena.Gui.Canvas/Image.cs b/Hyena.Gui/Hyena.Gui.Canvas/Image.cs
new file mode 100644
index 0000000..e013d5e
--- /dev/null
+++ b/Hyena.Gui/Hyena.Gui.Canvas/Image.cs
@@ -0,0 +1,67 @@
+//
+// Image.cs
+//
+// Author:
+//       Aaron Bockover <abockover novell com>
+//
+// Copyright 2009 Aaron Bockover
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+
+namespace Hyena.Gui.Canvas
+{
+    public class Image : CanvasItem
+    {
+        public Image ()
+        {
+        }
+
+        protected override void ClippedRender (Cairo.Context cr)
+        {
+            Brush brush = Background;
+            if (!brush.IsValid) {
+                return;
+            }
+
+            double x = Double.IsNaN (brush.Width)
+                ? 0
+                : (RenderSize.Width - brush.Width) * XAlign;
+
+            double y = Double.IsNaN (brush.Height)
+                ? 0
+                : (RenderSize.Height - brush.Height) * YAlign;
+
+            cr.Rectangle (0, 0, RenderSize.Width, RenderSize.Height);
+            cr.ClipPreserve ();
+
+            if (x != 0 || y != 0) {
+                cr.Translate (x, y);
+            }
+
+            cr.Antialias = Cairo.Antialias.None;
+            brush.Apply (cr);
+            cr.Fill ();
+        }
+
+        public double XAlign { get; set; }
+        public double YAlign { get; set; }
+    }
+}
diff --git a/Hyena.Gui/Hyena.Gui.Canvas/ImageBrush.cs b/Hyena.Gui/Hyena.Gui.Canvas/ImageBrush.cs
new file mode 100644
index 0000000..e30405a
--- /dev/null
+++ b/Hyena.Gui/Hyena.Gui.Canvas/ImageBrush.cs
@@ -0,0 +1,82 @@
+//
+// ImageBrush.cs
+//
+// Author:
+//       Aaron Bockover <abockover novell com>
+//
+// Copyright 2009 Aaron Bockover
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+
+using Cairo;
+using Hyena.Gui;
+
+namespace Hyena.Gui.Canvas
+{
+    public class ImageBrush : Brush
+    {
+        private ImageSurface surface;
+        private bool surface_owner;
+
+        public ImageBrush ()
+        {
+        }
+
+        public ImageBrush (string path) : this (new Gdk.Pixbuf (path), true)
+        {
+        }
+
+        public ImageBrush (Gdk.Pixbuf pixbuf, bool disposePixbuf)
+            : this (new PixbufImageSurface (pixbuf, disposePixbuf), true)
+        {
+        }
+
+        public ImageBrush (ImageSurface surface, bool disposeSurface)
+        {
+            this.surface = surface;
+            this.surface_owner = disposeSurface;
+        }
+
+        protected ImageSurface Surface {
+            get { return surface; }
+            set { surface = value; }
+        }
+
+        public override bool IsValid {
+            get { return surface != null; }
+        }
+
+        public override void Apply (Cairo.Context cr)
+        {
+            if (surface != null) {
+                cr.SetSource (surface);
+            }
+        }
+
+        public override double Width {
+            get { return surface == null ? 0 : surface.Width; }
+        }
+
+        public override double Height {
+            get { return surface == null ? 0 : surface.Height; }
+        }
+    }
+}
diff --git a/Hyena.Gui/Hyena.Gui.Canvas/MarginStyle.cs b/Hyena.Gui/Hyena.Gui.Canvas/MarginStyle.cs
new file mode 100644
index 0000000..5afcc3c
--- /dev/null
+++ b/Hyena.Gui/Hyena.Gui.Canvas/MarginStyle.cs
@@ -0,0 +1,43 @@
+//
+// MarginStyle.cs
+//
+// Author:
+//       Aaron Bockover <abockover novell com>
+//
+// Copyright 2009 Aaron Bockover
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+
+namespace Hyena.Gui.Canvas
+{
+    public class MarginStyle
+    {
+        public MarginStyle ()
+        {
+        }
+
+        public virtual void Apply (CanvasItem item, Cairo.Context cr)
+        {
+        }
+
+        public static readonly MarginStyle None = new MarginStyle ();
+    }
+}
diff --git a/Hyena.Gui/Hyena.Gui.Canvas/Orientation.cs b/Hyena.Gui/Hyena.Gui.Canvas/Orientation.cs
new file mode 100644
index 0000000..30e7234
--- /dev/null
+++ b/Hyena.Gui/Hyena.Gui.Canvas/Orientation.cs
@@ -0,0 +1,36 @@
+//
+// Orientation.cs
+//
+// Author:
+//       Aaron Bockover <abockover novell com>
+//
+// Copyright 2009 Aaron Bockover
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+
+namespace Hyena.Gui.Canvas
+{
+    public enum Orientation
+    {
+        Horizontal,
+        Vertical
+    }
+}
diff --git a/Hyena.Gui/Hyena.Gui.Canvas/Panel.cs b/Hyena.Gui/Hyena.Gui.Canvas/Panel.cs
new file mode 100644
index 0000000..f2effc8
--- /dev/null
+++ b/Hyena.Gui/Hyena.Gui.Canvas/Panel.cs
@@ -0,0 +1,148 @@
+//
+// Panel.cs
+//
+// Author:
+//       Aaron Bockover <abockover novell com>
+//
+// Copyright 2009 Aaron Bockover
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+
+namespace Hyena.Gui.Canvas
+{
+    public class Panel : CanvasItem
+    {
+        private CanvasItemCollection children;
+
+        public Panel ()
+        {
+            children = new CanvasItemCollection (this);
+        }
+
+        public override Size Measure (Size available)
+        {
+            Size result = new Size (0, 0);
+
+            foreach (var child in Children) {
+                if (child.Visible) {
+                    Size size = child.Measure (available);
+                    result.Width = Math.Max (result.Width, size.Width);
+                    result.Height = Math.Max (result.Height, size.Height);
+                }
+            }
+
+            if (!Double.IsNaN (Width)) {
+                result.Width = Width;
+            }
+
+            if (!Double.IsNaN (Height)) {
+                result.Height = Height;
+            }
+
+            if (!available.IsEmpty) {
+                result.Width = Math.Min (result.Width, available.Width);
+                result.Height = Math.Min (result.Height, available.Height);
+            }
+
+            return DesiredSize = result;
+        }
+
+        public override void Arrange ()
+        {
+            foreach (var child in Children) {
+                if (!child.Visible) {
+                    continue;
+                }
+
+                child.Allocation = new Rect (0, 0,
+                    Math.Min (ContentAllocation.Width, child.DesiredSize.Width),
+                    Math.Min (ContentAllocation.Height, child.DesiredSize.Height));
+
+                child.Arrange ();
+            }
+        }
+
+        protected override void ClippedRender (Cairo.Context cr)
+        {
+            foreach (var child in Children) {
+                if (child.Visible) {
+                    child.Render (cr);
+                }
+            }
+        }
+
+        protected CanvasItem FindChildAt (double x, double y, bool grabHasPriority)
+        {
+            foreach (var child in Children) {
+                if (child.IsPointerGrabbed || (child.Visible && child.Allocation.Contains (x, y))) {
+                    return child;
+                }
+            }
+
+            return null;
+        }
+
+        protected delegate void CanvasItemHandler (CanvasItem item);
+
+        protected void WithPointerGrabChild (CanvasItemHandler handler)
+        {
+            WithChildAt (-1, -1, true, handler);
+        }
+
+        protected void WithChildAt (double x, double y, CanvasItemHandler handler)
+        {
+            WithChildAt (x, y, true, handler);
+        }
+
+        protected void WithChildAt (double x, double y, bool grabHasPriority, CanvasItemHandler handler)
+        {
+            CanvasItem child = FindChildAt (x, y, grabHasPriority);
+            if (child != null) {
+                handler (child);
+            }
+        }
+
+        public override void ButtonPress (double x, double y, uint button)
+        {
+            WithChildAt (x, y, (item) => item.ButtonPress (
+                x - item.Allocation.X, y - item.Allocation.Y, button));
+        }
+
+        public override void ButtonRelease ()
+        {
+            WithPointerGrabChild ((item) => item.ButtonRelease ());
+        }
+
+        public override void PointerMotion (double x, double y)
+        {
+            WithChildAt (x, y, (item) => item.PointerMotion (
+                x - item.Allocation.X, y - item.Allocation.Y));
+        }
+
+        public override bool IsPointerGrabbed {
+            get { return base.IsPointerGrabbed || FindChildAt (-1, -1, true) != null; }
+        }
+
+        public CanvasItemCollection Children {
+            get { return children; }
+        }
+    }
+}
diff --git a/Hyena.Gui/Hyena.Gui.Canvas/Rect.cs b/Hyena.Gui/Hyena.Gui.Canvas/Rect.cs
index bb49fd7..0a1b406 100644
--- a/Hyena.Gui/Hyena.Gui.Canvas/Rect.cs
+++ b/Hyena.Gui/Hyena.Gui.Canvas/Rect.cs
@@ -30,7 +30,7 @@ using System;
 
 namespace Hyena.Gui.Canvas
 {
-    public struct Rect 
+    public struct Rect
     {
         private double x, y, w, h;
 
@@ -63,10 +63,10 @@ namespace Hyena.Gui.Canvas
             if (IsEmpty) {
                 return "Empty";
             }
-            
+
             return String.Format ("{0}+{1},{2}x{3}", x, y, w, h);
         }
-        
+
         public double X {
             get { return x; }
             set { x = value; }
@@ -74,28 +74,28 @@ namespace Hyena.Gui.Canvas
 
         public double Y {
             get { return y; }
-            set { y = value; } 
+            set { y = value; }
         }
 
         public double Width {
             get { return w; }
-            set { 
+            set {
                 if (value < 0) {
                     throw new ArgumentException ();
                 }
-                
-                w = value; 
-            } 
+
+                w = value;
+            }
         }
 
         public double Height {
             get { return h; }
-            set { 
+            set {
                 if (value < 0) {
                     throw new ArgumentException ();
                 }
-                
-                h = value; 
+
+                h = value;
             }
         }
 
@@ -116,35 +116,35 @@ namespace Hyena.Gui.Canvas
                 Top > rect.Bottom ||
                 Bottom < rect.Top);
         }
-        
-        public static Rect Empty { 
-            get { 
+
+        public static Rect Empty {
+            get {
                 var empty = new Rect (Double.PositiveInfinity, Double.PositiveInfinity, 0, 0);
                 empty.w = empty.h = Double.NegativeInfinity;
                 return empty;
-            } 
+            }
         }
-        
-        public bool IsEmpty { 
+
+        public bool IsEmpty {
             get { return w < 0 && h < 0; }
         }
-        
-        public double Left { 
+
+        public double Left {
             get { return x; }
         }
-        
-        public double Top { 
+
+        public double Top {
             get { return y; }
         }
 
         public double Right {
             get { return IsEmpty ? Double.NegativeInfinity : x + w; }
         }
-        
-        public double Bottom { 
+
+        public double Bottom {
             get { return IsEmpty ? Double.NegativeInfinity : y + h; }
         }
-        
+
         public void Intersect (Rect rect)
         {
             if (IsEmpty || rect.IsEmpty) {
@@ -155,7 +155,7 @@ namespace Hyena.Gui.Canvas
             double new_x = Math.Max (x, rect.x);
             double new_y = Math.Max (y, rect.y);
             double new_w = Math.Min (Right, rect.Right) - new_x;
-            double new_h = Math.Min (Bottom, rect.Bottom) - new_y; 
+            double new_h = Math.Min (Bottom, rect.Bottom) - new_y;
 
             x = new_x;
             y = new_y;
@@ -179,14 +179,14 @@ namespace Hyena.Gui.Canvas
                 double new_y = Math.Min (Top, rect.Top);
                 double new_w = Math.Max (Right, rect.Right) - new_x;
                 double new_h = Math.Max (Bottom, rect.Bottom) - new_y;
-    
+
                 x = new_x;
                 y = new_y;
                 w = new_w;
                 h = new_h;
             }
         }
-        
+
         public void Union (Point point)
         {
             Union (new Rect (point, point));
@@ -209,17 +209,17 @@ namespace Hyena.Gui.Canvas
             x += dx;
             y += dy;
         }
-        
+
         public static bool operator == (Rect rect1, Rect rect2)
         {
             return rect1.x == rect2.x && rect1.y == rect2.y && rect1.w == rect2.w && rect1.h == rect2.h;
         }
-        
+
         public static bool operator != (Rect rect1, Rect rect2)
         {
             return !(rect1 == rect2);
         }
-        
+
         public override bool Equals (object o)
         {
             if (o is Rect) {
@@ -227,12 +227,12 @@ namespace Hyena.Gui.Canvas
             }
             return false;
         }
-        
+
         public bool Equals (Rect value)
         {
             return this == value;
         }
-        
+
         public override int GetHashCode ()
         {
             return x.GetHashCode () ^ y.GetHashCode () ^ w.GetHashCode () ^ h.GetHashCode ();
diff --git a/Hyena.Gui/Hyena.Gui.Canvas/ShadowMarginStyle.cs b/Hyena.Gui/Hyena.Gui.Canvas/ShadowMarginStyle.cs
new file mode 100644
index 0000000..63648d6
--- /dev/null
+++ b/Hyena.Gui/Hyena.Gui.Canvas/ShadowMarginStyle.cs
@@ -0,0 +1,89 @@
+//
+// ShadowMarginStyle.cs
+//
+// Author:
+//       Aaron Bockover <abockover novell com>
+//
+// Copyright 2009 Aaron Bockover
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+
+using Cairo;
+
+namespace Hyena.Gui.Canvas
+{
+    public class ShadowMarginStyle : MarginStyle
+    {
+        private int shadow_size;
+        private double shadow_opacity = 0.75;
+        private Brush fill;
+
+        public ShadowMarginStyle ()
+        {
+        }
+
+        public override void Apply (CanvasItem item, Context cr)
+        {
+            int steps = ShadowSize;
+            double opacity_step = ShadowOpacity / ShadowSize;
+            Color color = new Color (0, 0, 0);
+
+            double width = Math.Round (item.Allocation.Width);
+            double height = Math.Round (item.Allocation.Height);
+
+            if (Fill != null) {
+                cr.Rectangle (shadow_size, shadow_size, width - ShadowSize * 2, height - ShadowSize * 2);
+                Fill.Apply (cr);
+                cr.Fill ();
+            }
+
+            cr.LineWidth = 1.0;
+
+            for (int i = 0; i < steps; i++) {
+                CairoExtensions.RoundedRectangle (cr,
+                    i + 0.5,
+                    i + 0.5,
+                    (width - 2 * i) - 1,
+                    (height - 2 * i) - 1,
+                    steps - i);
+
+                color.A = opacity_step * (i + 1);
+                cr.Color = color;
+                cr.Stroke ();
+            }
+        }
+
+        public double ShadowOpacity {
+            get { return shadow_opacity; }
+            set { shadow_opacity = value; }
+        }
+
+        public int ShadowSize {
+            get { return shadow_size; }
+            set { shadow_size = value; }
+        }
+
+        public Brush Fill {
+            get { return fill; }
+            set { fill = value; }
+        }
+    }
+}
diff --git a/Hyena.Gui/Hyena.Gui.Canvas/Size.cs b/Hyena.Gui/Hyena.Gui.Canvas/Size.cs
index 4d2cca7..b626597 100644
--- a/Hyena.Gui/Hyena.Gui.Canvas/Size.cs
+++ b/Hyena.Gui/Hyena.Gui.Canvas/Size.cs
@@ -30,7 +30,7 @@ using System;
 
 namespace Hyena.Gui.Canvas
 {
-    public struct Size 
+    public struct Size
     {
         private double width;
         private double height;
@@ -40,44 +40,44 @@ namespace Hyena.Gui.Canvas
             Width = width;
             Height = height;
         }
-        
+
         public override bool Equals (object o)
         {
             if (!(o is Size)) {
                 return false;
             }
-            
+
             return Equals ((Size)o);
         }
-        
+
         public bool Equals (Size value)
         {
             return value.width == width && value.height == height;
         }
-        
+
         public override int GetHashCode ()
         {
             return ((int)width) ^ ((int)height);
         }
-        
+
         public static bool operator == (Size size1, Size size2)
         {
             return size1.width == size2.width && size1.height == size2.height;
         }
-            
+
         public static bool operator != (Size size1, Size size2)
         {
             return size1.width != size2.width || size1.height != size2.height;
         }
-        
+
         public double Height {
-            get { return height; } 
+            get { return height; }
             set {
                 if (value < 0) {
                     throw new ArgumentException ();
                 }
-                
-                height = value; 
+
+                height = value;
             }
         }
 
@@ -87,7 +87,7 @@ namespace Hyena.Gui.Canvas
                 if (value < 0) {
                     throw new ArgumentException ();
                 }
-                
+
                 width = value;
             }
         }
@@ -95,7 +95,7 @@ namespace Hyena.Gui.Canvas
         public bool IsEmpty {
             get { return width == Double.NegativeInfinity && height == Double.NegativeInfinity; }
         }
-        
+
         public static Size Empty {
             get {
                 Size size = new Size ();
@@ -110,7 +110,7 @@ namespace Hyena.Gui.Canvas
             if (IsEmpty) {
                 return "Empty";
             }
-            
+
             return String.Format ("{0}x{1}", width, height);
         }
     }
diff --git a/Hyena.Gui/Hyena.Gui.Canvas/Slider.cs b/Hyena.Gui/Hyena.Gui.Canvas/Slider.cs
new file mode 100644
index 0000000..82dd6c1
--- /dev/null
+++ b/Hyena.Gui/Hyena.Gui.Canvas/Slider.cs
@@ -0,0 +1,237 @@
+//
+// Slider.cs
+//
+// Author:
+//       Aaron Bockover <abockover novell com>
+//
+// Copyright 2009 Aaron Bockover
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+using Cairo;
+
+using Hyena.Gui;
+using Hyena.Gui.Theming;
+
+namespace Hyena.Gui.Canvas
+{
+    public class Slider : CanvasItem
+    {
+        private uint value_changed_inhibit_ref = 0;
+
+        public event EventHandler<EventArgs> ValueChanged;
+        public event EventHandler<EventArgs> PendingValueChanged;
+
+        public Slider ()
+        {
+            Margin = new Thickness (3);
+            MarginStyle = new ShadowMarginStyle {
+                ShadowSize = 3,
+                ShadowOpacity = 0.25
+            };
+        }
+
+        protected virtual void OnValueChanged ()
+        {
+            if (value_changed_inhibit_ref != 0) {
+                return;
+            }
+
+            var handler = ValueChanged;
+            if (handler != null) {
+                handler (this, EventArgs.Empty);
+            }
+        }
+
+        protected virtual void OnPendingValueChanged ()
+        {
+            var handler = PendingValueChanged;
+            if (handler != null) {
+                handler (this, EventArgs.Empty);
+            }
+        }
+
+        public void InhibitValueChangeEvent ()
+        {
+            value_changed_inhibit_ref++;
+        }
+
+        public void UninhibitValueChangeEvent ()
+        {
+            value_changed_inhibit_ref--;
+        }
+
+        private void SetPendingValueFromX (double x)
+        {
+            IsValueUpdatePending = true;
+            PendingValue = Math.Max (0, Math.Min ((x - ThrobberSize / 2) / RenderSize.Width, 1));
+        }
+
+        public override void ButtonPress (double x, double y, uint button)
+        {
+            if (button == 1) {
+                GrabPointer ();
+                SetPendingValueFromX (x);
+            }
+        }
+
+        public override void ButtonRelease ()
+        {
+            if (IsPointerGrabbed) {
+                ReleasePointer ();
+                Value = PendingValue;
+                IsValueUpdatePending = false;
+            }
+        }
+
+        public override void PointerMotion (double x, double y)
+        {
+            if (IsPointerGrabbed) {
+                SetPendingValueFromX (x);
+            }
+        }
+
+        private double last_invalidate_value = -1;
+
+        private void Invalidate ()
+        {
+            double current_value = (IsValueUpdatePending ? PendingValue : Value);
+
+            // FIXME: Something is wrong with the updating below causing an
+            // invalid region when IsValueUpdatePending is true, so when
+            // that is the case for now, we trigger a full invalidation
+            if (last_invalidate_value < 0 || IsValueUpdatePending) {
+                last_invalidate_value = current_value;
+                InvalidateRender ();
+                return;
+            }
+
+            double max = Math.Max (last_invalidate_value, current_value) * RenderSize.Width;
+            double min = Math.Min (last_invalidate_value, current_value) * RenderSize.Width;
+
+            Rect region = new Rect (
+                InvalidationRect.X + min,
+                InvalidationRect.Y,
+                (max - min) + 2 * ThrobberSize,
+                InvalidationRect.Height
+            );
+
+            last_invalidate_value = current_value;
+            InvalidateRender (region);
+        }
+
+        protected override Rect InvalidationRect {
+            get { return new Rect (
+                -Margin.Left - ThrobberSize / 2,
+                -Margin.Top,
+                Allocation.Width + ThrobberSize,
+                Allocation.Height);
+            }
+        }
+
+        protected override void ClippedRender (Cairo.Context cr)
+        {
+            double throbber_r = ThrobberSize / 2.0;
+            double throbber_x = Math.Round (RenderSize.Width * (IsValueUpdatePending ? PendingValue : Value));
+            double throbber_y = (Allocation.Height - ThrobberSize) / 2.0 - Margin.Top + throbber_r;
+            double bar_w = RenderSize.Width * Value;
+
+            cr.Color = Theme.Colors.GetWidgetColor (GtkColorClass.Base, Gtk.StateType.Normal);
+            cr.Rectangle (0, 0, RenderSize.Width, RenderSize.Height);
+            cr.Fill ();
+
+            Color color = Theme.Colors.GetWidgetColor (GtkColorClass.Dark, Gtk.StateType.Active);
+            Color fill_color = CairoExtensions.ColorShade (color, 0.4);
+            Color light_fill_color = CairoExtensions.ColorShade (color, 0.3);
+            fill_color.A = 1.0;
+            light_fill_color.A = 1.0;
+
+            LinearGradient fill = new LinearGradient (0, 0, 0, RenderSize.Height);
+            fill.AddColorStop (0, light_fill_color);
+            fill.AddColorStop (0.5, fill_color);
+            fill.AddColorStop (1, light_fill_color);
+
+            cr.Rectangle (0, 0, bar_w, RenderSize.Height);
+            cr.Pattern = fill;
+            cr.Fill ();
+
+            cr.Color = fill_color;
+            cr.Arc (throbber_x, throbber_y, throbber_r, 0, Math.PI * 2);
+            cr.Fill ();
+        }
+
+        public override Size Measure (Size available)
+        {
+            Height = BarSize;
+            return DesiredSize = new Size (base.Measure (available).Width,
+                Height + Margin.Top + Margin.Bottom);
+        }
+
+        private double bar_size = 3;
+        public virtual double BarSize {
+            get { return bar_size; }
+            set { bar_size = value; }
+        }
+
+        private double throbber_size = 7;
+        public virtual double ThrobberSize {
+            get { return throbber_size; }
+            set { throbber_size = value; }
+        }
+
+        private double value;
+        public virtual double Value {
+            get { return this.value; }
+            set {
+                if (value < 0.0 || value > 1.0) {
+                    throw new ArgumentOutOfRangeException ("Value", "Must be between 0.0 and 1.0 inclusive");
+                } else if (this.value == value) {
+                    return;
+                }
+
+                this.value = value;
+                Invalidate ();
+                OnValueChanged ();
+            }
+        }
+
+        private bool is_value_update_pending;
+        public virtual bool IsValueUpdatePending {
+            get { return is_value_update_pending; }
+            set { is_value_update_pending = value; }
+        }
+
+        private double pending_value;
+        public virtual double PendingValue {
+            get { return pending_value; }
+            set {
+                if (value < 0.0 || value > 1.0) {
+                    throw new ArgumentOutOfRangeException ("Value", "Must be between 0.0 and 1.0 inclusive");
+                } else if (pending_value == value) {
+                    return;
+                }
+
+                pending_value = value;
+                Invalidate ();
+                OnPendingValueChanged ();
+            }
+        }
+    }
+}
diff --git a/Hyena.Gui/Hyena.Gui.Canvas/StackPanel.cs b/Hyena.Gui/Hyena.Gui.Canvas/StackPanel.cs
new file mode 100644
index 0000000..e02fbec
--- /dev/null
+++ b/Hyena.Gui/Hyena.Gui.Canvas/StackPanel.cs
@@ -0,0 +1,153 @@
+//
+// StackPanel.cs
+//
+// Author:
+//       Aaron Bockover <abockover novell com>
+//
+// Copyright 2009 Aaron Bockover
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+
+namespace Hyena.Gui.Canvas
+{
+    public class StackPanel : Panel
+    {
+        private Orientation orientation;
+        public double spacing;
+
+        public StackPanel ()
+        {
+        }
+
+        public override Size Measure (Size available)
+        {
+            Size result = new Size (0, 0);
+
+            foreach (var child in Children) {
+                if (!child.Visible) {
+                    continue;
+                }
+
+                Size size = child.Measure (available);
+
+                if (Orientation == Orientation.Vertical) {
+                    result.Height += size.Height;
+                    result.Width = Math.Max (result.Width, size.Width);
+                } else {
+                    result.Width += size.Width;
+                    result.Height = Math.Max (result.Height, size.Height);
+                }
+            }
+
+            if (!Double.IsNaN (Width)) {
+                result.Width = Width;
+            }
+
+            if (!Double.IsNaN (Height)) {
+                result.Height = Height;
+            }
+
+            if (!available.IsEmpty) {
+                result.Width = Math.Min (result.Width, available.Width);
+                result.Height = Math.Min (result.Height, available.Height);
+            }
+
+            if (Orientation == Orientation.Vertical) {
+                result.Height += Spacing * (Children.Count - 1);
+            } else {
+                result.Width += Spacing * (Children.Count - 1);
+            }
+
+            return DesiredSize = result;
+        }
+
+        public override void Arrange ()
+        {
+            int visible_child_count = 0;
+            int flex_count = 0;
+            double offset = 0;
+            double static_space = 0;
+            double flex_space = 0;
+
+            foreach (var child in Children) {
+                if (!child.Visible) {
+                    continue;
+                }
+
+                child.Measure (ContentSize);
+
+                visible_child_count++;
+
+                if (Orientation == Orientation.Vertical) {
+                    static_space += Double.IsNaN (child.Height) ? 0 : child.DesiredSize.Height;
+                    flex_count += Double.IsNaN (child.Height) ? 1 : 0;
+                } else {
+                    static_space += Double.IsNaN (child.Width) ? 0 : child.DesiredSize.Width;
+                    flex_count += Double.IsNaN (child.Width) ? 1 : 0;
+                }
+            }
+
+            flex_space = (Orientation == Orientation.Vertical ? ContentAllocation.Height : ContentAllocation.Width) -
+                static_space - (visible_child_count - 1) * Spacing;
+            if (flex_space < 0) {
+                flex_space = 0;
+            }
+
+            foreach (var child in Children) {
+                if (!child.Visible) {
+                    continue;
+                }
+
+                double variable_size = 0;
+
+                if ((Orientation == Orientation.Vertical && Double.IsNaN (child.Height)) ||
+                    (Orientation == Orientation.Horizontal && Double.IsNaN (child.Width))) {
+                    variable_size = flex_space / flex_count--;
+                    flex_space -= variable_size;
+                    if (flex_count == 0) {
+                        variable_size += flex_space;
+                    }
+                } else if (Orientation == Orientation.Vertical) {
+                    variable_size = child.DesiredSize.Height;
+                } else {
+                    variable_size = child.DesiredSize.Width;
+                }
+
+                child.Allocation = Orientation == Orientation.Vertical
+                    ? new Rect (0, offset, ContentAllocation.Width, variable_size)
+                    : new Rect (offset, 0, variable_size, ContentAllocation.Height);
+                child.Arrange ();
+
+                offset += variable_size + Spacing;
+            }
+        }
+
+        public Orientation Orientation {
+            get { return orientation; }
+            set { orientation = value; }
+        }
+
+        public double Spacing {
+            get { return spacing; }
+            set { spacing = value; }
+        }
+    }
+}
diff --git a/Hyena.Gui/Hyena.Gui.Canvas/TestTile.cs b/Hyena.Gui/Hyena.Gui.Canvas/TestTile.cs
new file mode 100644
index 0000000..3d58654
--- /dev/null
+++ b/Hyena.Gui/Hyena.Gui.Canvas/TestTile.cs
@@ -0,0 +1,62 @@
+//
+// TestTile.cs
+//
+// Author:
+//       Aaron Bockover <abockover novell com>
+//
+// Copyright 2009 Aaron Bockover
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+
+using Cairo;
+using Hyena.Gui;
+
+namespace Hyena.Gui.Canvas
+{
+    public class TestTile : CanvasItem
+    {
+        private static Random rand = new Random ();
+        private Color color;
+        private bool color_set;
+
+        private bool change_on_render = true;
+        public bool ChangeOnRender {
+            get { return change_on_render; }
+            set { change_on_render = value; }
+        }
+
+        public TestTile ()
+        {
+        }
+
+        protected override void ClippedRender (Context cr)
+        {
+            if (!color_set || ChangeOnRender) {
+                color = CairoExtensions.RgbToColor ((uint)rand.Next (0, 0xffffff));
+                color_set = true;
+            }
+
+            CairoExtensions.RoundedRectangle (cr, 0, 0, RenderSize.Width, RenderSize.Height, 5);
+            cr.Color = color;
+            cr.Fill ();
+        }
+    }
+}
diff --git a/Hyena.Gui/Hyena.Gui.Canvas/TextBlock.cs b/Hyena.Gui/Hyena.Gui.Canvas/TextBlock.cs
new file mode 100644
index 0000000..3569bd6
--- /dev/null
+++ b/Hyena.Gui/Hyena.Gui.Canvas/TextBlock.cs
@@ -0,0 +1,260 @@
+//
+// TextBlock.cs
+//
+// Author:
+//       Aaron Bockover <abockover novell com>
+//
+// Copyright 2009 Aaron Bockover
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+
+using Cairo;
+
+namespace Hyena.Gui.Canvas
+{
+    public class TextBlock : CanvasItem
+    {
+        private Pango.Layout layout;
+        private Rect text_alloc = Rect.Empty;
+        private Rect invalidation_rect = Rect.Empty;
+
+        public TextBlock ()
+        {
+            InstallProperty<string> ("Text", String.Empty);
+            InstallProperty<double> ("HorizontalAlignment", 0.0);
+            InstallProperty<double> ("VerticalAlignment", 0.0);
+            InstallProperty<FontWeight> ("FontWeight", FontWeight.Normal);
+            InstallProperty<TextWrap> ("TextWrap", TextWrap.None);
+            InstallProperty<bool> ("ForceSize", false);
+        }
+
+        private bool EnsureLayout ()
+        {
+            if (layout != null) {
+                return true;
+            }
+
+            Gtk.Widget widget = Manager == null ? null : Manager.Host as Gtk.Widget;
+            if (widget == null || widget.GdkWindow == null || !widget.IsRealized) {
+                return false;
+            }
+
+            using (var cr = Gdk.CairoHelper.Create (widget.GdkWindow)) {
+                layout = CairoExtensions.CreateLayout (widget, cr);
+            }
+
+            return layout != null;
+        }
+
+        public override Size Measure (Size available)
+        {
+            if (!EnsureLayout ()) {
+                return new Size (0, 0);
+            }
+
+            available = base.Measure (available);
+
+            int text_w, text_h;
+
+            TextWrap wrap = TextWrap;
+            layout.Width = wrap == TextWrap.None ? -1 : (int)(Pango.Scale.PangoScale * available.Width);
+            layout.Wrap = GetPangoWrapMode (wrap);
+            layout.FontDescription.Weight = GetPangoFontWeight (FontWeight);
+            layout.SetText (Text);
+            layout.GetPixelSize (out text_w, out text_h);
+
+            double width = text_w;
+            if (!available.IsEmpty && available.Width > 0) {
+                width = available.Width;
+            }
+
+            DesiredSize = new Size (
+                width + Margin.Left + Margin.Right,
+                text_h + Margin.Top + Margin.Bottom);
+
+            // Hack, as this prevents the TextBlock from
+            // being flexible in a Vertical StackPanel
+            Height = DesiredSize.Height;
+
+            if (ForceSize) {
+                Width = DesiredSize.Width;
+            }
+
+            return DesiredSize;
+        }
+
+        public override void Arrange ()
+        {
+            if (!EnsureLayout ()) {
+                return;
+            }
+
+            int layout_width = TextWrap == TextWrap.None
+                ? -1
+                : (int)(Pango.Scale.PangoScale * ContentAllocation.Width);
+            if (layout.Width != layout_width) {
+                layout.Width = layout_width;
+            }
+
+            int text_width, text_height;
+            layout.GetPixelSize (out text_width, out text_height);
+
+            Rect new_alloc = new Rect (
+                Math.Round ((RenderSize.Width - text_width) * HorizontalAlignment),
+                Math.Round ((RenderSize.Height - text_height) * VerticalAlignment),
+                text_width,
+                text_height);
+
+            if (text_alloc.IsEmpty) {
+                InvalidateRender (text_alloc);
+            } else {
+                invalidation_rect = text_alloc;
+                invalidation_rect.Union (new_alloc);
+
+                // Some padding, likely because of the pen size for
+                // showing the actual text layout in the render pass
+                invalidation_rect.X -= 2;
+                invalidation_rect.Y -= 2;
+                invalidation_rect.Width += 4;
+                invalidation_rect.Height += 4;
+
+                InvalidateRender (invalidation_rect);
+            }
+
+            text_alloc = new_alloc;
+        }
+
+        protected override void ClippedRender (Context cr)
+        {
+            if (!EnsureLayout ()) {
+                return;
+            }
+
+            Brush foreground = Foreground;
+            if (!foreground.IsValid) {
+                return;
+            }
+
+            cr.Rectangle (0, 0, RenderSize.Width, RenderSize.Height);
+            cr.Clip ();
+
+            bool fade = text_alloc.Width > RenderSize.Width;
+
+            if (fade) {
+                cr.PushGroup ();
+            }
+
+            cr.MoveTo (text_alloc.X, text_alloc.Y);
+            Foreground.Apply (cr);
+            Pango.CairoHelper.ShowLayout (cr, layout);
+            cr.Fill ();
+
+            if (fade) {
+                LinearGradient mask = new LinearGradient (RenderSize.Width - 20, 0, RenderSize.Width, 0);
+                mask.AddColorStop (0, new Color (0, 0, 0, 1));
+                mask.AddColorStop (1, new Color (0, 0, 0, 0));
+
+                cr.PopGroupToSource ();
+                cr.Mask (mask);
+                mask.Destroy ();
+            }
+
+            cr.ResetClip ();
+        }
+
+        private Pango.Weight GetPangoFontWeight (FontWeight weight)
+        {
+            switch (weight) {
+                case FontWeight.Bold: return Pango.Weight.Bold;
+                default: return Pango.Weight.Normal;
+            }
+        }
+
+        private Pango.WrapMode GetPangoWrapMode (TextWrap wrap)
+        {
+            switch (wrap) {
+                case TextWrap.Char: return Pango.WrapMode.Char;
+                case TextWrap.WordChar: return Pango.WrapMode.WordChar;
+                case TextWrap.None:
+                case TextWrap.Word:
+                default:
+                    return Pango.WrapMode.Word;
+            }
+        }
+
+        protected override bool OnPropertyChange (string property, object value)
+        {
+            switch (property) {
+                case "FontWeight":
+                case "TextWrap":
+                case "Text":
+                case "ForceSize":
+                    if (layout != null) {
+                        InvalidateMeasure ();
+                        InvalidateArrange ();
+                    }
+                    return true;
+                case "HorizontalAlignment":
+                case "VerticalAlignment":
+                    if (layout != null) {
+                        InvalidateArrange ();
+                    }
+                    return true;
+            }
+
+            return base.OnPropertyChange (property, value);
+        }
+
+        protected override Rect InvalidationRect {
+            get { return invalidation_rect; }
+        }
+
+        public string Text {
+            get { return GetValue<string> ("Text"); }
+            set { SetValue<string> ("Text", value); }
+        }
+
+        public FontWeight FontWeight {
+            get { return GetValue<FontWeight> ("FontWeight"); }
+            set { SetValue<FontWeight> ("FontWeight", value); }
+        }
+
+        public TextWrap TextWrap {
+            get { return GetValue<TextWrap> ("TextWrap"); }
+            set { SetValue<TextWrap> ("TextWrap", value); }
+        }
+
+        public bool ForceSize {
+            get { return GetValue<bool> ("ForceSize"); }
+            set { SetValue<bool> ("ForceSize", value); }
+        }
+
+        public double HorizontalAlignment {
+            get { return GetValue<double> ("HorizontalAlignment", 0.5); }
+            set { SetValue<double> ("HorizontalAlignment", value); }
+        }
+
+        public double VerticalAlignment {
+            get { return GetValue<double> ("VerticalAlignment", 0.0); }
+            set { SetValue<double> ("VerticalAlignment", value); }
+        }
+    }
+}
diff --git a/Hyena.Gui/Hyena.Gui.Canvas/TextWrap.cs b/Hyena.Gui/Hyena.Gui.Canvas/TextWrap.cs
new file mode 100644
index 0000000..f14624f
--- /dev/null
+++ b/Hyena.Gui/Hyena.Gui.Canvas/TextWrap.cs
@@ -0,0 +1,38 @@
+//
+// TextWrap.cs
+//
+// Author:
+//       Aaron Bockover <abockover novell com>
+//
+// Copyright 2009 Aaron Bockover
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+
+namespace Hyena.Gui.Canvas
+{
+    public enum TextWrap
+    {
+        None,
+        Word,
+        Char,
+        WordChar
+    }
+}
diff --git a/Hyena.Gui/Hyena.Gui.Canvas/Thickness.cs b/Hyena.Gui/Hyena.Gui.Canvas/Thickness.cs
index 56fa88d..f0f59db 100644
--- a/Hyena.Gui/Hyena.Gui.Canvas/Thickness.cs
+++ b/Hyena.Gui/Hyena.Gui.Canvas/Thickness.cs
@@ -38,17 +38,17 @@ namespace Hyena.Gui.Canvas
         private double bottom;
 
         public static readonly Thickness Zero = new Thickness (0);
-        
-        public Thickness (double thickness) 
+
+        public Thickness (double thickness)
             : this (thickness, thickness, thickness, thickness)
         {
         }
-        
-        public Thickness (double xthickness, double ythickness) 
+
+        public Thickness (double xthickness, double ythickness)
             : this (xthickness, ythickness, xthickness, ythickness)
         {
         }
-        
+
         public Thickness (double left, double top, double right, double bottom)
         {
             this.left = left;
@@ -56,13 +56,13 @@ namespace Hyena.Gui.Canvas
             this.right = right;
             this.bottom = bottom;
         }
-        
+
         public override string ToString ()
         {
             return string.Format ("{0},{1},{2},{3}", Double.IsNaN (left) ? "Auto" : left.ToString (),
-                Double.IsNaN (top) ? "Auto" : top.ToString (), 
-                Double.IsNaN (right) ? "Auto" : right.ToString (), 
-                Double.IsNaN (bottom) ? "Auto" : bottom.ToString ()); 
+                Double.IsNaN (top) ? "Auto" : top.ToString (),
+                Double.IsNaN (right) ? "Auto" : right.ToString (),
+                Double.IsNaN (bottom) ? "Auto" : bottom.ToString ());
         }
 
         public override bool Equals (object o)
@@ -70,20 +70,20 @@ namespace Hyena.Gui.Canvas
             if (!(o is Thickness)) {
                 return false;
             }
-            
+
             return this == (Thickness)o;
         }
-        
+
         public bool Equals (Thickness thickness)
         {
             return this == thickness;
         }
-        
+
         public override int GetHashCode ()
         {
             return left.GetHashCode () ^ top.GetHashCode () ^ right.GetHashCode () ^ bottom.GetHashCode ();
         }
-        
+
         public static bool operator == (Thickness t1, Thickness t2)
         {
             return t1.left == t2.left &&
@@ -91,28 +91,28 @@ namespace Hyena.Gui.Canvas
                 t1.top == t2.top &&
                 t1.bottom == t2.bottom;
         }
-        
+
         public static bool operator != (Thickness t1, Thickness t2)
         {
             return !(t1 == t2);
         }
-        
+
         public double Left {
             get { return left; }
             set { left = value; }
         }
-        
+
         public double Top {
             get { return top; }
-            set { top = value; }    
+            set { top = value; }
         }
-        
-        public double Right { 
+
+        public double Right {
             get { return right; }
             set { right = value; }
         }
-        
-        public double Bottom { 
+
+        public double Bottom {
             get { return bottom; }
             set { bottom = value; }
         }
diff --git a/Hyena.Gui/Makefile.am b/Hyena.Gui/Makefile.am
index 27faf5d..e7b6c8c 100644
--- a/Hyena.Gui/Makefile.am
+++ b/Hyena.Gui/Makefile.am
@@ -43,10 +43,31 @@ SOURCES =  \
 	Hyena.Data.Gui/ObjectListView.cs \
 	Hyena.Data.Gui/RowActivatedHandler.cs \
 	Hyena.Data.Gui/SortableColumn.cs \
-	Hyena.Gui.Canvas/Point.cs \
-	Hyena.Gui.Canvas/Rect.cs \
-	Hyena.Gui.Canvas/Size.cs \
-	Hyena.Gui.Canvas/Thickness.cs \
+    Hyena.Gui.Canvas/AnimationManager.cs \
+    Hyena.Gui.Canvas/Brush.cs \
+    Hyena.Gui.Canvas/CanvasHost.cs \
+    Hyena.Gui.Canvas/CanvasItem.cs \
+    Hyena.Gui.Canvas/CanvasItemCollection.cs \
+    Hyena.Gui.Canvas/CanvasManager.cs \
+    Hyena.Gui.Canvas/FixedPanel.cs \
+    Hyena.Gui.Canvas/FontWeight.cs \
+    Hyena.Gui.Canvas/FpsCalculator.cs \
+    Hyena.Gui.Canvas/ICanvasHost.cs \
+    Hyena.Gui.Canvas/Image.cs \
+    Hyena.Gui.Canvas/ImageBrush.cs \
+    Hyena.Gui.Canvas/MarginStyle.cs \
+    Hyena.Gui.Canvas/Orientation.cs \
+    Hyena.Gui.Canvas/Panel.cs \
+    Hyena.Gui.Canvas/Point.cs \
+    Hyena.Gui.Canvas/Rect.cs \
+    Hyena.Gui.Canvas/ShadowMarginStyle.cs \
+    Hyena.Gui.Canvas/Size.cs \
+    Hyena.Gui.Canvas/Slider.cs \
+    Hyena.Gui.Canvas/StackPanel.cs \
+    Hyena.Gui.Canvas/TestTile.cs \
+    Hyena.Gui.Canvas/TextBlock.cs \
+    Hyena.Gui.Canvas/TextWrap.cs \
+    Hyena.Gui.Canvas/Thickness.cs \
 	Hyena.Gui.Dialogs/ExceptionDialog.cs \
 	Hyena.Gui.Dialogs/VersionInformationDialog.cs \
 	Hyena.Gui.Theatrics/Actor.cs \



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