diff options
| author | LukePulverenti <luke.pulverenti@gmail.com> | 2013-02-20 20:33:05 -0500 |
|---|---|---|
| committer | LukePulverenti <luke.pulverenti@gmail.com> | 2013-02-20 20:33:05 -0500 |
| commit | 767cdc1f6f6a63ce997fc9476911e2c361f9d402 (patch) | |
| tree | 49add55976f895441167c66cfa95e5c7688d18ce /MediaBrowser.UI.Controls | |
| parent | 845554722efaed872948a9e0f7202e3ef52f1b6e (diff) | |
Pushing missing changes
Diffstat (limited to 'MediaBrowser.UI.Controls')
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 |
