From 767cdc1f6f6a63ce997fc9476911e2c361f9d402 Mon Sep 17 00:00:00 2001 From: LukePulverenti Date: Wed, 20 Feb 2013 20:33:05 -0500 Subject: Pushing missing changes --- MediaBrowser.UI.Controls/BaseModalWindow.cs | 62 ++ MediaBrowser.UI.Controls/BaseUserControl.cs | 21 + MediaBrowser.UI.Controls/BaseWindow.cs | 175 +++++ MediaBrowser.UI.Controls/ExtendedButton.cs | 49 ++ MediaBrowser.UI.Controls/ExtendedCheckbox.cs | 49 ++ MediaBrowser.UI.Controls/ExtendedListBox.cs | 260 ++++++++ MediaBrowser.UI.Controls/ExtendedRadioButton.cs | 49 ++ MediaBrowser.UI.Controls/ExtendedScrollViewer.cs | 41 ++ MediaBrowser.UI.Controls/ItemEventArgs.cs | 17 + .../MediaBrowser.UI.Controls.csproj | 107 +++ .../Properties/AssemblyInfo.cs | 55 ++ .../Properties/Resources.Designer.cs | 62 ++ MediaBrowser.UI.Controls/Properties/Resources.resx | 117 ++++ .../Properties/Settings.Designer.cs | 30 + .../Properties/Settings.settings | 7 + MediaBrowser.UI.Controls/ScrollingPanel.cs | 404 +++++++++++ MediaBrowser.UI.Controls/Themes/Generic.xaml | 17 + MediaBrowser.UI.Controls/TransitionControl.cs | 147 +++++ MediaBrowser.UI.Controls/TransitionFrame.cs | 194 ++++++ MediaBrowser.UI.Controls/TreeHelper.cs | 321 +++++++++ MediaBrowser.UI.Controls/VirtualizingWrapPanel.cs | 735 +++++++++++++++++++++ 21 files changed, 2919 insertions(+) create mode 100644 MediaBrowser.UI.Controls/BaseModalWindow.cs create mode 100644 MediaBrowser.UI.Controls/BaseUserControl.cs create mode 100644 MediaBrowser.UI.Controls/BaseWindow.cs create mode 100644 MediaBrowser.UI.Controls/ExtendedButton.cs create mode 100644 MediaBrowser.UI.Controls/ExtendedCheckbox.cs create mode 100644 MediaBrowser.UI.Controls/ExtendedListBox.cs create mode 100644 MediaBrowser.UI.Controls/ExtendedRadioButton.cs create mode 100644 MediaBrowser.UI.Controls/ExtendedScrollViewer.cs create mode 100644 MediaBrowser.UI.Controls/ItemEventArgs.cs create mode 100644 MediaBrowser.UI.Controls/MediaBrowser.UI.Controls.csproj create mode 100644 MediaBrowser.UI.Controls/Properties/AssemblyInfo.cs create mode 100644 MediaBrowser.UI.Controls/Properties/Resources.Designer.cs create mode 100644 MediaBrowser.UI.Controls/Properties/Resources.resx create mode 100644 MediaBrowser.UI.Controls/Properties/Settings.Designer.cs create mode 100644 MediaBrowser.UI.Controls/Properties/Settings.settings create mode 100644 MediaBrowser.UI.Controls/ScrollingPanel.cs create mode 100644 MediaBrowser.UI.Controls/Themes/Generic.xaml create mode 100644 MediaBrowser.UI.Controls/TransitionControl.cs create mode 100644 MediaBrowser.UI.Controls/TransitionFrame.cs create mode 100644 MediaBrowser.UI.Controls/TreeHelper.cs create mode 100644 MediaBrowser.UI.Controls/VirtualizingWrapPanel.cs (limited to 'MediaBrowser.UI.Controls') 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 +{ + /// + /// Class BaseModalWindow + /// + public class BaseModalWindow : BaseWindow + { + /// + /// Shows the modal. + /// + /// The owner. + 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(); + } + + /// + /// Called when [browser back]. + /// + protected override void OnBrowserBack() + { + base.OnBrowserBack(); + + CloseModal(); + } + + /// + /// Raises the event. This method is invoked whenever is set to true internally. + /// + /// The that contains the event data. + protected override void OnInitialized(EventArgs e) + { + base.OnInitialized(e); + + DataContext = this; + } + + /// + /// Closes the modal. + /// + 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 +{ + /// + /// Provides a base class for all user controls + /// + 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 +{ + /// + /// Provides a base class for all Windows + /// + public abstract class BaseWindow : Window, INotifyPropertyChanged + { + /// + /// Occurs when [property changed]. + /// + public event PropertyChangedEventHandler PropertyChanged; + + /// + /// Called when [property changed]. + /// + /// The info. + public void OnPropertyChanged(String info) + { + if (PropertyChanged != null) + { + PropertyChanged(this, new PropertyChangedEventArgs(info)); + } + } + + /// + /// The _content scale + /// + private double _contentScale = 1; + /// + /// Gets the content scale. + /// + /// The content scale. + public double ContentScale + { + get { return _contentScale; } + private set + { + _contentScale = value; + OnPropertyChanged("ContentScale"); + } + } + + /// + /// Initializes a new instance of the class. + /// + protected BaseWindow() + : base() + { + SizeChanged += MainWindow_SizeChanged; + Loaded += BaseWindowLoaded; + } + + /// + /// Bases the window loaded. + /// + /// The sender. + /// The instance containing the event data. + void BaseWindowLoaded(object sender, RoutedEventArgs e) + { + OnLoaded(); + } + + /// + /// Called when [loaded]. + /// + protected virtual void OnLoaded() + { + MoveFocus(new TraversalRequest(FocusNavigationDirection.First)); + } + + /// + /// Handles the SizeChanged event of the MainWindow control. + /// + /// The source of the event. + /// The instance containing the event data. + void MainWindow_SizeChanged(object sender, SizeChangedEventArgs e) + { + ContentScale = e.NewSize.Height / 1080; + } + + /// + /// Called when [browser back]. + /// + protected virtual void OnBrowserBack() + { + + } + + /// + /// Called when [browser forward]. + /// + protected virtual void OnBrowserForward() + { + + } + + /// + /// Invoked when an unhandled  attached event reaches an element in its route that is derived from this class. Implement this method to add class handling for this event. + /// + /// The that contains the event data. + 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); + } + + /// + /// Determines if a keypress should be treated as a backward press + /// + /// The instance containing the event data. + /// true if [is back press] [the specified e]; otherwise, false. + 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; + } + + /// + /// Determines if a keypress should be treated as a forward press + /// + /// The instance containing the event data. + /// true if [is forward press] [the specified e]; otherwise, false. + 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 +{ + /// + /// This subclass simply autofocuses itself when the mouse moves over it + /// + public class ExtendedButton : Button + { + private Point? _lastMouseMovePoint; + + /// + /// Handles OnMouseMove to auto-select the item that's being moused over + /// + 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 +{ + /// + /// Extends Checkbox to provide focus on mouse over + /// + public class ExtendedCheckbox : CheckBox + { + private Point? _lastMouseMovePoint; + + /// + /// Handles OnMouseMove to auto-select the item that's being moused over + /// + 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 +{ + /// + /// 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 + /// + public class ExtendedListBox : ListBox + { + /// + /// Fired when an item is clicked or invoked using the enter key + /// + public event EventHandler> ItemInvoked; + + /// + /// Called when [item invoked]. + /// + /// The bound object. + protected virtual void OnItemInvoked(object boundObject) + { + if (ItemInvoked != null) + { + ItemInvoked(this, new ItemEventArgs { Argument = boundObject }); + } + } + + /// + /// The _auto focus + /// + private bool _autoFocus = true; + /// + /// Gets or sets a value indicating if the first list item should be auto-focused on load + /// + /// true if [auto focus]; otherwise, false. + public bool AutoFocus + { + get { return _autoFocus; } + set + { + _autoFocus = value; + } + } + + /// + /// Initializes a new instance of the class. + /// + public ExtendedListBox() + : base() + { + ItemContainerGenerator.StatusChanged += ItemContainerGeneratorStatusChanged; + } + + /// + /// The mouse down object + /// + private object mouseDownObject; + + /// + /// Invoked when an unhandled 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. + /// + /// The that contains the event data. The event data reports that one or more mouse buttons were pressed. + protected override void OnPreviewMouseDown(MouseButtonEventArgs e) + { + base.OnPreviewMouseDown(e); + + // Get the item that the mouse down event occurred on + mouseDownObject = GetBoundListItemObject((DependencyObject)e.OriginalSource); + } + + /// + /// Invoked when an unhandled  routed event reaches an element in its route that is derived from this class. Implement this method to add class handling for this event. + /// + /// The that contains the event data. The event data reports that the left mouse button was released. + 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); + } + } + } + + /// + /// The key down object + /// + private object keyDownObject; + + /// + /// Responds to the event. + /// + /// Provides data for . + 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); + } + + /// + /// Invoked when an unhandled  attached event reaches an element in its route that is derived from this class. Implement this method to add class handling for this event. + /// + /// The that contains the event data. + 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; + } + } + + /// + /// The _last mouse move point + /// + private Point? _lastMouseMovePoint; + + /// + /// Handles OnMouseMove to auto-select the item that's being moused over + /// + /// Provides data for . + 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(); + } + } + } + + /// + /// Gets the datacontext for a given ListBoxItem + /// + /// The dep. + /// System.Object. + private object GetBoundListItemObject(DependencyObject dep) + { + while ((dep != null) && !(dep is ListBoxItem)) + { + dep = VisualTreeHelper.GetParent(dep); + } + + if (dep == null) + { + return null; + } + + return ItemContainerGenerator.ItemFromContainer(dep); + } + + /// + /// Autofocuses the first list item when the list is loaded + /// + /// The sender. + /// The instance containing the event data. + void ItemContainerGeneratorStatusChanged(object sender, EventArgs e) + { + if (ItemContainerGenerator.Status == GeneratorStatus.ContainersGenerated && AutoFocus) + { + Dispatcher.InvokeAsync(OnContainersGenerated); + } + } + + /// + /// Called when [containers generated]. + /// + 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 +{ + /// + /// Extends RadioButton to provide focus on mouse over, and invoke on enter press + /// + public class ExtendedRadioButton : RadioButton + { + private Point? _lastMouseMovePoint; + + /// + /// Handles OnMouseMove to auto-select the item that's being moused over + /// + 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 +{ + /// + /// This subclass solves the problem of ScrollViewers eating KeyDown for all arrow keys + /// + 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 +{ + /// + /// Provides a generic EventArgs subclass that can hold any kind of object + /// + /// + public class ItemEventArgs : EventArgs + { + /// + /// Gets or sets the argument. + /// + /// The argument. + 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 @@ + + + + + Debug + AnyCPU + {1ADFE460-FD95-46FA-8871-CCCB4B62E2E8} + library + Properties + MediaBrowser.UI.Controls + MediaBrowser.UI.Controls + v4.5 + 512 + {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + 4 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\ThirdParty\Expression\Microsoft.Expression.Effects.dll + + + ..\ThirdParty\Expression\Microsoft.Expression.Interactions.dll + + + + + + + + + + 4.0 + + + + + + + + + + + + + + + + + Code + + + True + True + Resources.resx + + + True + Settings.settings + True + + + + + + + + ResXFileCodeGenerator + Resources.Designer.cs + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + + + + MSBuild:Compile + Designer + + + + + \ 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 +//CultureYouAreCodingWith in your .csproj file +//inside a . For example, if you are using US english +//in your source files, set the 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 @@ +//------------------------------------------------------------------------------ +// +// 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. +// +//------------------------------------------------------------------------------ + +namespace MediaBrowser.UI.Controls.Properties { + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // 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() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [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; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [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 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ 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 @@ +//------------------------------------------------------------------------------ +// +// 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. +// +//------------------------------------------------------------------------------ + +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 @@ + + + + + + + \ 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 +{ + /// + /// 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. + /// + public class ScrollingPanel : Grid, IScrollInfo + { + /// + /// The infinite size + /// + private static Size InfiniteSize = new Size(double.PositiveInfinity, double.PositiveInfinity); + /// + /// The line size + /// + private const double LineSize = 16; + /// + /// The wheel size + /// + private const double WheelSize = 3 * LineSize; + + /// + /// The _ offset + /// + private Vector _Offset; + /// + /// The _ extent + /// + private Size _Extent; + /// + /// The _ viewport + /// + private Size _Viewport; + + /// + /// The _ animation length + /// + private TimeSpan _AnimationLength = TimeSpan.FromMilliseconds(125); + + /// + /// When overridden in a derived class, measures the size in layout required for child elements and determines a size for the -derived class. + /// + /// 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. + /// The size that this element determines it needs during layout, based on its calculations of child element sizes. + 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; + } + + /// + /// Arranges the content of a element. + /// + /// Specifies the size this element should use to arrange its child elements. + /// that represents the arranged size of this Grid element and its children. + 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; + } + + /// + /// Gets the animation. + /// + /// To value. + /// DoubleAnimation. + private DoubleAnimation GetAnimation(double toValue) + { + var animation = new DoubleAnimation(toValue, _AnimationLength); + + animation.EasingFunction = new ExponentialEase { EasingMode = EasingMode.EaseInOut }; + + return animation; + } + + #region Movement Methods + /// + /// Scrolls down within content by one logical unit. + /// + public void LineDown() + { SetVerticalOffset(VerticalOffset + LineSize); } + + /// + /// Scrolls up within content by one logical unit. + /// + public void LineUp() + { SetVerticalOffset(VerticalOffset - LineSize); } + + /// + /// Scrolls left within content by one logical unit. + /// + public void LineLeft() + { SetHorizontalOffset(HorizontalOffset - LineSize); } + + /// + /// Scrolls right within content by one logical unit. + /// + public void LineRight() + { SetHorizontalOffset(HorizontalOffset + LineSize); } + + /// + /// Scrolls down within content after a user clicks the wheel button on a mouse. + /// + public void MouseWheelDown() + { SetVerticalOffset(VerticalOffset + WheelSize); } + + /// + /// Scrolls up within content after a user clicks the wheel button on a mouse. + /// + public void MouseWheelUp() + { SetVerticalOffset(VerticalOffset - WheelSize); } + + /// + /// Scrolls left within content after a user clicks the wheel button on a mouse. + /// + public void MouseWheelLeft() + { SetHorizontalOffset(HorizontalOffset - WheelSize); } + + /// + /// Scrolls right within content after a user clicks the wheel button on a mouse. + /// + public void MouseWheelRight() + { SetHorizontalOffset(HorizontalOffset + WheelSize); } + + /// + /// Scrolls down within content by one page. + /// + public void PageDown() + { SetVerticalOffset(VerticalOffset + ViewportHeight); } + + /// + /// Scrolls up within content by one page. + /// + public void PageUp() + { SetVerticalOffset(VerticalOffset - ViewportHeight); } + + /// + /// Scrolls left within content by one page. + /// + public void PageLeft() + { SetHorizontalOffset(HorizontalOffset - ViewportWidth); } + + /// + /// Scrolls right within content by one page. + /// + public void PageRight() + { SetHorizontalOffset(HorizontalOffset + ViewportWidth); } + #endregion + + /// + /// Gets or sets a element that controls scrolling behavior. + /// + /// The scroll owner. + /// A element that controls scrolling behavior. This property has no default value. + public ScrollViewer ScrollOwner { get; set; } + + /// + /// Gets or sets a value that indicates whether scrolling on the horizontal axis is possible. + /// + /// true if this instance can horizontally scroll; otherwise, false. + /// true if scrolling is possible; otherwise, false. This property has no default value. + public bool CanHorizontallyScroll { get; set; } + + /// + /// Gets or sets a value that indicates whether scrolling on the vertical axis is possible. + /// + /// true if this instance can vertically scroll; otherwise, false. + /// true if scrolling is possible; otherwise, false. This property has no default value. + public bool CanVerticallyScroll { get; set; } + + /// + /// Gets the vertical size of the extent. + /// + /// The height of the extent. + /// A that represents, in device independent pixels, the vertical size of the extent.This property has no default value. + public double ExtentHeight + { get { return _Extent.Height; } } + + /// + /// Gets the horizontal size of the extent. + /// + /// The width of the extent. + /// A that represents, in device independent pixels, the horizontal size of the extent. This property has no default value. + public double ExtentWidth + { get { return _Extent.Width; } } + + /// + /// Gets the horizontal offset of the scrolled content. + /// + /// The horizontal offset. + /// A that represents, in device independent pixels, the horizontal offset. This property has no default value. + public double HorizontalOffset + { get { return _Offset.X; } } + + /// + /// Gets the vertical offset of the scrolled content. + /// + /// The vertical offset. + /// A that represents, in device independent pixels, the vertical offset of the scrolled content. Valid values are between zero and the minus the . This property has no default value. + public double VerticalOffset + { get { return _Offset.Y; } } + + /// + /// Gets the vertical size of the viewport for this content. + /// + /// The height of the viewport. + /// A that represents, in device independent pixels, the vertical size of the viewport for this content. This property has no default value. + public double ViewportHeight + { get { return _Viewport.Height; } } + + /// + /// Gets the horizontal size of the viewport for this content. + /// + /// The width of the viewport. + /// A that represents, in device independent pixels, the horizontal size of the viewport for this content. This property has no default value. + public double ViewportWidth + { get { return _Viewport.Width; } } + + /// + /// Forces content to scroll until the coordinate space of a object is visible. + /// + /// A that becomes visible. + /// A bounding rectangle that identifies the coordinate space to make visible. + /// A that is visible. + 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; + } + + /// + /// Calculates the new scroll offset. + /// + /// The top view. + /// The bottom view. + /// The top child. + /// The bottom child. + /// System.Double. + 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)); + } + + /// + /// Verifies the scroll data. + /// + /// The viewport. + /// The extent. + 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(); } + } + + /// + /// Sets the amount of horizontal offset. + /// + /// The degree to which content is horizontally offset from the containing viewport. + public void SetHorizontalOffset(double offset) + { + offset = Math.Max(0, + Math.Min(offset, ExtentWidth - ViewportWidth)); + if (!offset.Equals(_Offset.X)) + { + _Offset.X = offset; + InvalidateArrange(); + } + } + + /// + /// Sets the amount of vertical offset. + /// + /// The degree to which content is vertically offset from the containing viewport. + 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 @@ + + + + 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 +{ + /// + /// http://victorcher.blogspot.com/2012/02/wpf-transactions.html + /// + public class TransitionControl : ContentControl + { + /// + /// The _content presenter + /// + private ContentPresenter _contentPresenter; + + /// + /// Initializes static members of the class. + /// + static TransitionControl() + { + DefaultStyleKeyProperty.OverrideMetadata( + typeof(TransitionControl), new FrameworkPropertyMetadata(typeof(TransitionControl))); + + ContentProperty.OverrideMetadata( + typeof(TransitionControl), new FrameworkPropertyMetadata(OnContentPropertyChanged)); + } + + /// + /// When overridden in a derived class, is invoked whenever application code or internal processes call . + /// + public override void OnApplyTemplate() + { + base.OnApplyTemplate(); + _contentPresenter = (ContentPresenter)Template.FindName("ContentPresenter", this); + } + + #region DP TransitionType + + /// + /// Gets or sets the type of the transition. + /// + /// The type of the transition. + 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... + /// + /// The transition type property + /// + public static readonly DependencyProperty TransitionTypeProperty = + DependencyProperty.Register("TransitionType", typeof(TransitionEffect), typeof(TransitionControl), + new UIPropertyMetadata(new BlindsTransitionEffect())); + + #endregion DP TransitionType + + #region DP Transition Animation + + /// + /// Gets or sets the transition animation. + /// + /// The transition animation. + 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... + /// + /// The transition animation property + /// + public static readonly DependencyProperty TransitionAnimationProperty = + DependencyProperty.Register("TransitionAnimation", typeof(DoubleAnimation), typeof(TransitionControl), new UIPropertyMetadata(null)); + + #endregion DP Transition Animation + + /// + /// Called when [content property changed]. + /// + /// The dp. + /// The instance containing the event data. + 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; + } + } + + /// + /// Animates the content. + /// + /// The old content. + /// The new content. + 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 +{ + /// + /// Class TransitionFrame + /// + public class TransitionFrame : Frame + { + /// + /// The _content presenter + /// + private ContentPresenter _contentPresenter = null; + + #region DP TransitionType + + /// + /// Gets or sets the type of the transition. + /// + /// The type of the transition. + 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... + /// + /// The transition type property + /// + public static readonly DependencyProperty TransitionTypeProperty = + DependencyProperty.Register("TransitionType", typeof(TransitionEffect), typeof(TransitionFrame), + new UIPropertyMetadata(new BlindsTransitionEffect())); + + #endregion DP TransitionType + + #region DP Transition Animation + + /// + /// Gets or sets the transition animation. + /// + /// The transition animation. + 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... + /// + /// The transition animation property + /// + public static readonly DependencyProperty TransitionAnimationProperty = + DependencyProperty.Register("TransitionAnimation", typeof(DoubleAnimation), typeof(TransitionFrame), new UIPropertyMetadata(null)); + + #endregion DP Transition Animation + + /// + /// Called when the template generation for the visual tree is created. + /// + 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(); + } + + /// + /// Animates the content. + /// + /// The navigation action. + /// if set to true [check content]. + /// if set to true [is back]. + 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(); + } + + /// + /// Navigates the with transition. + /// + /// The page. + public void NavigateWithTransition(Page page) + { + AnimateContent(() => Navigate(page)); + } + + /// + /// Navigates the with transition. + /// + /// The page. + public void NavigateWithTransition(Uri page) + { + AnimateContent(() => Navigate(page)); + } + + /// + /// Goes the back with transition. + /// + public void GoBackWithTransition() + { + if (CanGoBack) + { + AnimateContent(GoBack, false, true); + } + } + + /// + /// Goes the forward with transition. + /// + public void GoForwardWithTransition() + { + if (CanGoForward) + { + AnimateContent(GoForward, false); + } + } + + /// + /// Reverses the direction. + /// + /// The transition effect. + 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 +{ + /// + /// Helper methods for UI-related tasks. + /// + public static class TreeHelper + { + /// + /// Gets the window. + /// + /// The element. + /// Window. + /// The window. + public static Window GetWindow(this FrameworkElement element) + { + return element.ParentOfType(); + } + + /// + /// Gets the parent. + /// + /// The element. + /// DependencyObject. + 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; + } + + /// + /// Gets the parents. + /// + /// The element. + /// IEnumerable{DependencyObject}. + /// element + public static IEnumerable GetParents(this DependencyObject element) + { + if (element == null) + { + throw new ArgumentNullException("element"); + } + while ((element = element.GetParent()) != null) + { + yield return element; + } + yield break; + } + + /// + /// Parents the type of the of. + /// + /// + /// The element. + /// ``0. + public static T ParentOfType(this DependencyObject element) where T : DependencyObject + { + if (element == null) + { + return default(T); + } + return element.GetParents().OfType().FirstOrDefault(); + } + + /// + /// Finds a Child of a given item in the visual tree. + /// + /// The type of the queried item. + /// A direct parent of the queried item. + /// x:Name or Name of child. + /// The first parent item that matches the submitted type parameter. + /// If not matching item can be found, + /// a null parent is being returned. + public static T FindChild(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(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; + } + + /// + /// Gets the visual child. + /// + /// + /// The reference visual. + /// ``0. + public static T GetVisualChild(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(child); + if (child != null && (child.GetType() == typeof(T))) + { + break; + } + } + } + return child as T; + } + + #region find parent + + /// + /// Finds a parent of a given item on the visual tree. + /// + /// The type of the queried item. + /// A direct or indirect child of the + /// queried item. + /// The first parent item that matches the submitted + /// type parameter. If not matching item can be found, a null + /// reference is being returned. + public static T TryFindParent(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(parentObject); + } + + /// + /// This method is an alternative to WPF's + /// method, which also + /// supports content elements. Keep in mind that for content element, + /// this method falls back to the logical tree of the element! + /// + /// The item to be processed. + /// The submitted item's parent, if available. Otherwise + /// null. + 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 + + /// + /// Analyzes both visual and logical tree in order to find all elements of a given + /// type that are descendants of the item. + /// + /// The type of the queried items. + /// 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. + /// All descendants of that match the requested type. + public static IEnumerable FindChildren(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(child)) + { + yield return descendant; + } + } + } + } + + + /// + /// This method is an alternative to WPF's + /// method, which also + /// supports content elements. Keep in mind that for content elements, + /// this method falls back to the logical tree of the element. + /// + /// The item to be processed. + /// The submitted item's child elements, if available. + public static IEnumerable 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 + + /// + /// Tries to locate a given item within the visual tree, + /// starting with the dependency object at a given position. + /// + /// The type of the element to be found + /// on the visual tree of the element at the given location. + /// The main element which is used to perform + /// hit testing. + /// The position to be evaluated on the origin. + /// ``0. + public static T TryFindFromPoint(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(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 +{ + /// + /// 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. + /// + [DefaultProperty("Orientation")] + public class VirtualizingWrapPanel : VirtualizingPanel, IScrollInfo + { + /// + /// Identifies the ItemHeight dependency property. + /// + public static readonly DependencyProperty ItemHeightProperty = DependencyProperty.Register("ItemHeight", typeof(double), typeof(VirtualizingWrapPanel), new PropertyMetadata(100.0, new PropertyChangedCallback(VirtualizingWrapPanel.OnAppearancePropertyChanged))); + /// + /// Identifies the Orientation dependency property. + /// + public static readonly DependencyProperty OrientationProperty = DependencyProperty.Register("Orientation", typeof(Orientation), typeof(VirtualizingWrapPanel), new PropertyMetadata(Orientation.Horizontal, new PropertyChangedCallback(VirtualizingWrapPanel.OnAppearancePropertyChanged))); + /// + /// Identifies the ItemWidth dependency property. + /// + public static readonly DependencyProperty ItemWidthProperty = DependencyProperty.Register("ItemWidth", typeof(double), typeof(VirtualizingWrapPanel), new PropertyMetadata(100.0, new PropertyChangedCallback(VirtualizingWrapPanel.OnAppearancePropertyChanged))); + /// + /// Identifies the ScrollStep dependency property. + /// + 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; + /// + /// Gets or sets a value that specifies the height of all items that are + /// contained within a VirtualizingWrapPanel. This is a dependency property. + /// + public double ItemHeight + { + get + { + return (double)base.GetValue(VirtualizingWrapPanel.ItemHeightProperty); + } + set + { + base.SetValue(VirtualizingWrapPanel.ItemHeightProperty, value); + } + } + /// + /// Gets or sets a value that specifies the width of all items that are + /// contained within a VirtualizingWrapPanel. This is a dependency property. + /// + public double ItemWidth + { + get + { + return (double)base.GetValue(VirtualizingWrapPanel.ItemWidthProperty); + } + set + { + base.SetValue(VirtualizingWrapPanel.ItemWidthProperty, value); + } + } + /// + /// Gets or sets a value that specifies the dimension in which child + /// content is arranged. This is a dependency property. + /// + public Orientation Orientation + { + get + { + return (Orientation)base.GetValue(VirtualizingWrapPanel.OrientationProperty); + } + set + { + base.SetValue(VirtualizingWrapPanel.OrientationProperty, value); + } + } + /// + /// Gets or sets a value that indicates whether scrolling on the horizontal axis is possible. + /// + public bool CanHorizontallyScroll + { + get + { + return this.canHorizontallyScroll; + } + set + { + if (this.canHorizontallyScroll != value) + { + this.canHorizontallyScroll = value; + base.InvalidateMeasure(); + } + } + } + /// + /// Gets or sets a value that indicates whether scrolling on the vertical axis is possible. + /// + public bool CanVerticallyScroll + { + get + { + return this.canVerticallyScroll; + } + set + { + if (this.canVerticallyScroll != value) + { + this.canVerticallyScroll = value; + base.InvalidateMeasure(); + } + } + } + /// + /// Gets or sets a ScrollViewer element that controls scrolling behavior. + /// + public ScrollViewer ScrollOwner + { + get + { + return this.scrollOwner; + } + set + { + this.scrollOwner = value; + } + } + /// + /// Gets the vertical offset of the scrolled content. + /// + public double VerticalOffset + { + get + { + return this.contentOffset.Y; + } + } + /// + /// Gets the vertical size of the viewport for this content. + /// + public double ViewportHeight + { + get + { + return this.viewport.Height; + } + } + /// + /// Gets the horizontal size of the viewport for this content. + /// + public double ViewportWidth + { + get + { + return this.viewport.Width; + } + } + /// + /// Gets or sets a value for mouse wheel scroll step. + /// + public double ScrollStep + { + get + { + return (double)base.GetValue(VirtualizingWrapPanel.ScrollStepProperty); + } + set + { + base.SetValue(VirtualizingWrapPanel.ScrollStepProperty, value); + } + } + /// + /// Gets the vertical size of the extent. + /// + public double ExtentHeight + { + get + { + return this.contentExtent.Height; + } + } + /// + /// Gets the horizontal size of the extent. + /// + public double ExtentWidth + { + get + { + return this.contentExtent.Width; + } + } + /// + /// Gets the horizontal offset of the scrolled content. + /// + public double HorizontalOffset + { + get + { + return this.contentOffset.X; + } + } + /// + /// Scrolls down within content by one logical unit. + /// + public void LineDown() + { + this.SetVerticalOffset(this.VerticalOffset + this.ScrollStep); + } + /// + /// Scrolls left within content by one logical unit. + /// + public void LineLeft() + { + this.SetHorizontalOffset(this.HorizontalOffset - this.ScrollStep); + } + /// + /// Scrolls right within content by one logical unit. + /// + public void LineRight() + { + this.SetHorizontalOffset(this.HorizontalOffset + this.ScrollStep); + } + /// + /// Scrolls up within content by one logical unit. + /// + public void LineUp() + { + this.SetVerticalOffset(this.VerticalOffset - this.ScrollStep); + } + /// + /// Forces content to scroll until the coordinate space of a Visual object is visible. + /// + public Rect MakeVisible(Visual visual, Rect rectangle) + { + this.MakeVisible(visual as UIElement); + return rectangle; + } + /// + /// Scrolls down within content after a user clicks the wheel button on a mouse. + /// + public void MouseWheelDown() + { + this.SetVerticalOffset(this.VerticalOffset + this.ScrollStep); + } + /// + /// Scrolls left within content after a user clicks the wheel button on a mouse. + /// + public void MouseWheelLeft() + { + this.SetHorizontalOffset(this.HorizontalOffset - this.ScrollStep); + } + /// + /// Scrolls right within content after a user clicks the wheel button on a mouse. + /// + public void MouseWheelRight() + { + this.SetHorizontalOffset(this.HorizontalOffset + this.ScrollStep); + } + /// + /// Scrolls up within content after a user clicks the wheel button on a mouse. + /// + public void MouseWheelUp() + { + this.SetVerticalOffset(this.VerticalOffset - this.ScrollStep); + } + /// + /// Scrolls down within content by one page. + /// + public void PageDown() + { + this.SetVerticalOffset(this.VerticalOffset + this.ViewportHeight); + } + /// + /// Scrolls left within content by one page. + /// + public void PageLeft() + { + this.SetHorizontalOffset(this.HorizontalOffset - this.ViewportHeight); + } + /// + /// Scrolls right within content by one page. + /// + public void PageRight() + { + this.SetHorizontalOffset(this.HorizontalOffset + this.ViewportHeight); + } + /// + /// Scrolls up within content by one page. + /// + public void PageUp() + { + this.SetVerticalOffset(this.VerticalOffset - this.viewport.Height); + } + /// + /// Sets the amount of vertical offset. + /// + 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(); + } + /// + /// Sets the amount of horizontal offset. + /// + 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(); + } + /// + /// Note: Works only for vertical. + /// + internal void PageLast() + { + this.contentOffset.Y = this.ExtentHeight; + if (this.ScrollOwner != null) + { + this.ScrollOwner.InvalidateScrollInfo(); + } + base.InvalidateMeasure(); + } + /// + /// Note: Works only for vertical. + /// + internal void PageFirst() + { + this.contentOffset.Y = 0.0; + if (this.ScrollOwner != null) + { + this.ScrollOwner.InvalidateScrollInfo(); + } + base.InvalidateMeasure(); + } + /// + /// When items are removed, remove the corresponding UI if necessary. + /// + /// + /// + 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; + } + } + /// + /// Measure the children. + /// + /// The available size. + /// The desired size. + 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); + } + + /// + /// Arranges the children. + /// + /// The available size. + /// The used size. + 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(); + } + ScrollViewer scrollViewer = element.ParentOfType(); + 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 -- cgit v1.2.3