aboutsummaryrefslogtreecommitdiff
path: root/MediaBrowser.UI.Controls
diff options
context:
space:
mode:
authorLukePulverenti <luke.pulverenti@gmail.com>2013-02-20 20:33:05 -0500
committerLukePulverenti <luke.pulverenti@gmail.com>2013-02-20 20:33:05 -0500
commit767cdc1f6f6a63ce997fc9476911e2c361f9d402 (patch)
tree49add55976f895441167c66cfa95e5c7688d18ce /MediaBrowser.UI.Controls
parent845554722efaed872948a9e0f7202e3ef52f1b6e (diff)
Pushing missing changes
Diffstat (limited to 'MediaBrowser.UI.Controls')
-rw-r--r--MediaBrowser.UI.Controls/BaseModalWindow.cs62
-rw-r--r--MediaBrowser.UI.Controls/BaseUserControl.cs21
-rw-r--r--MediaBrowser.UI.Controls/BaseWindow.cs175
-rw-r--r--MediaBrowser.UI.Controls/ExtendedButton.cs49
-rw-r--r--MediaBrowser.UI.Controls/ExtendedCheckbox.cs49
-rw-r--r--MediaBrowser.UI.Controls/ExtendedListBox.cs260
-rw-r--r--MediaBrowser.UI.Controls/ExtendedRadioButton.cs49
-rw-r--r--MediaBrowser.UI.Controls/ExtendedScrollViewer.cs41
-rw-r--r--MediaBrowser.UI.Controls/ItemEventArgs.cs17
-rw-r--r--MediaBrowser.UI.Controls/MediaBrowser.UI.Controls.csproj107
-rw-r--r--MediaBrowser.UI.Controls/Properties/AssemblyInfo.cs55
-rw-r--r--MediaBrowser.UI.Controls/Properties/Resources.Designer.cs62
-rw-r--r--MediaBrowser.UI.Controls/Properties/Resources.resx117
-rw-r--r--MediaBrowser.UI.Controls/Properties/Settings.Designer.cs30
-rw-r--r--MediaBrowser.UI.Controls/Properties/Settings.settings7
-rw-r--r--MediaBrowser.UI.Controls/ScrollingPanel.cs404
-rw-r--r--MediaBrowser.UI.Controls/Themes/Generic.xaml17
-rw-r--r--MediaBrowser.UI.Controls/TransitionControl.cs147
-rw-r--r--MediaBrowser.UI.Controls/TransitionFrame.cs194
-rw-r--r--MediaBrowser.UI.Controls/TreeHelper.cs321
-rw-r--r--MediaBrowser.UI.Controls/VirtualizingWrapPanel.cs735
21 files changed, 2919 insertions, 0 deletions
diff --git a/MediaBrowser.UI.Controls/BaseModalWindow.cs b/MediaBrowser.UI.Controls/BaseModalWindow.cs
new file mode 100644
index 0000000000..90bd8114f6
--- /dev/null
+++ b/MediaBrowser.UI.Controls/BaseModalWindow.cs
@@ -0,0 +1,62 @@
+using System;
+using System.Windows;
+
+namespace MediaBrowser.UI.Controls
+{
+ /// <summary>
+ /// Class BaseModalWindow
+ /// </summary>
+ public class BaseModalWindow : BaseWindow
+ {
+ /// <summary>
+ /// Shows the modal.
+ /// </summary>
+ /// <param name="owner">The owner.</param>
+ public void ShowModal(Window owner)
+ {
+ WindowStyle = WindowStyle.None;
+ ResizeMode = ResizeMode.NoResize;
+ ShowInTaskbar = false;
+ WindowStartupLocation = WindowStartupLocation.Manual;
+ AllowsTransparency = true;
+
+ Width = owner.Width;
+ Height = owner.Height;
+ Top = owner.Top;
+ Left = owner.Left;
+ WindowState = owner.WindowState;
+ Owner = owner;
+
+ ShowDialog();
+ }
+
+ /// <summary>
+ /// Called when [browser back].
+ /// </summary>
+ protected override void OnBrowserBack()
+ {
+ base.OnBrowserBack();
+
+ CloseModal();
+ }
+
+ /// <summary>
+ /// Raises the <see cref="E:System.Windows.FrameworkElement.Initialized" /> event. This method is invoked whenever <see cref="P:System.Windows.FrameworkElement.IsInitialized" /> is set to true internally.
+ /// </summary>
+ /// <param name="e">The <see cref="T:System.Windows.RoutedEventArgs" /> that contains the event data.</param>
+ protected override void OnInitialized(EventArgs e)
+ {
+ base.OnInitialized(e);
+
+ DataContext = this;
+ }
+
+ /// <summary>
+ /// Closes the modal.
+ /// </summary>
+ protected virtual void CloseModal()
+ {
+ Close();
+ }
+ }
+}
diff --git a/MediaBrowser.UI.Controls/BaseUserControl.cs b/MediaBrowser.UI.Controls/BaseUserControl.cs
new file mode 100644
index 0000000000..e47fc84cf2
--- /dev/null
+++ b/MediaBrowser.UI.Controls/BaseUserControl.cs
@@ -0,0 +1,21 @@
+using System.ComponentModel;
+using System.Windows.Controls;
+
+namespace MediaBrowser.UI.Controls
+{
+ /// <summary>
+ /// Provides a base class for all user controls
+ /// </summary>
+ public abstract class BaseUserControl : UserControl
+ {
+ public event PropertyChangedEventHandler PropertyChanged;
+
+ public virtual void OnPropertyChanged(string name)
+ {
+ if (PropertyChanged != null)
+ {
+ PropertyChanged(this, new PropertyChangedEventArgs(name));
+ }
+ }
+ }
+}
diff --git a/MediaBrowser.UI.Controls/BaseWindow.cs b/MediaBrowser.UI.Controls/BaseWindow.cs
new file mode 100644
index 0000000000..0f3ff2874c
--- /dev/null
+++ b/MediaBrowser.UI.Controls/BaseWindow.cs
@@ -0,0 +1,175 @@
+using System;
+using System.ComponentModel;
+using System.Windows;
+using System.Windows.Input;
+
+namespace MediaBrowser.UI.Controls
+{
+ /// <summary>
+ /// Provides a base class for all Windows
+ /// </summary>
+ public abstract class BaseWindow : Window, INotifyPropertyChanged
+ {
+ /// <summary>
+ /// Occurs when [property changed].
+ /// </summary>
+ public event PropertyChangedEventHandler PropertyChanged;
+
+ /// <summary>
+ /// Called when [property changed].
+ /// </summary>
+ /// <param name="info">The info.</param>
+ public void OnPropertyChanged(String info)
+ {
+ if (PropertyChanged != null)
+ {
+ PropertyChanged(this, new PropertyChangedEventArgs(info));
+ }
+ }
+
+ /// <summary>
+ /// The _content scale
+ /// </summary>
+ private double _contentScale = 1;
+ /// <summary>
+ /// Gets the content scale.
+ /// </summary>
+ /// <value>The content scale.</value>
+ public double ContentScale
+ {
+ get { return _contentScale; }
+ private set
+ {
+ _contentScale = value;
+ OnPropertyChanged("ContentScale");
+ }
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="BaseWindow" /> class.
+ /// </summary>
+ protected BaseWindow()
+ : base()
+ {
+ SizeChanged += MainWindow_SizeChanged;
+ Loaded += BaseWindowLoaded;
+ }
+
+ /// <summary>
+ /// Bases the window loaded.
+ /// </summary>
+ /// <param name="sender">The sender.</param>
+ /// <param name="e">The <see cref="RoutedEventArgs" /> instance containing the event data.</param>
+ void BaseWindowLoaded(object sender, RoutedEventArgs e)
+ {
+ OnLoaded();
+ }
+
+ /// <summary>
+ /// Called when [loaded].
+ /// </summary>
+ protected virtual void OnLoaded()
+ {
+ MoveFocus(new TraversalRequest(FocusNavigationDirection.First));
+ }
+
+ /// <summary>
+ /// Handles the SizeChanged event of the MainWindow control.
+ /// </summary>
+ /// <param name="sender">The source of the event.</param>
+ /// <param name="e">The <see cref="SizeChangedEventArgs" /> instance containing the event data.</param>
+ void MainWindow_SizeChanged(object sender, SizeChangedEventArgs e)
+ {
+ ContentScale = e.NewSize.Height / 1080;
+ }
+
+ /// <summary>
+ /// Called when [browser back].
+ /// </summary>
+ protected virtual void OnBrowserBack()
+ {
+
+ }
+
+ /// <summary>
+ /// Called when [browser forward].
+ /// </summary>
+ protected virtual void OnBrowserForward()
+ {
+
+ }
+
+ /// <summary>
+ /// Invoked when an unhandled <see cref="E:System.Windows.Input.Keyboard.PreviewKeyDown" /> attached event reaches an element in its route that is derived from this class. Implement this method to add class handling for this event.
+ /// </summary>
+ /// <param name="e">The <see cref="T:System.Windows.Input.KeyEventArgs" /> that contains the event data.</param>
+ protected override void OnPreviewKeyDown(KeyEventArgs e)
+ {
+ if (IsBackPress(e))
+ {
+ e.Handled = true;
+
+ if (!e.IsRepeat)
+ {
+ OnBrowserBack();
+ }
+ }
+
+ else if (IsForwardPress(e))
+ {
+ e.Handled = true;
+
+ if (!e.IsRepeat)
+ {
+ OnBrowserForward();
+ }
+ }
+ base.OnPreviewKeyDown(e);
+ }
+
+ /// <summary>
+ /// Determines if a keypress should be treated as a backward press
+ /// </summary>
+ /// <param name="e">The <see cref="KeyEventArgs" /> instance containing the event data.</param>
+ /// <returns><c>true</c> if [is back press] [the specified e]; otherwise, <c>false</c>.</returns>
+ private bool IsBackPress(KeyEventArgs e)
+ {
+ if (e.Key == Key.Escape)
+ {
+ return true;
+ }
+
+ if (e.Key == Key.BrowserBack || e.Key == Key.Back)
+ {
+ return true;
+ }
+
+ if (e.SystemKey == Key.Left && e.KeyboardDevice.Modifiers.HasFlag(ModifierKeys.Alt))
+ {
+ return true;
+ }
+
+ return false;
+ }
+
+ /// <summary>
+ /// Determines if a keypress should be treated as a forward press
+ /// </summary>
+ /// <param name="e">The <see cref="KeyEventArgs" /> instance containing the event data.</param>
+ /// <returns><c>true</c> if [is forward press] [the specified e]; otherwise, <c>false</c>.</returns>
+ private bool IsForwardPress(KeyEventArgs e)
+ {
+ if (e.Key == Key.BrowserForward)
+ {
+ return true;
+ }
+
+ if (e.SystemKey == Key.RightAlt && e.KeyboardDevice.Modifiers.HasFlag(ModifierKeys.Alt))
+ {
+ return true;
+ }
+
+ return false;
+ }
+ }
+}
diff --git a/MediaBrowser.UI.Controls/ExtendedButton.cs b/MediaBrowser.UI.Controls/ExtendedButton.cs
new file mode 100644
index 0000000000..1b8e9039d4
--- /dev/null
+++ b/MediaBrowser.UI.Controls/ExtendedButton.cs
@@ -0,0 +1,49 @@
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Input;
+
+namespace MediaBrowser.UI.Controls
+{
+ /// <summary>
+ /// This subclass simply autofocuses itself when the mouse moves over it
+ /// </summary>
+ public class ExtendedButton : Button
+ {
+ private Point? _lastMouseMovePoint;
+
+ /// <summary>
+ /// Handles OnMouseMove to auto-select the item that's being moused over
+ /// </summary>
+ protected override void OnMouseMove(MouseEventArgs e)
+ {
+ base.OnMouseMove(e);
+
+ var window = this.GetWindow();
+
+ // If the cursor is currently hidden, don't bother reacting to it
+ if (Cursor == Cursors.None || window.Cursor == Cursors.None)
+ {
+ return;
+ }
+
+ // Store the last position for comparison purposes
+ // Even if the mouse is not moving this event will fire as elements are showing and hiding
+ var pos = e.GetPosition(window);
+
+ if (!_lastMouseMovePoint.HasValue)
+ {
+ _lastMouseMovePoint = pos;
+ return;
+ }
+
+ if (pos == _lastMouseMovePoint)
+ {
+ return;
+ }
+
+ _lastMouseMovePoint = pos;
+
+ Focus();
+ }
+ }
+}
diff --git a/MediaBrowser.UI.Controls/ExtendedCheckbox.cs b/MediaBrowser.UI.Controls/ExtendedCheckbox.cs
new file mode 100644
index 0000000000..120fa3c24d
--- /dev/null
+++ b/MediaBrowser.UI.Controls/ExtendedCheckbox.cs
@@ -0,0 +1,49 @@
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Input;
+
+namespace MediaBrowser.UI.Controls
+{
+ /// <summary>
+ /// Extends Checkbox to provide focus on mouse over
+ /// </summary>
+ public class ExtendedCheckbox : CheckBox
+ {
+ private Point? _lastMouseMovePoint;
+
+ /// <summary>
+ /// Handles OnMouseMove to auto-select the item that's being moused over
+ /// </summary>
+ protected override void OnMouseMove(MouseEventArgs e)
+ {
+ base.OnMouseMove(e);
+
+ var window = this.GetWindow();
+
+ // If the cursor is currently hidden, don't bother reacting to it
+ if (Cursor == Cursors.None || window.Cursor == Cursors.None)
+ {
+ return;
+ }
+
+ // Store the last position for comparison purposes
+ // Even if the mouse is not moving this event will fire as elements are showing and hiding
+ var pos = e.GetPosition(window);
+
+ if (!_lastMouseMovePoint.HasValue)
+ {
+ _lastMouseMovePoint = pos;
+ return;
+ }
+
+ if (pos == _lastMouseMovePoint)
+ {
+ return;
+ }
+
+ _lastMouseMovePoint = pos;
+
+ Focus();
+ }
+ }
+}
diff --git a/MediaBrowser.UI.Controls/ExtendedListBox.cs b/MediaBrowser.UI.Controls/ExtendedListBox.cs
new file mode 100644
index 0000000000..fb6738939e
--- /dev/null
+++ b/MediaBrowser.UI.Controls/ExtendedListBox.cs
@@ -0,0 +1,260 @@
+using System;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Controls.Primitives;
+using System.Windows.Input;
+using System.Windows.Media;
+
+namespace MediaBrowser.UI.Controls
+{
+ /// <summary>
+ /// Extends the ListBox to provide auto-focus behavior when items are moused over
+ /// This also adds an ItemInvoked event that is fired when an item is clicked or invoked using the enter key
+ /// </summary>
+ public class ExtendedListBox : ListBox
+ {
+ /// <summary>
+ /// Fired when an item is clicked or invoked using the enter key
+ /// </summary>
+ public event EventHandler<ItemEventArgs<object>> ItemInvoked;
+
+ /// <summary>
+ /// Called when [item invoked].
+ /// </summary>
+ /// <param name="boundObject">The bound object.</param>
+ protected virtual void OnItemInvoked(object boundObject)
+ {
+ if (ItemInvoked != null)
+ {
+ ItemInvoked(this, new ItemEventArgs<object> { Argument = boundObject });
+ }
+ }
+
+ /// <summary>
+ /// The _auto focus
+ /// </summary>
+ private bool _autoFocus = true;
+ /// <summary>
+ /// Gets or sets a value indicating if the first list item should be auto-focused on load
+ /// </summary>
+ /// <value><c>true</c> if [auto focus]; otherwise, <c>false</c>.</value>
+ public bool AutoFocus
+ {
+ get { return _autoFocus; }
+ set
+ {
+ _autoFocus = value;
+ }
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="ExtendedListBox" /> class.
+ /// </summary>
+ public ExtendedListBox()
+ : base()
+ {
+ ItemContainerGenerator.StatusChanged += ItemContainerGeneratorStatusChanged;
+ }
+
+ /// <summary>
+ /// The mouse down object
+ /// </summary>
+ private object mouseDownObject;
+
+ /// <summary>
+ /// Invoked when an unhandled <see cref="E:System.Windows.Input.Mouse.PreviewMouseDown" /> attached routed event reaches an element in its route that is derived from this class. Implement this method to add class handling for this event.
+ /// </summary>
+ /// <param name="e">The <see cref="T:System.Windows.Input.MouseButtonEventArgs" /> that contains the event data. The event data reports that one or more mouse buttons were pressed.</param>
+ protected override void OnPreviewMouseDown(MouseButtonEventArgs e)
+ {
+ base.OnPreviewMouseDown(e);
+
+ // Get the item that the mouse down event occurred on
+ mouseDownObject = GetBoundListItemObject((DependencyObject)e.OriginalSource);
+ }
+
+ /// <summary>
+ /// Invoked when an unhandled <see cref="E:System.Windows.UIElement.MouseLeftButtonUp" /> routed event reaches an element in its route that is derived from this class. Implement this method to add class handling for this event.
+ /// </summary>
+ /// <param name="e">The <see cref="T:System.Windows.Input.MouseButtonEventArgs" /> that contains the event data. The event data reports that the left mouse button was released.</param>
+ protected override void OnMouseLeftButtonUp(MouseButtonEventArgs e)
+ {
+ base.OnMouseLeftButtonUp(e);
+
+ // If the mouse up event occurred on the same item as the mousedown event, then fire ItemInvoked
+ if (mouseDownObject != null)
+ {
+ var boundObject = GetBoundListItemObject((DependencyObject)e.OriginalSource);
+
+ if (mouseDownObject == boundObject)
+ {
+ mouseDownObject = null;
+ OnItemInvoked(boundObject);
+ }
+ }
+ }
+
+ /// <summary>
+ /// The key down object
+ /// </summary>
+ private object keyDownObject;
+
+ /// <summary>
+ /// Responds to the <see cref="E:System.Windows.UIElement.KeyDown" /> event.
+ /// </summary>
+ /// <param name="e">Provides data for <see cref="T:System.Windows.Input.KeyEventArgs" />.</param>
+ protected override void OnKeyDown(KeyEventArgs e)
+ {
+ if (e.Key == Key.Enter)
+ {
+ if (!e.IsRepeat)
+ {
+ // Get the item that the keydown event occurred on
+ keyDownObject = GetBoundListItemObject((DependencyObject)e.OriginalSource);
+ }
+
+ e.Handled = true;
+ }
+
+ base.OnKeyDown(e);
+ }
+
+ /// <summary>
+ /// Invoked when an unhandled <see cref="E:System.Windows.Input.Keyboard.KeyUp" /> attached event reaches an element in its route that is derived from this class. Implement this method to add class handling for this event.
+ /// </summary>
+ /// <param name="e">The <see cref="T:System.Windows.Input.KeyEventArgs" /> that contains the event data.</param>
+ protected override void OnKeyUp(KeyEventArgs e)
+ {
+ base.OnKeyUp(e);
+
+ // Fire ItemInvoked when enter is pressed on an item
+ if (e.Key == Key.Enter)
+ {
+ if (!e.IsRepeat)
+ {
+ // If the keyup event occurred on the same item as the keydown event, then fire ItemInvoked
+ if (keyDownObject != null)
+ {
+ var boundObject = GetBoundListItemObject((DependencyObject)e.OriginalSource);
+
+ if (keyDownObject == boundObject)
+ {
+ keyDownObject = null;
+ OnItemInvoked(boundObject);
+ }
+ }
+ }
+
+ e.Handled = true;
+ }
+ }
+
+ /// <summary>
+ /// The _last mouse move point
+ /// </summary>
+ private Point? _lastMouseMovePoint;
+
+ /// <summary>
+ /// Handles OnMouseMove to auto-select the item that's being moused over
+ /// </summary>
+ /// <param name="e">Provides data for <see cref="T:System.Windows.Input.MouseEventArgs" />.</param>
+ protected override void OnMouseMove(MouseEventArgs e)
+ {
+ base.OnMouseMove(e);
+
+ var window = this.GetWindow();
+
+ // If the cursor is currently hidden, don't bother reacting to it
+ if (Cursor == Cursors.None || window.Cursor == Cursors.None)
+ {
+ return;
+ }
+
+ // Store the last position for comparison purposes
+ // Even if the mouse is not moving this event will fire as elements are showing and hiding
+ var pos = e.GetPosition(window);
+
+ if (!_lastMouseMovePoint.HasValue)
+ {
+ _lastMouseMovePoint = pos;
+ return;
+ }
+
+ if (pos == _lastMouseMovePoint)
+ {
+ return;
+ }
+
+ _lastMouseMovePoint = pos;
+
+ var dep = (DependencyObject)e.OriginalSource;
+
+ while ((dep != null) && !(dep is ListBoxItem))
+ {
+ dep = VisualTreeHelper.GetParent(dep);
+ }
+
+ if (dep != null)
+ {
+ var listBoxItem = dep as ListBoxItem;
+
+ if (!listBoxItem.IsFocused)
+ {
+ listBoxItem.Focus();
+ }
+ }
+ }
+
+ /// <summary>
+ /// Gets the datacontext for a given ListBoxItem
+ /// </summary>
+ /// <param name="dep">The dep.</param>
+ /// <returns>System.Object.</returns>
+ private object GetBoundListItemObject(DependencyObject dep)
+ {
+ while ((dep != null) && !(dep is ListBoxItem))
+ {
+ dep = VisualTreeHelper.GetParent(dep);
+ }
+
+ if (dep == null)
+ {
+ return null;
+ }
+
+ return ItemContainerGenerator.ItemFromContainer(dep);
+ }
+
+ /// <summary>
+ /// Autofocuses the first list item when the list is loaded
+ /// </summary>
+ /// <param name="sender">The sender.</param>
+ /// <param name="e">The <see cref="EventArgs" /> instance containing the event data.</param>
+ void ItemContainerGeneratorStatusChanged(object sender, EventArgs e)
+ {
+ if (ItemContainerGenerator.Status == GeneratorStatus.ContainersGenerated && AutoFocus)
+ {
+ Dispatcher.InvokeAsync(OnContainersGenerated);
+ }
+ }
+
+ /// <summary>
+ /// Called when [containers generated].
+ /// </summary>
+ void OnContainersGenerated()
+ {
+ var index = 0;
+
+ if (index >= 0)
+ {
+ var item = ItemContainerGenerator.ContainerFromIndex(index) as ListBoxItem;
+
+ if (item != null)
+ {
+ item.Focus();
+ ItemContainerGenerator.StatusChanged -= ItemContainerGeneratorStatusChanged;
+ }
+ }
+ }
+ }
+}
diff --git a/MediaBrowser.UI.Controls/ExtendedRadioButton.cs b/MediaBrowser.UI.Controls/ExtendedRadioButton.cs
new file mode 100644
index 0000000000..82aad7f098
--- /dev/null
+++ b/MediaBrowser.UI.Controls/ExtendedRadioButton.cs
@@ -0,0 +1,49 @@
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Input;
+
+namespace MediaBrowser.UI.Controls
+{
+ /// <summary>
+ /// Extends RadioButton to provide focus on mouse over, and invoke on enter press
+ /// </summary>
+ public class ExtendedRadioButton : RadioButton
+ {
+ private Point? _lastMouseMovePoint;
+
+ /// <summary>
+ /// Handles OnMouseMove to auto-select the item that's being moused over
+ /// </summary>
+ protected override void OnMouseMove(MouseEventArgs e)
+ {
+ base.OnMouseMove(e);
+
+ var window = this.GetWindow();
+
+ // If the cursor is currently hidden, don't bother reacting to it
+ if (Cursor == Cursors.None || window.Cursor == Cursors.None)
+ {
+ return;
+ }
+
+ // Store the last position for comparison purposes
+ // Even if the mouse is not moving this event will fire as elements are showing and hiding
+ var pos = e.GetPosition(window);
+
+ if (!_lastMouseMovePoint.HasValue)
+ {
+ _lastMouseMovePoint = pos;
+ return;
+ }
+
+ if (pos == _lastMouseMovePoint)
+ {
+ return;
+ }
+
+ _lastMouseMovePoint = pos;
+
+ Focus();
+ }
+ }
+}
diff --git a/MediaBrowser.UI.Controls/ExtendedScrollViewer.cs b/MediaBrowser.UI.Controls/ExtendedScrollViewer.cs
new file mode 100644
index 0000000000..c1a6f1c478
--- /dev/null
+++ b/MediaBrowser.UI.Controls/ExtendedScrollViewer.cs
@@ -0,0 +1,41 @@
+using System.Windows.Controls;
+using System.Windows.Input;
+
+namespace MediaBrowser.UI.Controls
+{
+ /// <summary>
+ /// This subclass solves the problem of ScrollViewers eating KeyDown for all arrow keys
+ /// </summary>
+ public class ExtendedScrollViewer : ScrollViewer
+ {
+ protected override void OnKeyDown(KeyEventArgs e)
+ {
+ if (e.Handled || e.OriginalSource == this)
+ {
+ base.OnKeyDown(e);
+ return;
+ }
+
+ // Don't eat left/right if horizontal scrolling is disabled
+ if (e.Key == Key.Left || e.Key == Key.Right)
+ {
+ if (HorizontalScrollBarVisibility == ScrollBarVisibility.Disabled)
+ {
+ return;
+ }
+ }
+
+ // Don't eat up/down if vertical scrolling is disabled
+ if (e.Key == Key.Up || e.Key == Key.Down)
+ {
+ if (VerticalScrollBarVisibility == ScrollBarVisibility.Disabled)
+ {
+ return;
+ }
+ }
+
+ // Let the base class do it's thing
+ base.OnKeyDown(e);
+ }
+ }
+}
diff --git a/MediaBrowser.UI.Controls/ItemEventArgs.cs b/MediaBrowser.UI.Controls/ItemEventArgs.cs
new file mode 100644
index 0000000000..e0c24b2f59
--- /dev/null
+++ b/MediaBrowser.UI.Controls/ItemEventArgs.cs
@@ -0,0 +1,17 @@
+using System;
+
+namespace MediaBrowser.UI.Controls
+{
+ /// <summary>
+ /// Provides a generic EventArgs subclass that can hold any kind of object
+ /// </summary>
+ /// <typeparam name="T"></typeparam>
+ public class ItemEventArgs<T> : EventArgs
+ {
+ /// <summary>
+ /// Gets or sets the argument.
+ /// </summary>
+ /// <value>The argument.</value>
+ public T Argument { get; set; }
+ }
+}
diff --git a/MediaBrowser.UI.Controls/MediaBrowser.UI.Controls.csproj b/MediaBrowser.UI.Controls/MediaBrowser.UI.Controls.csproj
new file mode 100644
index 0000000000..c6eb064f3a
--- /dev/null
+++ b/MediaBrowser.UI.Controls/MediaBrowser.UI.Controls.csproj
@@ -0,0 +1,107 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
+ <PropertyGroup>
+ <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+ <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+ <ProjectGuid>{1ADFE460-FD95-46FA-8871-CCCB4B62E2E8}</ProjectGuid>
+ <OutputType>library</OutputType>
+ <AppDesignerFolder>Properties</AppDesignerFolder>
+ <RootNamespace>MediaBrowser.UI.Controls</RootNamespace>
+ <AssemblyName>MediaBrowser.UI.Controls</AssemblyName>
+ <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
+ <FileAlignment>512</FileAlignment>
+ <ProjectTypeGuids>{60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
+ <WarningLevel>4</WarningLevel>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+ <DebugSymbols>true</DebugSymbols>
+ <DebugType>full</DebugType>
+ <Optimize>false</Optimize>
+ <OutputPath>bin\Debug\</OutputPath>
+ <DefineConstants>DEBUG;TRACE</DefineConstants>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+ <DebugType>pdbonly</DebugType>
+ <Optimize>true</Optimize>
+ <OutputPath>bin\Release\</OutputPath>
+ <DefineConstants>TRACE</DefineConstants>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ </PropertyGroup>
+ <ItemGroup>
+ <Reference Include="Microsoft.Expression.Effects">
+ <HintPath>..\ThirdParty\Expression\Microsoft.Expression.Effects.dll</HintPath>
+ </Reference>
+ <Reference Include="Microsoft.Expression.Interactions">
+ <HintPath>..\ThirdParty\Expression\Microsoft.Expression.Interactions.dll</HintPath>
+ </Reference>
+ <Reference Include="System" />
+ <Reference Include="System.Data" />
+ <Reference Include="System.Xml" />
+ <Reference Include="Microsoft.CSharp" />
+ <Reference Include="System.Core" />
+ <Reference Include="System.Xml.Linq" />
+ <Reference Include="System.Data.DataSetExtensions" />
+ <Reference Include="System.Xaml">
+ <RequiredTargetFramework>4.0</RequiredTargetFramework>
+ </Reference>
+ <Reference Include="WindowsBase" />
+ <Reference Include="PresentationCore" />
+ <Reference Include="PresentationFramework" />
+ </ItemGroup>
+ <ItemGroup>
+ <Compile Include="BaseModalWindow.cs" />
+ <Compile Include="BaseUserControl.cs" />
+ <Compile Include="BaseWindow.cs" />
+ <Compile Include="ExtendedButton.cs" />
+ <Compile Include="ExtendedCheckbox.cs" />
+ <Compile Include="ExtendedListBox.cs" />
+ <Compile Include="ExtendedRadioButton.cs" />
+ <Compile Include="ExtendedScrollViewer.cs" />
+ <Compile Include="ItemEventArgs.cs" />
+ <Compile Include="Properties\AssemblyInfo.cs">
+ <SubType>Code</SubType>
+ </Compile>
+ <Compile Include="Properties\Resources.Designer.cs">
+ <AutoGen>True</AutoGen>
+ <DesignTime>True</DesignTime>
+ <DependentUpon>Resources.resx</DependentUpon>
+ </Compile>
+ <Compile Include="Properties\Settings.Designer.cs">
+ <AutoGen>True</AutoGen>
+ <DependentUpon>Settings.settings</DependentUpon>
+ <DesignTimeSharedInput>True</DesignTimeSharedInput>
+ </Compile>
+ <Compile Include="ScrollingPanel.cs" />
+ <Compile Include="TransitionControl.cs" />
+ <Compile Include="TransitionFrame.cs" />
+ <Compile Include="TreeHelper.cs" />
+ <Compile Include="VirtualizingWrapPanel.cs" />
+ <EmbeddedResource Include="Properties\Resources.resx">
+ <Generator>ResXFileCodeGenerator</Generator>
+ <LastGenOutput>Resources.Designer.cs</LastGenOutput>
+ </EmbeddedResource>
+ <None Include="Properties\Settings.settings">
+ <Generator>SettingsSingleFileGenerator</Generator>
+ <LastGenOutput>Settings.Designer.cs</LastGenOutput>
+ </None>
+ <AppDesigner Include="Properties\" />
+ </ItemGroup>
+ <ItemGroup>
+ <Page Include="Themes\Generic.xaml">
+ <Generator>MSBuild:Compile</Generator>
+ <SubType>Designer</SubType>
+ </Page>
+ </ItemGroup>
+ <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+ <!-- To modify your build process, add your task inside one of the targets below and uncomment it.
+ Other similar extension points exist, see Microsoft.Common.targets.
+ <Target Name="BeforeBuild">
+ </Target>
+ <Target Name="AfterBuild">
+ </Target>
+ -->
+</Project> \ No newline at end of file
diff --git a/MediaBrowser.UI.Controls/Properties/AssemblyInfo.cs b/MediaBrowser.UI.Controls/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000000..0d360004c1
--- /dev/null
+++ b/MediaBrowser.UI.Controls/Properties/AssemblyInfo.cs
@@ -0,0 +1,55 @@
+using System.Reflection;
+using System.Resources;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Windows;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("MediaBrowser.UI.Controls")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("MediaBrowser.UI.Controls")]
+[assembly: AssemblyCopyright("Copyright © 2013")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+//In order to begin building localizable applications, set
+//<UICulture>CultureYouAreCodingWith</UICulture> in your .csproj file
+//inside a <PropertyGroup>. For example, if you are using US english
+//in your source files, set the <UICulture> to en-US. Then uncomment
+//the NeutralResourceLanguage attribute below. Update the "en-US" in
+//the line below to match the UICulture setting in the project file.
+
+//[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)]
+
+
+[assembly:ThemeInfo(
+ ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
+ //(used if a resource is not found in the page,
+ // or application resource dictionaries)
+ ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
+ //(used if a resource is not found in the page,
+ // app, or any theme specific resource dictionaries)
+)]
+
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/MediaBrowser.UI.Controls/Properties/Resources.Designer.cs b/MediaBrowser.UI.Controls/Properties/Resources.Designer.cs
new file mode 100644
index 0000000000..b03f1d59bd
--- /dev/null
+++ b/MediaBrowser.UI.Controls/Properties/Resources.Designer.cs
@@ -0,0 +1,62 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+// This code was generated by a tool.
+// Runtime Version:4.0.30319.18033
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+namespace MediaBrowser.UI.Controls.Properties {
+
+
+ /// <summary>
+ /// A strongly-typed resource class, for looking up localized strings, etc.
+ /// </summary>
+ // This class was auto-generated by the StronglyTypedResourceBuilder
+ // class via a tool like ResGen or Visual Studio.
+ // To add or remove a member, edit your .ResX file then rerun ResGen
+ // with the /str option, or rebuild your VS project.
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+ internal class Resources {
+
+ private static global::System.Resources.ResourceManager resourceMan;
+
+ private static global::System.Globalization.CultureInfo resourceCulture;
+
+ [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+ internal Resources() {
+ }
+
+ /// <summary>
+ /// Returns the cached ResourceManager instance used by this class.
+ /// </summary>
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Resources.ResourceManager ResourceManager {
+ get {
+ if ((resourceMan == null)) {
+ global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("MediaBrowser.UI.Controls.Properties.Resources", typeof(Resources).Assembly);
+ resourceMan = temp;
+ }
+ return resourceMan;
+ }
+ }
+
+ /// <summary>
+ /// Overrides the current thread's CurrentUICulture property for all
+ /// resource lookups using this strongly typed resource class.
+ /// </summary>
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Globalization.CultureInfo Culture {
+ get {
+ return resourceCulture;
+ }
+ set {
+ resourceCulture = value;
+ }
+ }
+ }
+}
diff --git a/MediaBrowser.UI.Controls/Properties/Resources.resx b/MediaBrowser.UI.Controls/Properties/Resources.resx
new file mode 100644
index 0000000000..af7dbebbac
--- /dev/null
+++ b/MediaBrowser.UI.Controls/Properties/Resources.resx
@@ -0,0 +1,117 @@
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+ <!--
+ Microsoft ResX Schema
+
+ Version 2.0
+
+ The primary goals of this format is to allow a simple XML format
+ that is mostly human readable. The generation and parsing of the
+ various data types are done through the TypeConverter classes
+ associated with the data types.
+
+ Example:
+
+ ... ado.net/XML headers & schema ...
+ <resheader name="resmimetype">text/microsoft-resx</resheader>
+ <resheader name="version">2.0</resheader>
+ <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+ <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+ <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+ <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+ <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+ <value>[base64 mime encoded serialized .NET Framework object]</value>
+ </data>
+ <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+ <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+ <comment>This is a comment</comment>
+ </data>
+
+ There are any number of "resheader" rows that contain simple
+ name/value pairs.
+
+ Each data row contains a name, and value. The row also contains a
+ type or mimetype. Type corresponds to a .NET class that support
+ text/value conversion through the TypeConverter architecture.
+ Classes that don't support this are serialized and stored with the
+ mimetype set.
+
+ The mimetype is used for serialized objects, and tells the
+ ResXResourceReader how to depersist the object. This is currently not
+ extensible. For a given mimetype the value must be set accordingly:
+
+ Note - application/x-microsoft.net.object.binary.base64 is the format
+ that the ResXResourceWriter will generate, however the reader can
+ read any of the formats listed below.
+
+ mimetype: application/x-microsoft.net.object.binary.base64
+ value : The object must be serialized with
+ : System.Serialization.Formatters.Binary.BinaryFormatter
+ : and then encoded with base64 encoding.
+
+ mimetype: application/x-microsoft.net.object.soap.base64
+ value : The object must be serialized with
+ : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+ : and then encoded with base64 encoding.
+
+ mimetype: application/x-microsoft.net.object.bytearray.base64
+ value : The object must be serialized into a byte array
+ : using a System.ComponentModel.TypeConverter
+ : and then encoded with base64 encoding.
+ -->
+ <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+ <xsd:element name="root" msdata:IsDataSet="true">
+ <xsd:complexType>
+ <xsd:choice maxOccurs="unbounded">
+ <xsd:element name="metadata">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" />
+ <xsd:attribute name="type" type="xsd:string" />
+ <xsd:attribute name="mimetype" type="xsd:string" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="assembly">
+ <xsd:complexType>
+ <xsd:attribute name="alias" type="xsd:string" />
+ <xsd:attribute name="name" type="xsd:string" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="data">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+ <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" msdata:Ordinal="1" />
+ <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+ <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="resheader">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required" />
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:choice>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:schema>
+ <resheader name="resmimetype">
+ <value>text/microsoft-resx</value>
+ </resheader>
+ <resheader name="version">
+ <value>2.0</value>
+ </resheader>
+ <resheader name="reader">
+ <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ </resheader>
+ <resheader name="writer">
+ <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ </resheader>
+</root> \ No newline at end of file
diff --git a/MediaBrowser.UI.Controls/Properties/Settings.Designer.cs b/MediaBrowser.UI.Controls/Properties/Settings.Designer.cs
new file mode 100644
index 0000000000..d256289c13
--- /dev/null
+++ b/MediaBrowser.UI.Controls/Properties/Settings.Designer.cs
@@ -0,0 +1,30 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+// This code was generated by a tool.
+// Runtime Version:4.0.30319.18033
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+namespace MediaBrowser.UI.Controls.Properties
+{
+
+
+ [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")]
+ internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase
+ {
+
+ private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
+
+ public static Settings Default
+ {
+ get
+ {
+ return defaultInstance;
+ }
+ }
+ }
+}
diff --git a/MediaBrowser.UI.Controls/Properties/Settings.settings b/MediaBrowser.UI.Controls/Properties/Settings.settings
new file mode 100644
index 0000000000..033d7a5e9e
--- /dev/null
+++ b/MediaBrowser.UI.Controls/Properties/Settings.settings
@@ -0,0 +1,7 @@
+<?xml version='1.0' encoding='utf-8'?>
+<SettingsFile xmlns="uri:settings" CurrentProfile="(Default)">
+ <Profiles>
+ <Profile Name="(Default)" />
+ </Profiles>
+ <Settings />
+</SettingsFile> \ No newline at end of file
diff --git a/MediaBrowser.UI.Controls/ScrollingPanel.cs b/MediaBrowser.UI.Controls/ScrollingPanel.cs
new file mode 100644
index 0000000000..636661f544
--- /dev/null
+++ b/MediaBrowser.UI.Controls/ScrollingPanel.cs
@@ -0,0 +1,404 @@
+using System;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Controls.Primitives;
+using System.Windows.Media;
+using System.Windows.Media.Animation;
+
+namespace MediaBrowser.UI.Controls
+{
+ /// <summary>
+ /// This started from:
+ /// http://www.switchonthecode.com/tutorials/wpf-tutorial-implementing-iscrollinfo
+ /// Then, after implementing this, content was being displayed in stack panel like manner.
+ /// I then reviewed the source code of ScrollContentPresenter and updated MeasureOverride and ArrangeOverride to match.
+ /// </summary>
+ public class ScrollingPanel : Grid, IScrollInfo
+ {
+ /// <summary>
+ /// The infinite size
+ /// </summary>
+ private static Size InfiniteSize = new Size(double.PositiveInfinity, double.PositiveInfinity);
+ /// <summary>
+ /// The line size
+ /// </summary>
+ private const double LineSize = 16;
+ /// <summary>
+ /// The wheel size
+ /// </summary>
+ private const double WheelSize = 3 * LineSize;
+
+ /// <summary>
+ /// The _ offset
+ /// </summary>
+ private Vector _Offset;
+ /// <summary>
+ /// The _ extent
+ /// </summary>
+ private Size _Extent;
+ /// <summary>
+ /// The _ viewport
+ /// </summary>
+ private Size _Viewport;
+
+ /// <summary>
+ /// The _ animation length
+ /// </summary>
+ private TimeSpan _AnimationLength = TimeSpan.FromMilliseconds(125);
+
+ /// <summary>
+ /// When overridden in a derived class, measures the size in layout required for child elements and determines a size for the <see cref="T:System.Windows.FrameworkElement" />-derived class.
+ /// </summary>
+ /// <param name="availableSize">The available size that this element can give to child elements. Infinity can be specified as a value to indicate that the element will size to whatever content is available.</param>
+ /// <returns>The size that this element determines it needs during layout, based on its calculations of child element sizes.</returns>
+ protected override Size MeasureOverride(Size availableSize)
+ {
+ if (Children == null || Children.Count == 0)
+ {
+ return availableSize;
+ }
+
+ var constraint2 = availableSize;
+ if (CanHorizontallyScroll)
+ {
+ constraint2.Width = double.PositiveInfinity;
+ }
+ if (CanVerticallyScroll)
+ {
+ constraint2.Height = double.PositiveInfinity;
+ }
+
+ var uiElement = Children[0];
+
+ uiElement.Measure(constraint2);
+ var size = uiElement.DesiredSize;
+
+ VerifyScrollData(availableSize, size);
+
+ size.Width = Math.Min(availableSize.Width, size.Width);
+ size.Height = Math.Min(availableSize.Height, size.Height);
+
+ return size;
+ }
+
+ /// <summary>
+ /// Arranges the content of a <see cref="T:System.Windows.Controls.Grid" /> element.
+ /// </summary>
+ /// <param name="arrangeSize">Specifies the size this <see cref="T:System.Windows.Controls.Grid" /> element should use to arrange its child elements.</param>
+ /// <returns><see cref="T:System.Windows.Size" /> that represents the arranged size of this Grid element and its children.</returns>
+ protected override Size ArrangeOverride(Size arrangeSize)
+ {
+ this.VerifyScrollData(arrangeSize, _Extent);
+
+ if (this.Children == null || this.Children.Count == 0)
+ {
+ return arrangeSize;
+ }
+
+ TranslateTransform trans = null;
+
+ var uiElement = Children[0];
+
+ var finalRect = new Rect(uiElement.DesiredSize);
+
+ // ScrollContentPresenter sets these to 0 - current offset
+ // We need to set it to zero in order to make the animation work
+ finalRect.X = 0;
+ finalRect.Y = 0;
+
+ finalRect.Width = Math.Max(finalRect.Width, arrangeSize.Width);
+ finalRect.Height = Math.Max(finalRect.Height, arrangeSize.Height);
+
+ trans = uiElement.RenderTransform as TranslateTransform;
+
+ if (trans == null)
+ {
+ uiElement.RenderTransformOrigin = new Point(0, 0);
+ trans = new TranslateTransform();
+ uiElement.RenderTransform = trans;
+ }
+
+ uiElement.Arrange(finalRect);
+
+ trans.BeginAnimation(TranslateTransform.XProperty,
+ GetAnimation(0 - HorizontalOffset),
+ HandoffBehavior.Compose);
+ trans.BeginAnimation(TranslateTransform.YProperty,
+ GetAnimation(0 - VerticalOffset),
+ HandoffBehavior.Compose);
+
+ return arrangeSize;
+ }
+
+ /// <summary>
+ /// Gets the animation.
+ /// </summary>
+ /// <param name="toValue">To value.</param>
+ /// <returns>DoubleAnimation.</returns>
+ private DoubleAnimation GetAnimation(double toValue)
+ {
+ var animation = new DoubleAnimation(toValue, _AnimationLength);
+
+ animation.EasingFunction = new ExponentialEase { EasingMode = EasingMode.EaseInOut };
+
+ return animation;
+ }
+
+ #region Movement Methods
+ /// <summary>
+ /// Scrolls down within content by one logical unit.
+ /// </summary>
+ public void LineDown()
+ { SetVerticalOffset(VerticalOffset + LineSize); }
+
+ /// <summary>
+ /// Scrolls up within content by one logical unit.
+ /// </summary>
+ public void LineUp()
+ { SetVerticalOffset(VerticalOffset - LineSize); }
+
+ /// <summary>
+ /// Scrolls left within content by one logical unit.
+ /// </summary>
+ public void LineLeft()
+ { SetHorizontalOffset(HorizontalOffset - LineSize); }
+
+ /// <summary>
+ /// Scrolls right within content by one logical unit.
+ /// </summary>
+ public void LineRight()
+ { SetHorizontalOffset(HorizontalOffset + LineSize); }
+
+ /// <summary>
+ /// Scrolls down within content after a user clicks the wheel button on a mouse.
+ /// </summary>
+ public void MouseWheelDown()
+ { SetVerticalOffset(VerticalOffset + WheelSize); }
+
+ /// <summary>
+ /// Scrolls up within content after a user clicks the wheel button on a mouse.
+ /// </summary>
+ public void MouseWheelUp()
+ { SetVerticalOffset(VerticalOffset - WheelSize); }
+
+ /// <summary>
+ /// Scrolls left within content after a user clicks the wheel button on a mouse.
+ /// </summary>
+ public void MouseWheelLeft()
+ { SetHorizontalOffset(HorizontalOffset - WheelSize); }
+
+ /// <summary>
+ /// Scrolls right within content after a user clicks the wheel button on a mouse.
+ /// </summary>
+ public void MouseWheelRight()
+ { SetHorizontalOffset(HorizontalOffset + WheelSize); }
+
+ /// <summary>
+ /// Scrolls down within content by one page.
+ /// </summary>
+ public void PageDown()
+ { SetVerticalOffset(VerticalOffset + ViewportHeight); }
+
+ /// <summary>
+ /// Scrolls up within content by one page.
+ /// </summary>
+ public void PageUp()
+ { SetVerticalOffset(VerticalOffset - ViewportHeight); }
+
+ /// <summary>
+ /// Scrolls left within content by one page.
+ /// </summary>
+ public void PageLeft()
+ { SetHorizontalOffset(HorizontalOffset - ViewportWidth); }
+
+ /// <summary>
+ /// Scrolls right within content by one page.
+ /// </summary>
+ public void PageRight()
+ { SetHorizontalOffset(HorizontalOffset + ViewportWidth); }
+ #endregion
+
+ /// <summary>
+ /// Gets or sets a <see cref="T:System.Windows.Controls.ScrollViewer" /> element that controls scrolling behavior.
+ /// </summary>
+ /// <value>The scroll owner.</value>
+ /// <returns>A <see cref="T:System.Windows.Controls.ScrollViewer" /> element that controls scrolling behavior. This property has no default value.</returns>
+ public ScrollViewer ScrollOwner { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value that indicates whether scrolling on the horizontal axis is possible.
+ /// </summary>
+ /// <value><c>true</c> if this instance can horizontally scroll; otherwise, <c>false</c>.</value>
+ /// <returns>true if scrolling is possible; otherwise, false. This property has no default value.</returns>
+ public bool CanHorizontallyScroll { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value that indicates whether scrolling on the vertical axis is possible.
+ /// </summary>
+ /// <value><c>true</c> if this instance can vertically scroll; otherwise, <c>false</c>.</value>
+ /// <returns>true if scrolling is possible; otherwise, false. This property has no default value.</returns>
+ public bool CanVerticallyScroll { get; set; }
+
+ /// <summary>
+ /// Gets the vertical size of the extent.
+ /// </summary>
+ /// <value>The height of the extent.</value>
+ /// <returns>A <see cref="T:System.Double" /> that represents, in device independent pixels, the vertical size of the extent.This property has no default value.</returns>
+ public double ExtentHeight
+ { get { return _Extent.Height; } }
+
+ /// <summary>
+ /// Gets the horizontal size of the extent.
+ /// </summary>
+ /// <value>The width of the extent.</value>
+ /// <returns>A <see cref="T:System.Double" /> that represents, in device independent pixels, the horizontal size of the extent. This property has no default value.</returns>
+ public double ExtentWidth
+ { get { return _Extent.Width; } }
+
+ /// <summary>
+ /// Gets the horizontal offset of the scrolled content.
+ /// </summary>
+ /// <value>The horizontal offset.</value>
+ /// <returns>A <see cref="T:System.Double" /> that represents, in device independent pixels, the horizontal offset. This property has no default value.</returns>
+ public double HorizontalOffset
+ { get { return _Offset.X; } }
+
+ /// <summary>
+ /// Gets the vertical offset of the scrolled content.
+ /// </summary>
+ /// <value>The vertical offset.</value>
+ /// <returns>A <see cref="T:System.Double" /> that represents, in device independent pixels, the vertical offset of the scrolled content. Valid values are between zero and the <see cref="P:System.Windows.Controls.Primitives.IScrollInfo.ExtentHeight" /> minus the <see cref="P:System.Windows.Controls.Primitives.IScrollInfo.ViewportHeight" />. This property has no default value.</returns>
+ public double VerticalOffset
+ { get { return _Offset.Y; } }
+
+ /// <summary>
+ /// Gets the vertical size of the viewport for this content.
+ /// </summary>
+ /// <value>The height of the viewport.</value>
+ /// <returns>A <see cref="T:System.Double" /> that represents, in device independent pixels, the vertical size of the viewport for this content. This property has no default value.</returns>
+ public double ViewportHeight
+ { get { return _Viewport.Height; } }
+
+ /// <summary>
+ /// Gets the horizontal size of the viewport for this content.
+ /// </summary>
+ /// <value>The width of the viewport.</value>
+ /// <returns>A <see cref="T:System.Double" /> that represents, in device independent pixels, the horizontal size of the viewport for this content. This property has no default value.</returns>
+ public double ViewportWidth
+ { get { return _Viewport.Width; } }
+
+ /// <summary>
+ /// Forces content to scroll until the coordinate space of a <see cref="T:System.Windows.Media.Visual" /> object is visible.
+ /// </summary>
+ /// <param name="visual">A <see cref="T:System.Windows.Media.Visual" /> that becomes visible.</param>
+ /// <param name="rectangle">A bounding rectangle that identifies the coordinate space to make visible.</param>
+ /// <returns>A <see cref="T:System.Windows.Rect" /> that is visible.</returns>
+ public Rect MakeVisible(Visual visual, Rect rectangle)
+ {
+ if (rectangle.IsEmpty || visual == null
+ || visual == this || !base.IsAncestorOf(visual))
+ { return Rect.Empty; }
+
+ rectangle = visual.TransformToAncestor(this).TransformBounds(rectangle);
+
+ //rectangle.Inflate(50, 50);
+ rectangle.Scale(1.2, 1.2);
+
+ Rect viewRect = new Rect(HorizontalOffset,
+ VerticalOffset, ViewportWidth, ViewportHeight);
+ rectangle.X += viewRect.X;
+ rectangle.Y += viewRect.Y;
+
+ viewRect.X = CalculateNewScrollOffset(viewRect.Left,
+ viewRect.Right, rectangle.Left, rectangle.Right);
+ viewRect.Y = CalculateNewScrollOffset(viewRect.Top,
+ viewRect.Bottom, rectangle.Top, rectangle.Bottom);
+ SetHorizontalOffset(viewRect.X);
+ SetVerticalOffset(viewRect.Y);
+ rectangle.Intersect(viewRect);
+ rectangle.X -= viewRect.X;
+ rectangle.Y -= viewRect.Y;
+
+ return rectangle;
+ }
+
+ /// <summary>
+ /// Calculates the new scroll offset.
+ /// </summary>
+ /// <param name="topView">The top view.</param>
+ /// <param name="bottomView">The bottom view.</param>
+ /// <param name="topChild">The top child.</param>
+ /// <param name="bottomChild">The bottom child.</param>
+ /// <returns>System.Double.</returns>
+ private static double CalculateNewScrollOffset(double topView,
+ double bottomView, double topChild, double bottomChild)
+ {
+ bool offBottom = topChild < topView && bottomChild < bottomView;
+ bool offTop = bottomChild > bottomView && topChild > topView;
+ bool tooLarge = (bottomChild - topChild) > (bottomView - topView);
+
+ if (!offBottom && !offTop)
+ { return topView; } //Don't do anything, already in view
+
+ if ((offBottom && !tooLarge) || (offTop && tooLarge))
+ { return topChild; }
+
+ return (bottomChild - (bottomView - topView));
+ }
+
+ /// <summary>
+ /// Verifies the scroll data.
+ /// </summary>
+ /// <param name="viewport">The viewport.</param>
+ /// <param name="extent">The extent.</param>
+ protected void VerifyScrollData(Size viewport, Size extent)
+ {
+ if (double.IsInfinity(viewport.Width))
+ { viewport.Width = extent.Width; }
+
+ if (double.IsInfinity(viewport.Height))
+ { viewport.Height = extent.Height; }
+
+ _Extent = extent;
+ _Viewport = viewport;
+
+ _Offset.X = Math.Max(0,
+ Math.Min(_Offset.X, ExtentWidth - ViewportWidth));
+ _Offset.Y = Math.Max(0,
+ Math.Min(_Offset.Y, ExtentHeight - ViewportHeight));
+
+ if (ScrollOwner != null)
+ { ScrollOwner.InvalidateScrollInfo(); }
+ }
+
+ /// <summary>
+ /// Sets the amount of horizontal offset.
+ /// </summary>
+ /// <param name="offset">The degree to which content is horizontally offset from the containing viewport.</param>
+ public void SetHorizontalOffset(double offset)
+ {
+ offset = Math.Max(0,
+ Math.Min(offset, ExtentWidth - ViewportWidth));
+ if (!offset.Equals(_Offset.X))
+ {
+ _Offset.X = offset;
+ InvalidateArrange();
+ }
+ }
+
+ /// <summary>
+ /// Sets the amount of vertical offset.
+ /// </summary>
+ /// <param name="offset">The degree to which content is vertically offset from the containing viewport.</param>
+ public void SetVerticalOffset(double offset)
+ {
+ offset = Math.Max(0,
+ Math.Min(offset, ExtentHeight - ViewportHeight));
+ if (!offset.Equals(_Offset.Y))
+ {
+ _Offset.Y = offset;
+ InvalidateArrange();
+ }
+ }
+ }
+}
diff --git a/MediaBrowser.UI.Controls/Themes/Generic.xaml b/MediaBrowser.UI.Controls/Themes/Generic.xaml
new file mode 100644
index 0000000000..44e50a5596
--- /dev/null
+++ b/MediaBrowser.UI.Controls/Themes/Generic.xaml
@@ -0,0 +1,17 @@
+<ResourceDictionary
+ xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+ xmlns:local="clr-namespace:MediaBrowser.UI.Controls">
+
+ <Style TargetType="local:TransitionControl">
+ <Setter Property="Template">
+ <Setter.Value>
+ <ControlTemplate TargetType="local:TransitionControl">
+ <Grid>
+ <ContentPresenter x:Name="ContentPresenter" />
+ </Grid>
+ </ControlTemplate>
+ </Setter.Value>
+ </Setter>
+ </Style>
+</ResourceDictionary>
diff --git a/MediaBrowser.UI.Controls/TransitionControl.cs b/MediaBrowser.UI.Controls/TransitionControl.cs
new file mode 100644
index 0000000000..d1e5ccf0ae
--- /dev/null
+++ b/MediaBrowser.UI.Controls/TransitionControl.cs
@@ -0,0 +1,147 @@
+using Microsoft.Expression.Media.Effects;
+using System.ComponentModel;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Media;
+using System.Windows.Media.Animation;
+
+namespace MediaBrowser.UI.Controls
+{
+ /// <summary>
+ /// http://victorcher.blogspot.com/2012/02/wpf-transactions.html
+ /// </summary>
+ public class TransitionControl : ContentControl
+ {
+ /// <summary>
+ /// The _content presenter
+ /// </summary>
+ private ContentPresenter _contentPresenter;
+
+ /// <summary>
+ /// Initializes static members of the <see cref="TransitionControl" /> class.
+ /// </summary>
+ static TransitionControl()
+ {
+ DefaultStyleKeyProperty.OverrideMetadata(
+ typeof(TransitionControl), new FrameworkPropertyMetadata(typeof(TransitionControl)));
+
+ ContentProperty.OverrideMetadata(
+ typeof(TransitionControl), new FrameworkPropertyMetadata(OnContentPropertyChanged));
+ }
+
+ /// <summary>
+ /// When overridden in a derived class, is invoked whenever application code or internal processes call <see cref="M:System.Windows.FrameworkElement.ApplyTemplate" />.
+ /// </summary>
+ public override void OnApplyTemplate()
+ {
+ base.OnApplyTemplate();
+ _contentPresenter = (ContentPresenter)Template.FindName("ContentPresenter", this);
+ }
+
+ #region DP TransitionType
+
+ /// <summary>
+ /// Gets or sets the type of the transition.
+ /// </summary>
+ /// <value>The type of the transition.</value>
+ public TransitionEffect TransitionType
+ {
+ get { return (TransitionEffect)GetValue(TransitionTypeProperty); }
+ set { SetValue(TransitionTypeProperty, value); }
+ }
+
+ // Using a DependencyProperty as the backing store for TransitionType. This enables animation, styling, binding, etc...
+ /// <summary>
+ /// The transition type property
+ /// </summary>
+ public static readonly DependencyProperty TransitionTypeProperty =
+ DependencyProperty.Register("TransitionType", typeof(TransitionEffect), typeof(TransitionControl),
+ new UIPropertyMetadata(new BlindsTransitionEffect()));
+
+ #endregion DP TransitionType
+
+ #region DP Transition Animation
+
+ /// <summary>
+ /// Gets or sets the transition animation.
+ /// </summary>
+ /// <value>The transition animation.</value>
+ public DoubleAnimation TransitionAnimation
+ {
+ get { return (DoubleAnimation)GetValue(TransitionAnimationProperty); }
+ set { SetValue(TransitionAnimationProperty, value); }
+ }
+
+ // Using a DependencyProperty as the backing store for TransitionAnimation. This enables animation, styling, binding, etc...
+ /// <summary>
+ /// The transition animation property
+ /// </summary>
+ public static readonly DependencyProperty TransitionAnimationProperty =
+ DependencyProperty.Register("TransitionAnimation", typeof(DoubleAnimation), typeof(TransitionControl), new UIPropertyMetadata(null));
+
+ #endregion DP Transition Animation
+
+ /// <summary>
+ /// Called when [content property changed].
+ /// </summary>
+ /// <param name="dp">The dp.</param>
+ /// <param name="args">The <see cref="DependencyPropertyChangedEventArgs" /> instance containing the event data.</param>
+ private static void OnContentPropertyChanged(DependencyObject dp, DependencyPropertyChangedEventArgs args)
+ {
+ var oldContent = args.OldValue;
+ var newContent = args.NewValue;
+
+ var transitionControl = (TransitionControl)dp;
+
+ if (DesignerProperties.GetIsInDesignMode(transitionControl))
+ return;
+
+ if (oldContent != null && newContent != null && transitionControl.IsVisible)
+ {
+ transitionControl.AnimateContent(oldContent, newContent);
+ }
+ else if (newContent != null)
+ {
+ transitionControl.Content = newContent;
+ }
+ }
+
+ /// <summary>
+ /// Animates the content.
+ /// </summary>
+ /// <param name="oldContent">The old content.</param>
+ /// <param name="newContent">The new content.</param>
+ private void AnimateContent(object oldContent, object newContent)
+ {
+ FrameworkElement oldContentVisual;
+
+ try
+ {
+ oldContentVisual = VisualTreeHelper.GetChild(_contentPresenter, 0) as FrameworkElement;
+ }
+ catch
+ {
+ return;
+ }
+
+ var transitionEffect = TransitionType;
+
+ if (transitionEffect == null)
+ {
+ _contentPresenter.Content = newContent;
+ return;
+ }
+
+ var da = TransitionAnimation;
+ da.From = 0;
+ da.To = 1;
+ da.FillBehavior = FillBehavior.HoldEnd;
+
+ transitionEffect.OldImage = new VisualBrush(oldContentVisual);
+ transitionEffect.BeginAnimation(TransitionEffect.ProgressProperty, da);
+
+ _contentPresenter.Effect = transitionEffect;
+ _contentPresenter.Content = newContent;
+ }
+ }
+} \ No newline at end of file
diff --git a/MediaBrowser.UI.Controls/TransitionFrame.cs b/MediaBrowser.UI.Controls/TransitionFrame.cs
new file mode 100644
index 0000000000..e6f8325cc8
--- /dev/null
+++ b/MediaBrowser.UI.Controls/TransitionFrame.cs
@@ -0,0 +1,194 @@
+using Microsoft.Expression.Media.Effects;
+using System;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Media;
+using System.Windows.Media.Animation;
+
+namespace MediaBrowser.UI.Controls
+{
+ /// <summary>
+ /// Class TransitionFrame
+ /// </summary>
+ public class TransitionFrame : Frame
+ {
+ /// <summary>
+ /// The _content presenter
+ /// </summary>
+ private ContentPresenter _contentPresenter = null;
+
+ #region DP TransitionType
+
+ /// <summary>
+ /// Gets or sets the type of the transition.
+ /// </summary>
+ /// <value>The type of the transition.</value>
+ public TransitionEffect TransitionType
+ {
+ get { return (TransitionEffect)GetValue(TransitionTypeProperty); }
+ set { SetValue(TransitionTypeProperty, value); }
+ }
+
+ // Using a DependencyProperty as the backing store for TransitionType. This enables animation, styling, binding, etc...
+ /// <summary>
+ /// The transition type property
+ /// </summary>
+ public static readonly DependencyProperty TransitionTypeProperty =
+ DependencyProperty.Register("TransitionType", typeof(TransitionEffect), typeof(TransitionFrame),
+ new UIPropertyMetadata(new BlindsTransitionEffect()));
+
+ #endregion DP TransitionType
+
+ #region DP Transition Animation
+
+ /// <summary>
+ /// Gets or sets the transition animation.
+ /// </summary>
+ /// <value>The transition animation.</value>
+ public DoubleAnimation TransitionAnimation
+ {
+ get { return (DoubleAnimation)GetValue(TransitionAnimationProperty); }
+ set { SetValue(TransitionAnimationProperty, value); }
+ }
+
+ // Using a DependencyProperty as the backing store for TransitionAnimation. This enables animation, styling, binding, etc...
+ /// <summary>
+ /// The transition animation property
+ /// </summary>
+ public static readonly DependencyProperty TransitionAnimationProperty =
+ DependencyProperty.Register("TransitionAnimation", typeof(DoubleAnimation), typeof(TransitionFrame), new UIPropertyMetadata(null));
+
+ #endregion DP Transition Animation
+
+ /// <summary>
+ /// Called when the template generation for the visual tree is created.
+ /// </summary>
+ public override void OnApplyTemplate()
+ {
+ // get a reference to the frame's content presenter
+ // this is the element we will fade in and out
+ _contentPresenter = GetTemplateChild("PART_FrameCP") as ContentPresenter;
+ base.OnApplyTemplate();
+ }
+
+ /// <summary>
+ /// Animates the content.
+ /// </summary>
+ /// <param name="navigationAction">The navigation action.</param>
+ /// <param name="checkContent">if set to <c>true</c> [check content].</param>
+ /// <param name="isBack">if set to <c>true</c> [is back].</param>
+ private void AnimateContent(Action navigationAction, bool checkContent = true, bool isBack = false)
+ {
+ if (TransitionType == null || (checkContent && Content == null))
+ {
+ CommandBindings.Clear();
+ navigationAction();
+ CommandBindings.Clear();
+ return;
+ }
+
+ var oldContentVisual = this as FrameworkElement;
+
+ _contentPresenter.IsHitTestVisible = false;
+
+ var da = TransitionAnimation.Clone();
+ da.From = 0;
+ da.To = 1;
+ da.FillBehavior = FillBehavior.HoldEnd;
+
+ var transitionEffect = TransitionType.Clone() as TransitionEffect;
+
+ if (isBack)
+ {
+ ReverseDirection(transitionEffect);
+ }
+
+ transitionEffect.OldImage = new VisualBrush(oldContentVisual);
+ transitionEffect.BeginAnimation(TransitionEffect.ProgressProperty, da);
+
+ _contentPresenter.Effect = transitionEffect;
+ _contentPresenter.IsHitTestVisible = true;
+
+ // Remove base class bindings to remote buttons
+ CommandBindings.Clear();
+
+ navigationAction();
+
+ CommandBindings.Clear();
+ }
+
+ /// <summary>
+ /// Navigates the with transition.
+ /// </summary>
+ /// <param name="page">The page.</param>
+ public void NavigateWithTransition(Page page)
+ {
+ AnimateContent(() => Navigate(page));
+ }
+
+ /// <summary>
+ /// Navigates the with transition.
+ /// </summary>
+ /// <param name="page">The page.</param>
+ public void NavigateWithTransition(Uri page)
+ {
+ AnimateContent(() => Navigate(page));
+ }
+
+ /// <summary>
+ /// Goes the back with transition.
+ /// </summary>
+ public void GoBackWithTransition()
+ {
+ if (CanGoBack)
+ {
+ AnimateContent(GoBack, false, true);
+ }
+ }
+
+ /// <summary>
+ /// Goes the forward with transition.
+ /// </summary>
+ public void GoForwardWithTransition()
+ {
+ if (CanGoForward)
+ {
+ AnimateContent(GoForward, false);
+ }
+ }
+
+ /// <summary>
+ /// Reverses the direction.
+ /// </summary>
+ /// <param name="transitionEffect">The transition effect.</param>
+ private void ReverseDirection(TransitionEffect transitionEffect)
+ {
+ var circleRevealTransitionEffect = transitionEffect as CircleRevealTransitionEffect;
+
+ if (circleRevealTransitionEffect != null)
+ {
+ circleRevealTransitionEffect.Reverse = true;
+ return;
+ }
+
+ var slideInTransitionEffect = transitionEffect as SlideInTransitionEffect;
+ if (slideInTransitionEffect != null)
+ {
+ if (slideInTransitionEffect.SlideDirection == SlideDirection.RightToLeft)
+ {
+ slideInTransitionEffect.SlideDirection = SlideDirection.LeftToRight;
+ }
+ return;
+ }
+
+ var wipeTransitionEffect = transitionEffect as WipeTransitionEffect;
+ if (wipeTransitionEffect != null)
+ {
+ if (wipeTransitionEffect.WipeDirection == WipeDirection.RightToLeft)
+ {
+ wipeTransitionEffect.WipeDirection = WipeDirection.LeftToRight;
+ }
+ }
+ }
+ }
+}
diff --git a/MediaBrowser.UI.Controls/TreeHelper.cs b/MediaBrowser.UI.Controls/TreeHelper.cs
new file mode 100644
index 0000000000..0347f1eba4
--- /dev/null
+++ b/MediaBrowser.UI.Controls/TreeHelper.cs
@@ -0,0 +1,321 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Windows;
+using System.Windows.Media;
+
+namespace MediaBrowser.UI.Controls
+{
+ /// <summary>
+ /// Helper methods for UI-related tasks.
+ /// </summary>
+ public static class TreeHelper
+ {
+ /// <summary>
+ /// Gets the window.
+ /// </summary>
+ /// <param name="element">The element.</param>
+ /// <returns>Window.</returns>
+ /// <value>The window.</value>
+ public static Window GetWindow(this FrameworkElement element)
+ {
+ return element.ParentOfType<Window>();
+ }
+
+ /// <summary>
+ /// Gets the parent.
+ /// </summary>
+ /// <param name="element">The element.</param>
+ /// <returns>DependencyObject.</returns>
+ private static DependencyObject GetParent(this DependencyObject element)
+ {
+ DependencyObject parent = VisualTreeHelper.GetParent(element);
+ if (parent == null)
+ {
+ FrameworkElement frameworkElement = element as FrameworkElement;
+ if (frameworkElement != null)
+ {
+ parent = frameworkElement.Parent;
+ }
+ }
+ return parent;
+ }
+
+ /// <summary>
+ /// Gets the parents.
+ /// </summary>
+ /// <param name="element">The element.</param>
+ /// <returns>IEnumerable{DependencyObject}.</returns>
+ /// <exception cref="System.ArgumentNullException">element</exception>
+ public static IEnumerable<DependencyObject> GetParents(this DependencyObject element)
+ {
+ if (element == null)
+ {
+ throw new ArgumentNullException("element");
+ }
+ while ((element = element.GetParent()) != null)
+ {
+ yield return element;
+ }
+ yield break;
+ }
+
+ /// <summary>
+ /// Parents the type of the of.
+ /// </summary>
+ /// <typeparam name="T"></typeparam>
+ /// <param name="element">The element.</param>
+ /// <returns>``0.</returns>
+ public static T ParentOfType<T>(this DependencyObject element) where T : DependencyObject
+ {
+ if (element == null)
+ {
+ return default(T);
+ }
+ return element.GetParents().OfType<T>().FirstOrDefault<T>();
+ }
+
+ /// <summary>
+ /// Finds a Child of a given item in the visual tree.
+ /// </summary>
+ /// <typeparam name="T">The type of the queried item.</typeparam>
+ /// <param name="parent">A direct parent of the queried item.</param>
+ /// <param name="childName">x:Name or Name of child.</param>
+ /// <returns>The first parent item that matches the submitted type parameter.
+ /// If not matching item can be found,
+ /// a null parent is being returned.</returns>
+ public static T FindChild<T>(DependencyObject parent, string childName)
+ where T : DependencyObject
+ {
+ // Confirm parent and childName are valid.
+ if (parent == null) return null;
+
+ T foundChild = null;
+
+ int childrenCount = VisualTreeHelper.GetChildrenCount(parent);
+ for (int i = 0; i < childrenCount; i++)
+ {
+ var child = VisualTreeHelper.GetChild(parent, i);
+ // If the child is not of the request child type child
+ T childType = child as T;
+ if (childType == null)
+ {
+ // recursively drill down the tree
+ foundChild = FindChild<T>(child, childName);
+
+ // If the child is found, break so we do not overwrite the found child.
+ if (foundChild != null) break;
+ }
+ else if (!string.IsNullOrEmpty(childName))
+ {
+ var frameworkElement = child as FrameworkElement;
+ // If the child's name is set for search
+ if (frameworkElement != null && frameworkElement.Name == childName)
+ {
+ // if the child's name is of the request name
+ foundChild = (T)child;
+ break;
+ }
+ }
+ else
+ {
+ // child element found.
+ foundChild = (T)child;
+ break;
+ }
+ }
+
+ return foundChild;
+ }
+
+ /// <summary>
+ /// Gets the visual child.
+ /// </summary>
+ /// <typeparam name="T"></typeparam>
+ /// <param name="referenceVisual">The reference visual.</param>
+ /// <returns>``0.</returns>
+ public static T GetVisualChild<T>(this Visual referenceVisual) where T : Visual
+ {
+ Visual child = null;
+ for (Int32 i = 0; i < VisualTreeHelper.GetChildrenCount(referenceVisual); i++)
+ {
+ child = VisualTreeHelper.GetChild(referenceVisual, i) as Visual;
+ if (child != null && (child.GetType() == typeof(T)))
+ {
+ break;
+ }
+ else if (child != null)
+ {
+ child = GetVisualChild<T>(child);
+ if (child != null && (child.GetType() == typeof(T)))
+ {
+ break;
+ }
+ }
+ }
+ return child as T;
+ }
+
+ #region find parent
+
+ /// <summary>
+ /// Finds a parent of a given item on the visual tree.
+ /// </summary>
+ /// <typeparam name="T">The type of the queried item.</typeparam>
+ /// <param name="child">A direct or indirect child of the
+ /// queried item.</param>
+ /// <returns>The first parent item that matches the submitted
+ /// type parameter. If not matching item can be found, a null
+ /// reference is being returned.</returns>
+ public static T TryFindParent<T>(this DependencyObject child)
+ where T : DependencyObject
+ {
+ //get parent item
+ DependencyObject parentObject = GetParentObject(child);
+
+ //we've reached the end of the tree
+ if (parentObject == null) return null;
+
+ //check if the parent matches the type we're looking for
+ T parent = parentObject as T;
+ if (parent != null)
+ {
+ return parent;
+ }
+
+ //use recursion to proceed with next level
+ return TryFindParent<T>(parentObject);
+ }
+
+ /// <summary>
+ /// This method is an alternative to WPF's
+ /// <see cref="VisualTreeHelper.GetParent" /> method, which also
+ /// supports content elements. Keep in mind that for content element,
+ /// this method falls back to the logical tree of the element!
+ /// </summary>
+ /// <param name="child">The item to be processed.</param>
+ /// <returns>The submitted item's parent, if available. Otherwise
+ /// null.</returns>
+ public static DependencyObject GetParentObject(this DependencyObject child)
+ {
+ if (child == null) return null;
+
+ //handle content elements separately
+ ContentElement contentElement = child as ContentElement;
+ if (contentElement != null)
+ {
+ DependencyObject parent = ContentOperations.GetParent(contentElement);
+ if (parent != null) return parent;
+
+ FrameworkContentElement fce = contentElement as FrameworkContentElement;
+ return fce != null ? fce.Parent : null;
+ }
+
+ //also try searching for parent in framework elements (such as DockPanel, etc)
+ FrameworkElement frameworkElement = child as FrameworkElement;
+ if (frameworkElement != null)
+ {
+ DependencyObject parent = frameworkElement.Parent;
+ if (parent != null) return parent;
+ }
+
+ //if it's not a ContentElement/FrameworkElement, rely on VisualTreeHelper
+ return VisualTreeHelper.GetParent(child);
+ }
+
+ #endregion
+
+ #region find children
+
+ /// <summary>
+ /// Analyzes both visual and logical tree in order to find all elements of a given
+ /// type that are descendants of the <paramref name="source" /> item.
+ /// </summary>
+ /// <typeparam name="T">The type of the queried items.</typeparam>
+ /// <param name="source">The root element that marks the source of the search. If the
+ /// source is already of the requested type, it will not be included in the result.</param>
+ /// <returns>All descendants of <paramref name="source" /> that match the requested type.</returns>
+ public static IEnumerable<T> FindChildren<T>(this DependencyObject source) where T : DependencyObject
+ {
+ if (source != null)
+ {
+ var childs = GetChildObjects(source);
+ foreach (DependencyObject child in childs)
+ {
+ //analyze if children match the requested type
+ if (child is T)
+ {
+ yield return (T)child;
+ }
+
+ //recurse tree
+ foreach (T descendant in FindChildren<T>(child))
+ {
+ yield return descendant;
+ }
+ }
+ }
+ }
+
+
+ /// <summary>
+ /// This method is an alternative to WPF's
+ /// <see cref="VisualTreeHelper.GetChild" /> method, which also
+ /// supports content elements. Keep in mind that for content elements,
+ /// this method falls back to the logical tree of the element.
+ /// </summary>
+ /// <param name="parent">The item to be processed.</param>
+ /// <returns>The submitted item's child elements, if available.</returns>
+ public static IEnumerable<DependencyObject> GetChildObjects(this DependencyObject parent)
+ {
+ if (parent == null) yield break;
+
+ if (parent is ContentElement || parent is FrameworkElement)
+ {
+ //use the logical tree for content / framework elements
+ foreach (object obj in LogicalTreeHelper.GetChildren(parent))
+ {
+ var depObj = obj as DependencyObject;
+ if (depObj != null) yield return (DependencyObject)obj;
+ }
+ }
+ else
+ {
+ //use the visual tree per default
+ int count = VisualTreeHelper.GetChildrenCount(parent);
+ for (int i = 0; i < count; i++)
+ {
+ yield return VisualTreeHelper.GetChild(parent, i);
+ }
+ }
+ }
+
+ #endregion
+
+ #region find from point
+
+ /// <summary>
+ /// Tries to locate a given item within the visual tree,
+ /// starting with the dependency object at a given position.
+ /// </summary>
+ /// <typeparam name="T">The type of the element to be found
+ /// on the visual tree of the element at the given location.</typeparam>
+ /// <param name="reference">The main element which is used to perform
+ /// hit testing.</param>
+ /// <param name="point">The position to be evaluated on the origin.</param>
+ /// <returns>``0.</returns>
+ public static T TryFindFromPoint<T>(UIElement reference, Point point)
+ where T : DependencyObject
+ {
+ DependencyObject element = reference.InputHitTest(point) as DependencyObject;
+
+ if (element == null) return null;
+
+ if (element is T) return (T)element;
+
+ return TryFindParent<T>(element);
+ }
+
+ #endregion
+ }
+}
diff --git a/MediaBrowser.UI.Controls/VirtualizingWrapPanel.cs b/MediaBrowser.UI.Controls/VirtualizingWrapPanel.cs
new file mode 100644
index 0000000000..e9753ccea8
--- /dev/null
+++ b/MediaBrowser.UI.Controls/VirtualizingWrapPanel.cs
@@ -0,0 +1,735 @@
+using System;
+using System.Collections.Specialized;
+using System.ComponentModel;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Controls.Primitives;
+using System.Windows.Media;
+
+namespace MediaBrowser.UI.Controls
+{
+ /// <summary>
+ /// http://www.codeproject.com/Articles/75847/Virtualizing-WrapPanel
+ /// Positions child elements in sequential position from left to right, breaking content
+ /// to the next line at the edge of the containing box. Subsequent ordering happens
+ /// sequentially from top to bottom or from right to left, depending on the value of
+ /// the Orientation property.
+ /// </summary>
+ [DefaultProperty("Orientation")]
+ public class VirtualizingWrapPanel : VirtualizingPanel, IScrollInfo
+ {
+ /// <summary>
+ /// Identifies the ItemHeight dependency property.
+ /// </summary>
+ public static readonly DependencyProperty ItemHeightProperty = DependencyProperty.Register("ItemHeight", typeof(double), typeof(VirtualizingWrapPanel), new PropertyMetadata(100.0, new PropertyChangedCallback(VirtualizingWrapPanel.OnAppearancePropertyChanged)));
+ /// <summary>
+ /// Identifies the Orientation dependency property.
+ /// </summary>
+ public static readonly DependencyProperty OrientationProperty = DependencyProperty.Register("Orientation", typeof(Orientation), typeof(VirtualizingWrapPanel), new PropertyMetadata(Orientation.Horizontal, new PropertyChangedCallback(VirtualizingWrapPanel.OnAppearancePropertyChanged)));
+ /// <summary>
+ /// Identifies the ItemWidth dependency property.
+ /// </summary>
+ public static readonly DependencyProperty ItemWidthProperty = DependencyProperty.Register("ItemWidth", typeof(double), typeof(VirtualizingWrapPanel), new PropertyMetadata(100.0, new PropertyChangedCallback(VirtualizingWrapPanel.OnAppearancePropertyChanged)));
+ /// <summary>
+ /// Identifies the ScrollStep dependency property.
+ /// </summary>
+ public static readonly DependencyProperty ScrollStepProperty = DependencyProperty.Register("ScrollStep", typeof(double), typeof(VirtualizingWrapPanel), new PropertyMetadata(10.0, new PropertyChangedCallback(VirtualizingWrapPanel.OnAppearancePropertyChanged)));
+ private bool canHorizontallyScroll;
+ private bool canVerticallyScroll;
+ private Size contentExtent = new Size(0.0, 0.0);
+ private Point contentOffset = default(Point);
+ private ScrollViewer scrollOwner;
+ private Size viewport = new Size(0.0, 0.0);
+ private int previousItemCount;
+ /// <summary>
+ /// Gets or sets a value that specifies the height of all items that are
+ /// contained within a VirtualizingWrapPanel. This is a dependency property.
+ /// </summary>
+ public double ItemHeight
+ {
+ get
+ {
+ return (double)base.GetValue(VirtualizingWrapPanel.ItemHeightProperty);
+ }
+ set
+ {
+ base.SetValue(VirtualizingWrapPanel.ItemHeightProperty, value);
+ }
+ }
+ /// <summary>
+ /// Gets or sets a value that specifies the width of all items that are
+ /// contained within a VirtualizingWrapPanel. This is a dependency property.
+ /// </summary>
+ public double ItemWidth
+ {
+ get
+ {
+ return (double)base.GetValue(VirtualizingWrapPanel.ItemWidthProperty);
+ }
+ set
+ {
+ base.SetValue(VirtualizingWrapPanel.ItemWidthProperty, value);
+ }
+ }
+ /// <summary>
+ /// Gets or sets a value that specifies the dimension in which child
+ /// content is arranged. This is a dependency property.
+ /// </summary>
+ public Orientation Orientation
+ {
+ get
+ {
+ return (Orientation)base.GetValue(VirtualizingWrapPanel.OrientationProperty);
+ }
+ set
+ {
+ base.SetValue(VirtualizingWrapPanel.OrientationProperty, value);
+ }
+ }
+ /// <summary>
+ /// Gets or sets a value that indicates whether scrolling on the horizontal axis is possible.
+ /// </summary>
+ public bool CanHorizontallyScroll
+ {
+ get
+ {
+ return this.canHorizontallyScroll;
+ }
+ set
+ {
+ if (this.canHorizontallyScroll != value)
+ {
+ this.canHorizontallyScroll = value;
+ base.InvalidateMeasure();
+ }
+ }
+ }
+ /// <summary>
+ /// Gets or sets a value that indicates whether scrolling on the vertical axis is possible.
+ /// </summary>
+ public bool CanVerticallyScroll
+ {
+ get
+ {
+ return this.canVerticallyScroll;
+ }
+ set
+ {
+ if (this.canVerticallyScroll != value)
+ {
+ this.canVerticallyScroll = value;
+ base.InvalidateMeasure();
+ }
+ }
+ }
+ /// <summary>
+ /// Gets or sets a ScrollViewer element that controls scrolling behavior.
+ /// </summary>
+ public ScrollViewer ScrollOwner
+ {
+ get
+ {
+ return this.scrollOwner;
+ }
+ set
+ {
+ this.scrollOwner = value;
+ }
+ }
+ /// <summary>
+ /// Gets the vertical offset of the scrolled content.
+ /// </summary>
+ public double VerticalOffset
+ {
+ get
+ {
+ return this.contentOffset.Y;
+ }
+ }
+ /// <summary>
+ /// Gets the vertical size of the viewport for this content.
+ /// </summary>
+ public double ViewportHeight
+ {
+ get
+ {
+ return this.viewport.Height;
+ }
+ }
+ /// <summary>
+ /// Gets the horizontal size of the viewport for this content.
+ /// </summary>
+ public double ViewportWidth
+ {
+ get
+ {
+ return this.viewport.Width;
+ }
+ }
+ /// <summary>
+ /// Gets or sets a value for mouse wheel scroll step.
+ /// </summary>
+ public double ScrollStep
+ {
+ get
+ {
+ return (double)base.GetValue(VirtualizingWrapPanel.ScrollStepProperty);
+ }
+ set
+ {
+ base.SetValue(VirtualizingWrapPanel.ScrollStepProperty, value);
+ }
+ }
+ /// <summary>
+ /// Gets the vertical size of the extent.
+ /// </summary>
+ public double ExtentHeight
+ {
+ get
+ {
+ return this.contentExtent.Height;
+ }
+ }
+ /// <summary>
+ /// Gets the horizontal size of the extent.
+ /// </summary>
+ public double ExtentWidth
+ {
+ get
+ {
+ return this.contentExtent.Width;
+ }
+ }
+ /// <summary>
+ /// Gets the horizontal offset of the scrolled content.
+ /// </summary>
+ public double HorizontalOffset
+ {
+ get
+ {
+ return this.contentOffset.X;
+ }
+ }
+ /// <summary>
+ /// Scrolls down within content by one logical unit.
+ /// </summary>
+ public void LineDown()
+ {
+ this.SetVerticalOffset(this.VerticalOffset + this.ScrollStep);
+ }
+ /// <summary>
+ /// Scrolls left within content by one logical unit.
+ /// </summary>
+ public void LineLeft()
+ {
+ this.SetHorizontalOffset(this.HorizontalOffset - this.ScrollStep);
+ }
+ /// <summary>
+ /// Scrolls right within content by one logical unit.
+ /// </summary>
+ public void LineRight()
+ {
+ this.SetHorizontalOffset(this.HorizontalOffset + this.ScrollStep);
+ }
+ /// <summary>
+ /// Scrolls up within content by one logical unit.
+ /// </summary>
+ public void LineUp()
+ {
+ this.SetVerticalOffset(this.VerticalOffset - this.ScrollStep);
+ }
+ /// <summary>
+ /// Forces content to scroll until the coordinate space of a Visual object is visible.
+ /// </summary>
+ public Rect MakeVisible(Visual visual, Rect rectangle)
+ {
+ this.MakeVisible(visual as UIElement);
+ return rectangle;
+ }
+ /// <summary>
+ /// Scrolls down within content after a user clicks the wheel button on a mouse.
+ /// </summary>
+ public void MouseWheelDown()
+ {
+ this.SetVerticalOffset(this.VerticalOffset + this.ScrollStep);
+ }
+ /// <summary>
+ /// Scrolls left within content after a user clicks the wheel button on a mouse.
+ /// </summary>
+ public void MouseWheelLeft()
+ {
+ this.SetHorizontalOffset(this.HorizontalOffset - this.ScrollStep);
+ }
+ /// <summary>
+ /// Scrolls right within content after a user clicks the wheel button on a mouse.
+ /// </summary>
+ public void MouseWheelRight()
+ {
+ this.SetHorizontalOffset(this.HorizontalOffset + this.ScrollStep);
+ }
+ /// <summary>
+ /// Scrolls up within content after a user clicks the wheel button on a mouse.
+ /// </summary>
+ public void MouseWheelUp()
+ {
+ this.SetVerticalOffset(this.VerticalOffset - this.ScrollStep);
+ }
+ /// <summary>
+ /// Scrolls down within content by one page.
+ /// </summary>
+ public void PageDown()
+ {
+ this.SetVerticalOffset(this.VerticalOffset + this.ViewportHeight);
+ }
+ /// <summary>
+ /// Scrolls left within content by one page.
+ /// </summary>
+ public void PageLeft()
+ {
+ this.SetHorizontalOffset(this.HorizontalOffset - this.ViewportHeight);
+ }
+ /// <summary>
+ /// Scrolls right within content by one page.
+ /// </summary>
+ public void PageRight()
+ {
+ this.SetHorizontalOffset(this.HorizontalOffset + this.ViewportHeight);
+ }
+ /// <summary>
+ /// Scrolls up within content by one page.
+ /// </summary>
+ public void PageUp()
+ {
+ this.SetVerticalOffset(this.VerticalOffset - this.viewport.Height);
+ }
+ /// <summary>
+ /// Sets the amount of vertical offset.
+ /// </summary>
+ public void SetVerticalOffset(double offset)
+ {
+ if (offset < 0.0 || this.ViewportHeight >= this.ExtentHeight)
+ {
+ offset = 0.0;
+ }
+ else
+ {
+ if (offset + this.ViewportHeight >= this.ExtentHeight)
+ {
+ offset = this.ExtentHeight - this.ViewportHeight;
+ }
+ }
+ this.contentOffset.Y = offset;
+ if (this.ScrollOwner != null)
+ {
+ this.ScrollOwner.InvalidateScrollInfo();
+ }
+ base.InvalidateMeasure();
+ }
+ /// <summary>
+ /// Sets the amount of horizontal offset.
+ /// </summary>
+ public void SetHorizontalOffset(double offset)
+ {
+ if (offset < 0.0 || this.ViewportWidth >= this.ExtentWidth)
+ {
+ offset = 0.0;
+ }
+ else
+ {
+ if (offset + this.ViewportWidth >= this.ExtentWidth)
+ {
+ offset = this.ExtentWidth - this.ViewportWidth;
+ }
+ }
+ this.contentOffset.X = offset;
+ if (this.ScrollOwner != null)
+ {
+ this.ScrollOwner.InvalidateScrollInfo();
+ }
+ base.InvalidateMeasure();
+ }
+ /// <summary>
+ /// Note: Works only for vertical.
+ /// </summary>
+ internal void PageLast()
+ {
+ this.contentOffset.Y = this.ExtentHeight;
+ if (this.ScrollOwner != null)
+ {
+ this.ScrollOwner.InvalidateScrollInfo();
+ }
+ base.InvalidateMeasure();
+ }
+ /// <summary>
+ /// Note: Works only for vertical.
+ /// </summary>
+ internal void PageFirst()
+ {
+ this.contentOffset.Y = 0.0;
+ if (this.ScrollOwner != null)
+ {
+ this.ScrollOwner.InvalidateScrollInfo();
+ }
+ base.InvalidateMeasure();
+ }
+ /// <summary>
+ /// When items are removed, remove the corresponding UI if necessary.
+ /// </summary>
+ /// <param name="sender"></param>
+ /// <param name="args"></param>
+ protected override void OnItemsChanged(object sender, ItemsChangedEventArgs args)
+ {
+ switch (args.Action)
+ {
+ case NotifyCollectionChangedAction.Remove:
+ case NotifyCollectionChangedAction.Replace:
+ case NotifyCollectionChangedAction.Move:
+ base.RemoveInternalChildRange(args.Position.Index, args.ItemUICount);
+ return;
+ case NotifyCollectionChangedAction.Reset:
+ {
+ ItemsControl itemsControl = ItemsControl.GetItemsOwner(this);
+ if (itemsControl != null)
+ {
+ if (this.previousItemCount != itemsControl.Items.Count)
+ {
+ if (this.Orientation == Orientation.Horizontal)
+ {
+ this.SetVerticalOffset(0.0);
+ }
+ else
+ {
+ this.SetHorizontalOffset(0.0);
+ }
+ }
+ this.previousItemCount = itemsControl.Items.Count;
+ }
+ return;
+ }
+ default:
+ return;
+ }
+ }
+ /// <summary>
+ /// Measure the children.
+ /// </summary>
+ /// <param name="availableSize">The available size.</param>
+ /// <returns>The desired size.</returns>
+ protected override Size MeasureOverride(Size availableSize)
+ {
+ this.InvalidateScrollInfo(availableSize);
+ int firstVisibleIndex;
+ int lastVisibleIndex;
+ if (this.Orientation == Orientation.Horizontal)
+ {
+ this.GetVerticalVisibleRange(out firstVisibleIndex, out lastVisibleIndex);
+ }
+ else
+ {
+ this.GetHorizontalVisibleRange(out firstVisibleIndex, out lastVisibleIndex);
+ }
+ UIElementCollection children = base.Children;
+ IItemContainerGenerator generator = base.ItemContainerGenerator;
+ if (generator != null)
+ {
+ GeneratorPosition startPos = generator.GeneratorPositionFromIndex(firstVisibleIndex);
+ int childIndex = (startPos.Offset == 0) ? startPos.Index : (startPos.Index + 1);
+ if (childIndex == -1)
+ {
+ this.RefreshOffset();
+ }
+ using (generator.StartAt(startPos, GeneratorDirection.Forward, true))
+ {
+ int itemIndex = firstVisibleIndex;
+ while (itemIndex <= lastVisibleIndex)
+ {
+ bool newlyRealized;
+ UIElement child = generator.GenerateNext(out newlyRealized) as UIElement;
+ if (newlyRealized)
+ {
+ if (childIndex >= children.Count)
+ {
+ base.AddInternalChild(child);
+ }
+ else
+ {
+ base.InsertInternalChild(childIndex, child);
+ }
+ generator.PrepareItemContainer(child);
+ }
+ if (child != null)
+ {
+ child.Measure(new Size(this.ItemWidth, this.ItemHeight));
+ }
+ itemIndex++;
+ childIndex++;
+ }
+ }
+ this.CleanUpChildren(firstVisibleIndex, lastVisibleIndex);
+ }
+ if (IsCloseTo(availableSize.Height, double.PositiveInfinity) || IsCloseTo(availableSize.Width, double.PositiveInfinity))
+ {
+ return base.MeasureOverride(availableSize);
+ }
+
+ var itemsControl = ItemsControl.GetItemsOwner(this);
+ var numItems = itemsControl.Items.Count;
+
+ var width = availableSize.Width;
+ var height = availableSize.Height;
+
+ if (Orientation == Orientation.Vertical)
+ {
+ var numRows = Math.Floor(availableSize.Height / ItemHeight);
+
+ height = numRows * ItemHeight;
+
+ var requiredColumns = Math.Ceiling(numItems / numRows);
+
+ width = Math.Min(requiredColumns * ItemWidth, width);
+ }
+ else
+ {
+ var numColumns = Math.Floor(availableSize.Width / ItemWidth);
+
+ width = numColumns * ItemWidth;
+
+ //if (numItems > 0 && numItems < numColumns)
+ //{
+ // width = Math.Min(numColumns, numItems) * ItemWidth;
+ //}
+
+ var requiredRows = Math.Ceiling(numItems / numColumns);
+
+ height = Math.Min(requiredRows * ItemHeight, height);
+ }
+
+ return new Size(width, height);
+ }
+
+ /// <summary>
+ /// Arranges the children.
+ /// </summary>
+ /// <param name="finalSize">The available size.</param>
+ /// <returns>The used size.</returns>
+ protected override Size ArrangeOverride(Size finalSize)
+ {
+ bool isHorizontal = this.Orientation == Orientation.Horizontal;
+ this.InvalidateScrollInfo(finalSize);
+ int i = 0;
+ foreach (object item in base.Children)
+ {
+ this.ArrangeChild(isHorizontal, finalSize, i++, item as UIElement);
+ }
+ return finalSize;
+ }
+ private static void OnAppearancePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+ {
+ UIElement panel = d as UIElement;
+ if (panel != null)
+ {
+ panel.InvalidateMeasure();
+ }
+ }
+ private void MakeVisible(UIElement element)
+ {
+ ItemContainerGenerator generator = base.ItemContainerGenerator.GetItemContainerGeneratorForPanel(this);
+ if (element != null && generator != null)
+ {
+ for (int itemIndex = generator.IndexFromContainer(element); itemIndex == -1; itemIndex = generator.IndexFromContainer(element))
+ {
+ element = element.ParentOfType<UIElement>();
+ }
+ ScrollViewer scrollViewer = element.ParentOfType<ScrollViewer>();
+ if (scrollViewer != null)
+ {
+ GeneralTransform elementTransform = element.TransformToVisual(scrollViewer);
+ Rect elementRectangle = elementTransform.TransformBounds(new Rect(new Point(0.0, 0.0), element.RenderSize));
+
+ if (this.Orientation == Orientation.Horizontal)
+ {
+ var padding = ItemHeight / 3;
+
+ if (elementRectangle.Bottom > this.ViewportHeight)
+ {
+ this.SetVerticalOffset(this.contentOffset.Y + elementRectangle.Bottom - this.ViewportHeight + padding);
+ return;
+ }
+ if (elementRectangle.Top < 0.0)
+ {
+ this.SetVerticalOffset(this.contentOffset.Y + elementRectangle.Top - padding);
+ return;
+ }
+ }
+ else
+ {
+ var padding = ItemWidth / 3;
+
+ if (elementRectangle.Right > this.ViewportWidth)
+ {
+ this.SetHorizontalOffset(this.contentOffset.X + elementRectangle.Right - this.ViewportWidth + padding);
+ return;
+ }
+ if (elementRectangle.Left < 0.0)
+ {
+ this.SetHorizontalOffset(this.contentOffset.X + elementRectangle.Left - padding);
+ }
+ }
+ }
+ }
+ }
+ private void GetVerticalVisibleRange(out int firstVisibleItemIndex, out int lastVisibleItemIndex)
+ {
+ int childrenPerRow = this.GetVerticalChildrenCountPerRow(this.contentExtent);
+ firstVisibleItemIndex = (int)Math.Floor(this.VerticalOffset / this.ItemHeight) * childrenPerRow;
+ lastVisibleItemIndex = (int)Math.Ceiling((this.VerticalOffset + this.ViewportHeight) / this.ItemHeight) * childrenPerRow - 1;
+ this.AdjustVisibleRange(ref firstVisibleItemIndex, ref lastVisibleItemIndex);
+ }
+ private void GetHorizontalVisibleRange(out int firstVisibleItemIndex, out int lastVisibleItemIndex)
+ {
+ int childrenPerRow = this.GetHorizontalChildrenCountPerRow(this.contentExtent);
+ firstVisibleItemIndex = (int)Math.Floor(this.HorizontalOffset / this.ItemWidth) * childrenPerRow;
+ lastVisibleItemIndex = (int)Math.Ceiling((this.HorizontalOffset + this.ViewportWidth) / this.ItemWidth) * childrenPerRow - 1;
+ this.AdjustVisibleRange(ref firstVisibleItemIndex, ref lastVisibleItemIndex);
+ }
+ private void AdjustVisibleRange(ref int firstVisibleItemIndex, ref int lastVisibleItemIndex)
+ {
+ firstVisibleItemIndex--;
+ lastVisibleItemIndex++;
+ ItemsControl itemsControl = ItemsControl.GetItemsOwner(this);
+ if (itemsControl != null)
+ {
+ if (firstVisibleItemIndex < 0)
+ {
+ firstVisibleItemIndex = 0;
+ }
+ if (lastVisibleItemIndex >= itemsControl.Items.Count)
+ {
+ lastVisibleItemIndex = itemsControl.Items.Count - 1;
+ }
+ }
+ }
+ private void CleanUpChildren(int minIndex, int maxIndex)
+ {
+ UIElementCollection children = base.Children;
+ IItemContainerGenerator generator = base.ItemContainerGenerator;
+ for (int i = children.Count - 1; i >= 0; i--)
+ {
+ GeneratorPosition pos = new GeneratorPosition(i, 0);
+ int itemIndex = generator.IndexFromGeneratorPosition(pos);
+ if (itemIndex < minIndex || itemIndex > maxIndex)
+ {
+ generator.Remove(pos, 1);
+ base.RemoveInternalChildRange(i, 1);
+ }
+ }
+ }
+ private void ArrangeChild(bool isHorizontal, Size finalSize, int index, UIElement child)
+ {
+ if (child == null)
+ {
+ return;
+ }
+ int count = isHorizontal ? this.GetVerticalChildrenCountPerRow(finalSize) : this.GetHorizontalChildrenCountPerRow(finalSize);
+ int itemIndex = base.ItemContainerGenerator.IndexFromGeneratorPosition(new GeneratorPosition(index, 0));
+ int row = isHorizontal ? (itemIndex / count) : (itemIndex % count);
+ int column = isHorizontal ? (itemIndex % count) : (itemIndex / count);
+ Rect rect = new Rect((double)column * this.ItemWidth, (double)row * this.ItemHeight, this.ItemWidth, this.ItemHeight);
+ if (isHorizontal)
+ {
+ rect.Y -= this.VerticalOffset;
+ }
+ else
+ {
+ rect.X -= this.HorizontalOffset;
+ }
+ child.Arrange(rect);
+ }
+ private void InvalidateScrollInfo(Size availableSize)
+ {
+ ItemsControl ownerItemsControl = ItemsControl.GetItemsOwner(this);
+ if (ownerItemsControl != null)
+ {
+ Size extent = this.GetExtent(availableSize, ownerItemsControl.Items.Count);
+ if (extent != this.contentExtent)
+ {
+ this.contentExtent = extent;
+ this.RefreshOffset();
+ }
+ if (availableSize != this.viewport)
+ {
+ this.viewport = availableSize;
+ this.InvalidateScrollOwner();
+ }
+ }
+ }
+ private void RefreshOffset()
+ {
+ if (this.Orientation == Orientation.Horizontal)
+ {
+ this.SetVerticalOffset(this.VerticalOffset);
+ return;
+ }
+ this.SetHorizontalOffset(this.HorizontalOffset);
+ }
+ private void InvalidateScrollOwner()
+ {
+ if (this.ScrollOwner != null)
+ {
+ this.ScrollOwner.InvalidateScrollInfo();
+ }
+ }
+ private Size GetExtent(Size availableSize, int itemCount)
+ {
+ if (this.Orientation == Orientation.Horizontal)
+ {
+ int childrenPerRow = this.GetVerticalChildrenCountPerRow(availableSize);
+ return new Size((double)childrenPerRow * this.ItemWidth, this.ItemHeight * Math.Ceiling((double)itemCount / (double)childrenPerRow));
+ }
+ int childrenPerRow2 = this.GetHorizontalChildrenCountPerRow(availableSize);
+ return new Size(this.ItemWidth * Math.Ceiling((double)itemCount / (double)childrenPerRow2), (double)childrenPerRow2 * this.ItemHeight);
+ }
+ private int GetVerticalChildrenCountPerRow(Size availableSize)
+ {
+ int childrenCountPerRow;
+ if (availableSize.Width == double.PositiveInfinity)
+ {
+ childrenCountPerRow = base.Children.Count;
+ }
+ else
+ {
+ childrenCountPerRow = Math.Max(1, (int)Math.Floor(availableSize.Width / this.ItemWidth));
+ }
+ return childrenCountPerRow;
+ }
+ private int GetHorizontalChildrenCountPerRow(Size availableSize)
+ {
+ int childrenCountPerRow;
+ if (availableSize.Height == double.PositiveInfinity)
+ {
+ childrenCountPerRow = base.Children.Count;
+ }
+ else
+ {
+ childrenCountPerRow = Math.Max(1, (int)Math.Floor(availableSize.Height / this.ItemHeight));
+ }
+ return childrenCountPerRow;
+ }
+
+ private static bool IsCloseTo(double value1, double value2)
+ {
+ return AreClose(value1, value2);
+ }
+
+ private static bool AreClose(double value1, double value2)
+ {
+ if (value1 == value2)
+ {
+ return true;
+ }
+ double num = (Math.Abs(value1) + Math.Abs(value2) + 10.0) * 2.2204460492503131E-16;
+ double num2 = value1 - value2;
+ return -num < num2 && num > num2;
+ }
+ }
+} \ No newline at end of file