using Annulus; using Azimuth; using System; using System.Drawing; namespace Glass.Testbed { /// /// A FileDialog over an in-memory file system, to open and save through. Saving puts a file in the file system, /// and saving over a file asks first. The dialog's own buttons make new folders. /// public class FileDialogTestCase : TestCase { private const int StageHeight = 420; private const int PendingBytes = 100; private static readonly Vector DialogSize = new Vector(520, 400); private static readonly TextStyle TextStyle = Widgets.MenuLabelStyle; private static readonly TextStyle ButtonTextStyle = Widgets.MenuLabelStyle with { HorizontalAlignment = Alignment.Horizontal.Center }; private static readonly WindowMaterial WindowLook = new WindowMaterial { Background = UiPacksheetMaterials.Panel, TitleBar = UiPacksheetMaterials.BlueFlatPanel, Content = Material.Invisible, CloseButton = Widgets.CloseButton, }; private static readonly ButtonMaterial ArrowButton = new ButtonMaterial(UiPacksheetMaterials.NubDown with { Scale = 2, }); private static readonly DropdownMaterial DropdownLook = new DropdownMaterial { Background = UiPacksheetMaterials.TextField, ArrowButton = ArrowButton, ArrowGap = Margin, Popup = Widgets.PopupList, }; private static readonly FileFilter[] Filters = { new FileFilter("Text files", new[] { ".txt" }), new FileFilter("Markdown files", new[] { ".md" }), FileFilter.AllFiles, }; private static readonly string[] SuggestedNames = { "notes.txt", "readme.md", "untitled.txt" }; private static readonly FileDialogMaterial DialogLook = new FileDialogMaterial { Window = WindowLook, UpButton = ButtonFacing("Up"), NewFolderButton = ButtonFacing("New folder"), AcceptButton = ButtonFacing("OK"), CancelButton = ButtonFacing("Cancel"), EntryList = Widgets.PopupListFrame, EntryRow = Widgets.ListItemButton, SelectedEntryRow = Widgets.CurrentListItemButton, Dropdown = DropdownLook, OverwriteBox = new MessageBoxMaterial { Background = UiPacksheetMaterials.Panel, TextStyle = TextStyle, Spacing = Margin, } .WithButton(FileDialog.OverwriteAnswer.Replace, ButtonFacing("Replace")) .WithButton(FileDialog.OverwriteAnswer.Cancel, ButtonFacing("Cancel")), OverwriteQuestionFormat = "'{0}' already exists. Replace it?", TextStyle = TextStyle, Spacing = Margin, MinimumEntryRows = 6, }; private InMemoryFileSystem files = SampleFiles(); private FileDialog dialog; private Label resultLabel; private int savedCount; public FileDialogTestCase(string name) : base(name) { } private static ButtonMaterial ButtonFacing(string text) { return UiPacksheetMaterials.Button.Framing(new TextMaterial { Text = text, Style = ButtonTextStyle }); } private static InMemoryFileSystem SampleFiles() { var returnMe = new InMemoryFileSystem(); returnMe.AddFile("/docs/readme.md", size: 1200); returnMe.AddFile("/docs/notes.txt", size: 340); returnMe.AddFile("/docs/drafts/chapter one.txt", size: 8100); returnMe.AddFile("/docs/drafts/chapter two.txt", size: 7600); returnMe.AddDirectory("/empty folder"); returnMe.AddFile("/music/song.txt", size: 90); returnMe.AddFile("/todo.txt", size: 56); returnMe.AddFile("/notes.txt", size: 12); return returnMe; } private static Control BuildEntry(FileEntry entry) { string text = entry.IsDirectory ? entry.Name + "/" : $"{entry.Name} ({entry.Size} bytes)"; return new Label(TextStyle, text, Constraints.Unbounded); } private static Button NewButton(string text) { return new Label(Widgets.ButtonLabelStyle, text, Constraints.Unbounded) .WrapIn(UiPacksheetMaterials.Button, ButtonBehavior.Default); } private void ShowResult(string text) { resultLabel.Text = text; } private void RemoveDialog() { if (dialog != null) { Root.CanvasChildren.Remove(dialog); dialog = null; } } private void ShowDialog(string title, FileDialogMode mode, Rect stage) { RemoveDialog(); // The testbed's own file system always lists its root. FileDialogModel model = FileDialogModel.Create(files, mode, Filters).Value; var shown = new FileDialog(model, BuildEntry, DialogBehavior.Default); shown.FileDialogMaterial = DialogLook; shown.ResizeCursors = ResizeCursorMaterials.ByZone; shown.Caption.Material = Material.Invisible; shown.Bounds = Rect.FromPositionAndSize( stage.TopLeft + new Vector(20, 10), Vector.Max(DialogSize, shown.EffectiveMinSize)); var titleLabel = new Label(Widgets.WindowTitleStyle, title) { DebugName = "title", Bounds = shown.Caption.CanvasRect, }; shown.Caption.CanvasChildren.Add(titleLabel); shown.OnResized += _ => titleLabel.Bounds = shown.Caption.CanvasRect; new Label(Widgets.CloseButtonLabelStyle, "x").FillParent(shown.CloseButton); if (mode == FileDialogMode.Save) { shown.SuggestedNames = SuggestedNames; } model.OnAccepted += path => Accepted(mode, path); model.OnCancelled += () => ShowResult($"{title}: cancelled"); dialog = shown; Add(shown); } private void Accepted(FileDialogMode mode, string path) { if (mode == FileDialogMode.Save) { ++savedCount; files.AddFile(path, size: PendingBytes + savedCount); ShowResult($"Saved {path}"); } else { ShowResult($"Opened {path}"); } } protected override void Populate() { AddCaption("Open picks an existing file. Save picks a name, with suggestions, and asks before replacing."); AddCaption("Double-click a folder to go in, use Up to come out, and New folder to make one."); Button open = NewButton("Open a file..."); Button save = NewButton("Save a file..."); Button reset = NewButton("Reset files"); Rect buttonRow = ContentAggregator.NewRow(open.Bounds.Height); foreach (Button button in new[] { open, save, reset }) { Rect slot = buttonRow.NewCol(button.Bounds.Width, margin: Gutter); button.Bounds = button.Bounds.WithPosition(slot.TopLeft); Add(button); } resultLabel = new Label(Widgets.CaptionStyle, "Nothing chosen yet"); resultLabel.Bounds = ContentAggregator.NewRow(resultLabel.Bounds.Height); Add(resultLabel); Rect stageArea = ContentAggregator.NewRow(StageHeight); open.OnActivate += delegate (ButtonBase _) { ShowDialog("Open file", FileDialogMode.Open, stageArea); }; save.OnActivate += delegate (ButtonBase _) { ShowDialog("Save file", FileDialogMode.Save, stageArea); }; reset.OnActivate += delegate (ButtonBase _) { RemoveDialog(); files = SampleFiles(); savedCount = 0; ShowResult("Files reset"); }; } protected override void Teardown() { RemoveDialog(); } } }