diff --git a/Perspex.Base/PerspexObject.cs b/Perspex.Base/PerspexObject.cs index be12367034..79134d9890 100644 --- a/Perspex.Base/PerspexObject.cs +++ b/Perspex.Base/PerspexObject.cs @@ -593,7 +593,7 @@ namespace Perspex { Contract.Requires(property != null); - return this.Bind((PerspexProperty)property, (IObservable)source, priority); + return this.Bind((PerspexProperty)property, source.Select(x => (object)x), priority); } /// diff --git a/Perspex.Controls/Perspex.Controls.csproj b/Perspex.Controls/Perspex.Controls.csproj index f7ceec5d0d..a4d49af8ba 100644 --- a/Perspex.Controls/Perspex.Controls.csproj +++ b/Perspex.Controls/Perspex.Controls.csproj @@ -41,6 +41,7 @@ + @@ -71,6 +72,7 @@ + diff --git a/Perspex.Controls/Presenters/ScrollContentPresenter.cs b/Perspex.Controls/Presenters/ScrollContentPresenter.cs new file mode 100644 index 0000000000..ae3b89199c --- /dev/null +++ b/Perspex.Controls/Presenters/ScrollContentPresenter.cs @@ -0,0 +1,78 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2014 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex.Controls.Presenters +{ + using System.Linq; + using Perspex.Layout; + + public class ScrollContentPresenter : ContentPresenter + { + public static PerspexProperty ExtentProperty = + ScrollViewer.ExtentProperty.AddOwner(); + + public static PerspexProperty OffsetProperty = + ScrollViewer.OffsetProperty.AddOwner(); + + public static PerspexProperty ViewportProperty = + ScrollViewer.ViewportProperty.AddOwner(); + + public ScrollContentPresenter() + { + AffectsRender(OffsetProperty); + } + + public Size Extent + { + get { return this.GetValue(ExtentProperty); } + private set { this.SetValue(ExtentProperty, value); } + } + + public Vector Offset + { + get { return this.GetValue(OffsetProperty); } + set { this.SetValue(OffsetProperty, value); } + } + + public Size Viewport + { + get { return this.GetValue(ViewportProperty); } + private set { this.SetValue(ViewportProperty, value); } + } + + protected override Size MeasureOverride(Size availableSize) + { + var content = this.Content as ILayoutable; + + if (content != null) + { + content.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity)); + var size = content.DesiredSize.Value; + this.Extent = size; + return size.Constrain(availableSize); + } + else + { + return this.Extent = new Size(); + } + } + + protected override Size ArrangeOverride(Size finalSize) + { + var child = this.GetVisualChildren().SingleOrDefault() as ILayoutable; + + this.Viewport = finalSize; + + if (child != null) + { + child.Arrange(new Rect((Point)(-this.Offset), child.DesiredSize.Value)); + return finalSize; + } + + return new Size(); + } + } +} diff --git a/Perspex.Controls/Primitives/TemplatedControl.cs b/Perspex.Controls/Primitives/TemplatedControl.cs index 6d7e6c91cf..08caa61615 100644 --- a/Perspex.Controls/Primitives/TemplatedControl.cs +++ b/Perspex.Controls/Primitives/TemplatedControl.cs @@ -71,6 +71,26 @@ namespace Perspex.Controls.Primitives } } + protected T FindTemplateChild(string id) where T : Control + { + return this.GetTemplateControls().OfType().FirstOrDefault(x => x.Id == id); + } + + protected T GetTemplateChild(string id) where T : Control + { + var result = this.FindTemplateChild(id); + + if (result == null) + { + throw new InvalidOperationException(string.Format( + "Could not find template child '{0}' in template for '{1}'.", + id, + this.GetType().FullName)); + } + + return result; + } + protected virtual void OnTemplateApplied() { } diff --git a/Perspex.Controls/ScrollViewer.cs b/Perspex.Controls/ScrollViewer.cs new file mode 100644 index 0000000000..50db1338fd --- /dev/null +++ b/Perspex.Controls/ScrollViewer.cs @@ -0,0 +1,95 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2014 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +using System.Reactive.Linq; +using Perspex.Controls.Presenters; +using Perspex.Controls.Primitives; + +namespace Perspex.Controls +{ + public class ScrollViewer : ContentControl + { + public static PerspexProperty ExtentProperty = + PerspexProperty.Register("Extent"); + + public static PerspexProperty OffsetProperty = + PerspexProperty.Register("Offset"); + + public static PerspexProperty ViewportProperty = + PerspexProperty.Register("Viewport"); + + private ScrollContentPresenter presenter; + + private ScrollBar horizontalScrollBar; + + private ScrollBar verticalScrollBar; + + public Size Extent + { + get { return this.GetValue(ExtentProperty); } + private set { this.SetValue(ExtentProperty, value); } + } + + public Vector Offset + { + get { return this.GetValue(OffsetProperty); } + set { this.SetValue(OffsetProperty, value); } + } + + public Size Viewport + { + get { return this.GetValue(ViewportProperty); } + private set { this.SetValue(ViewportProperty, value); } + } + + protected override void OnTemplateApplied() + { + this.presenter = this.GetTemplateChild("presenter"); + this.horizontalScrollBar = this.GetTemplateChild("horizontalScrollBar"); + this.verticalScrollBar = this.GetTemplateChild("verticalScrollBar"); + + this[!ExtentProperty] = this.presenter[!ExtentProperty]; + this[!ViewportProperty] = this.presenter[!ViewportProperty]; + this.presenter[!OffsetProperty] = this[!OffsetProperty]; + + var extentAndViewport = Observable.CombineLatest( + this.GetObservable(ExtentProperty).StartWith(this.Extent), + this.GetObservable(ViewportProperty).StartWith(this.Viewport)) + .Select(x => new { Extent = x[0], Viewport = x[1] }); + + //this.horizontalScrollBar.Bind( + // IsVisibleProperty, + // extentAndViewport.Select(x => x.Extent.Width > x.Viewport.Width)); + + this.horizontalScrollBar.Bind( + ScrollBar.MaximumProperty, + extentAndViewport.Select(x => x.Extent.Width)); + + this.horizontalScrollBar.Bind( + ScrollBar.ViewportSizeProperty, + extentAndViewport.Select(x => x.Viewport.Width)); + + //this.verticalScrollBar.Bind( + // IsVisibleProperty, + // extentAndViewport.Select(x => x.Extent.Height > x.Viewport.Height)); + + this.verticalScrollBar.Bind( + ScrollBar.MaximumProperty, + extentAndViewport.Select(x => x.Extent.Height)); + + this.verticalScrollBar.Bind( + ScrollBar.ViewportSizeProperty, + extentAndViewport.Select(x => x.Viewport.Height)); + + var offset = Observable.CombineLatest( + this.horizontalScrollBar.GetObservable(ScrollBar.ValueProperty), + this.verticalScrollBar.GetObservable(ScrollBar.ValueProperty)) + .Select(x => new Vector(x[0], x[1])); + + this.Bind(OffsetProperty, offset); + } + } +} diff --git a/Perspex.SceneGraph/Vector.cs b/Perspex.SceneGraph/Vector.cs index 7222ef7c51..32b58ed609 100644 --- a/Perspex.SceneGraph/Vector.cs +++ b/Perspex.SceneGraph/Vector.cs @@ -65,6 +65,11 @@ namespace Perspex return new Vector(a.x - b.x, a.y - b.y); } + public static explicit operator Point(Vector a) + { + return new Point(a.x, a.y); + } + /// /// Returns the string representation of the point. /// diff --git a/Perspex.Themes.Default/DefaultTheme.cs b/Perspex.Themes.Default/DefaultTheme.cs index f09b82db2c..0d4e986785 100644 --- a/Perspex.Themes.Default/DefaultTheme.cs +++ b/Perspex.Themes.Default/DefaultTheme.cs @@ -18,6 +18,7 @@ namespace Perspex.Themes.Default this.Add(new GridSplitterStyle()); this.Add(new ItemsControlStyle()); this.Add(new ScrollBarStyle()); + this.Add(new ScrollViewerStyle()); this.Add(new TabControlStyle()); this.Add(new TabItemStyle()); this.Add(new TabStripStyle()); diff --git a/Perspex.Themes.Default/Perspex.Themes.Default.csproj b/Perspex.Themes.Default/Perspex.Themes.Default.csproj index b79b52b07a..643059bcba 100644 --- a/Perspex.Themes.Default/Perspex.Themes.Default.csproj +++ b/Perspex.Themes.Default/Perspex.Themes.Default.csproj @@ -66,6 +66,7 @@ + diff --git a/Perspex.Themes.Default/ScrollViewerStyle.cs b/Perspex.Themes.Default/ScrollViewerStyle.cs new file mode 100644 index 0000000000..c58d5d4fb0 --- /dev/null +++ b/Perspex.Themes.Default/ScrollViewerStyle.cs @@ -0,0 +1,69 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2014 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex.Themes.Default +{ + using System.Linq; + using Perspex.Controls; + using Perspex.Controls.Presenters; + using Perspex.Controls.Primitives; + using Perspex.Media; + using Perspex.Styling; + + public class ScrollViewerStyle : Styles + { + public ScrollViewerStyle() + { + this.AddRange(new[] + { + new Style(x => x.OfType()) + { + Setters = new[] + { + new Setter(ScrollViewer.TemplateProperty, ControlTemplate.Create(this.Template)), + }, + }, + }); + } + + private Control Template(ScrollViewer control) + { + return new Grid + { + ColumnDefinitions = new ColumnDefinitions + { + new ColumnDefinition(1, GridUnitType.Star), + new ColumnDefinition(GridLength.Auto), + }, + RowDefinitions = new RowDefinitions + { + new RowDefinition(1, GridUnitType.Star), + new RowDefinition(GridLength.Auto), + }, + Children = new Controls + { + new ScrollContentPresenter + { + Id = "presenter", + [~ContentPresenter.ContentProperty] = control[~ScrollViewer.ContentProperty], + }, + new ScrollBar + { + Id = "horizontalScrollBar", + Orientation = Orientation.Horizontal, + [Grid.RowProperty] = 1, + }, + new ScrollBar + { + Id = "verticalScrollBar", + Orientation = Orientation.Vertical, + [Grid.ColumnProperty] = 1, + }, + }, + }; + } + } +} diff --git a/TestApplication/Program.cs b/TestApplication/Program.cs index 6f745f678e..f508d7b064 100644 --- a/TestApplication/Program.cs +++ b/TestApplication/Program.cs @@ -116,6 +116,7 @@ namespace TestApplication { ButtonsTab(), TextTab(), + ImagesTab(), ListsTab(), SlidersTab(), LayoutTab(), @@ -211,14 +212,19 @@ namespace TestApplication HorizontalAlignment = HorizontalAlignment.Center, VerticalAlignment = VerticalAlignment.Center, Gap = 8, - Width = 120, Children = new Controls { - new Image + new ScrollViewer { - Source = new Bitmap("github_icon.png"), Width = 200, - }, + Height = 200, + Content = new Image + { + Source = new Bitmap("github_icon.png"), + Width = 400, + Height = 400, + }, + } } }, };