A game framework written with osu! in mind.
at master 176 lines 6.4 kB view raw
1// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. 2// See the LICENCE file in the repository root for full licence text. 3 4using System; 5using System.Collections.Generic; 6using System.IO; 7using System.Linq; 8using osu.Framework.Allocation; 9using osu.Framework.Bindables; 10using osu.Framework.Extensions.EnumExtensions; 11using osu.Framework.Graphics.Containers; 12using osuTK; 13 14namespace osu.Framework.Graphics.UserInterface 15{ 16 /// <summary> 17 /// A component which allows a user to select a directory. 18 /// </summary> 19 public abstract class DirectorySelector : CompositeDrawable 20 { 21 private FillFlowContainer directoryFlow; 22 23 protected abstract ScrollContainer<Drawable> CreateScrollContainer(); 24 25 /// <summary> 26 /// Create the breadcrumb part of the control. 27 /// </summary> 28 protected abstract DirectorySelectorBreadcrumbDisplay CreateBreadcrumb(); 29 30 protected abstract DirectorySelectorDirectory CreateDirectoryItem(DirectoryInfo directory, string displayName = null); 31 32 /// <summary> 33 /// Create the directory item that resolves the parent directory. 34 /// </summary> 35 protected abstract DirectorySelectorDirectory CreateParentDirectoryItem(DirectoryInfo directory); 36 37 [Cached] 38 public readonly Bindable<DirectoryInfo> CurrentPath = new Bindable<DirectoryInfo>(); 39 40 protected DirectorySelector(string initialPath = null) 41 { 42 CurrentPath.Value = new DirectoryInfo(initialPath ?? Environment.GetFolderPath(Environment.SpecialFolder.UserProfile)); 43 } 44 45 [BackgroundDependencyLoader] 46 private void load() 47 { 48 InternalChild = new GridContainer 49 { 50 RelativeSizeAxes = Axes.Both, 51 RowDimensions = new[] 52 { 53 new Dimension(GridSizeMode.AutoSize), 54 new Dimension(), 55 }, 56 Content = new[] 57 { 58 new Drawable[] 59 { 60 CreateBreadcrumb() 61 }, 62 new Drawable[] 63 { 64 CreateScrollContainer().With(d => 65 { 66 d.RelativeSizeAxes = Axes.Both; 67 d.Child = directoryFlow = new FillFlowContainer 68 { 69 AutoSizeAxes = Axes.Y, 70 RelativeSizeAxes = Axes.X, 71 Direction = FillDirection.Vertical, 72 Spacing = new Vector2(2), 73 }; 74 }) 75 } 76 } 77 }; 78 79 CurrentPath.BindValueChanged(updateDisplay, true); 80 } 81 82 /// <summary> 83 /// Because <see cref="CurrentPath"/> changes may not necessarily lead to directories that exist/are accessible, 84 /// <see cref="updateDisplay"/> may need to change <see cref="CurrentPath"/> again to lead to a directory that is actually accessible. 85 /// This flag intends to prevent recursive <see cref="updateDisplay"/> calls from taking place during the process of finding an accessible directory. 86 /// </summary> 87 private bool directoryChanging; 88 89 private void updateDisplay(ValueChangedEvent<DirectoryInfo> directory) 90 { 91 if (directoryChanging) 92 return; 93 94 try 95 { 96 directoryChanging = true; 97 98 directoryFlow.Clear(); 99 100 var newDirectory = directory.NewValue; 101 bool notifyError = false; 102 ICollection<DirectorySelectorItem> items = Array.Empty<DirectorySelectorItem>(); 103 104 while (newDirectory != null) 105 { 106 newDirectory.Refresh(); 107 108 if (TryGetEntriesForPath(newDirectory, out items)) 109 break; 110 111 notifyError = true; 112 newDirectory = newDirectory.Parent; 113 } 114 115 if (notifyError) 116 NotifySelectionError(); 117 118 if (newDirectory == null) 119 { 120 var drives = DriveInfo.GetDrives(); 121 122 foreach (var drive in drives) 123 directoryFlow.Add(CreateDirectoryItem(drive.RootDirectory)); 124 125 return; 126 } 127 128 CurrentPath.Value = newDirectory; 129 130 directoryFlow.Add(CreateParentDirectoryItem(newDirectory.Parent)); 131 directoryFlow.AddRange(items); 132 } 133 finally 134 { 135 directoryChanging = false; 136 } 137 } 138 139 /// <summary> 140 /// Attempts to create entries to display for the given <paramref name="path"/>. 141 /// A return value of <see langword="false"/> is used to indicate a non-specific I/O failure, signaling to the selector that it should attempt 142 /// to find another directory to display (since <paramref name="path"/> is inaccessible). 143 /// </summary> 144 /// <param name="path">The directory to create entries for.</param> 145 /// <param name="items"> 146 /// The created <see cref="DirectorySelectorItem"/>s, provided that the <paramref name="path"/> could be entered. 147 /// Not valid for reading if the return value of the method is <see langword="false"/>. 148 /// </param> 149 protected virtual bool TryGetEntriesForPath(DirectoryInfo path, out ICollection<DirectorySelectorItem> items) 150 { 151 items = new List<DirectorySelectorItem>(); 152 153 try 154 { 155 foreach (var dir in path.GetDirectories().OrderBy(d => d.Name)) 156 { 157 if (!dir.Attributes.HasFlagFast(FileAttributes.Hidden)) 158 items.Add(CreateDirectoryItem(dir)); 159 } 160 161 return true; 162 } 163 catch 164 { 165 return false; 166 } 167 } 168 169 /// <summary> 170 /// Called when an error has occured. Usually happens when trying to access protected directories. 171 /// </summary> 172 protected virtual void NotifySelectionError() 173 { 174 } 175 } 176}