From 641eca2ef47995b1402fb1c184e4feb800e3cb42 Mon Sep 17 00:00:00 2001 From: Ronen Date: Sat, 30 Sep 2023 00:56:26 +0300 Subject: [PATCH] fixed folder selection in file dialog and added same-value-selected event to list --- GeonBit.UI.Examples/GeonBitUI_Examples.cs | 33 +++++++++--- GeonBit.UI.nuspec | 2 +- GeonBit.UI/Source/Entities/SelectList.cs | 57 ++++++++++++++++++--- GeonBit.UI/Source/Utils/MessageBox.cs | 61 ++++++++++++++++++++--- README.md | 1 + 5 files changed, 130 insertions(+), 24 deletions(-) diff --git a/GeonBit.UI.Examples/GeonBitUI_Examples.cs b/GeonBit.UI.Examples/GeonBitUI_Examples.cs index 9597a9b..8d27205 100644 --- a/GeonBit.UI.Examples/GeonBitUI_Examples.cs +++ b/GeonBit.UI.Examples/GeonBitUI_Examples.cs @@ -1083,16 +1083,33 @@ Note that in order to use clipping and scrollbar with Panels you need to set the panel.AddChild(new HorizontalLine()); panel.AddChild(new Paragraph("GeonBit.UI also provide file dialogs. For example, save file dialog: \n")); - // add create form button - var btn = panel.AddChild(new Button(@"Open Save File Dialog")); - btn.OnClick += (Entity ent) => + // add save file button { - Utils.MessageBox.OpenSaveFileDialog("", (Utils.FileDialogResponse res) => + var btn = panel.AddChild(new Button(@"Open Save File Dialog")); + btn.OnClick += (Entity ent) => { - Utils.MessageBox.ShowMsgBox("File Selected!", $"Selected file: '{res.FullPath}'.\n\nIn this example we just show a message box, in a real project we would use this path to save the file."); - return true; - }, message: "It won't actually save anything so don't worry about picking existing files!"); - }; + Utils.MessageBox.OpenSaveFileDialog("", (Utils.FileDialogResponse res) => + { + Utils.MessageBox.ShowMsgBox("File Selected!", $"Selected file: '{res.FullPath}'.\n\nIn this example we just show a message box, in a real project we would use this path to save the file."); + return true; + }, message: "It won't actually save anything so don't worry about picking existing files."); + }; + } + + panel.AddChild(new Paragraph("And click below to open load file dialog: \n")); + + // add load file button + { + var btn = panel.AddChild(new Button(@"Open Load File Dialog")); + btn.OnClick += (Entity ent) => + { + Utils.MessageBox.OpenLoadFileDialog("", (Utils.FileDialogResponse res) => + { + Utils.MessageBox.ShowMsgBox("File Selected!", $"Selected file: '{res.FullPath}'.\n\nIn this example we just show a message box, in a real project we would use this path to load the file."); + return true; + }, message: "It won't actually load anything so don't worry about picking any file."); + }; + } } // example: top menu diff --git a/GeonBit.UI.nuspec b/GeonBit.UI.nuspec index c4a0bf6..1f7bfa3 100644 --- a/GeonBit.UI.nuspec +++ b/GeonBit.UI.nuspec @@ -12,7 +12,7 @@ It provide all the basic elements required to make a game / editor UI, and comes https://github.com/RonenNess/GeonBit.UI https://raw.githubusercontent.com/RonenNess/GeonBit.UI/master/assets/img/nuget_icon.png false - Added features and small refactoring. + Added file dialogs, icons to lists, and did minor refactoring. UI system for MonoGame projects. monogame ui gui hud user-interface gamedev diff --git a/GeonBit.UI/Source/Entities/SelectList.cs b/GeonBit.UI/Source/Entities/SelectList.cs index b01e167..0c7a52b 100644 --- a/GeonBit.UI/Source/Entities/SelectList.cs +++ b/GeonBit.UI/Source/Entities/SelectList.cs @@ -11,6 +11,7 @@ using Microsoft.Xna.Framework.Graphics; using System.Collections.Generic; using GeonBit.UI.Utils; +using System; namespace GeonBit.UI.Entities { @@ -62,10 +63,14 @@ static SelectList() /// Scale items in list. public float ItemsScale = 1f; - /// Special callback to execute when list size changes. + /// Invoked when list size changes. [System.Xml.Serialization.XmlIgnore] public EventCallback OnListChange = null; + /// Invoked when the user select the same value in the list again. + [System.Xml.Serialization.XmlIgnore] + public EventCallback OnSameValueSelected = null; + /// /// If icons are set, this factor will scale them. /// @@ -253,7 +258,7 @@ public void ClearIcons() /// /// Icon texture path, under theme folder. Set to null to remove icons. /// Item index to attach icon to. - public void SetIcon(string? texturePath, int index) + public void SetIcon(string texturePath, int index) { if (texturePath == null) { @@ -270,7 +275,7 @@ public void SetIcon(string? texturePath, int index) /// /// Icon texture path, under theme folder. Set to null to remove icons. /// Item text to attach icon to. - public void SetIcon(string? texturePath, string itemText) + public void SetIcon(string texturePath, string itemText) { var index = 0; foreach (var item in _valuesList) @@ -614,7 +619,19 @@ public bool HasSelectedValue protected void Select(string value) { // value not changed? skip - if (!AllowReselectValue && value == _value) { return; } + if (!AllowReselectValue && value == _value) + { + // invoke select same value event + if ((value == _value) && (value != null)) + { + OnSameValueSelected?.Invoke(this); + } + // stop here + return; + } + + // store previous value + var prevValue = _value; // special case - value is null if (value == null) @@ -630,7 +647,7 @@ protected void Select(string value) if (_index == -1) { _value = null; - if (UserInterface.Active.SilentSoftErrors) return; + if (UserInterface.Active.SilentSoftErrors) { return; } throw new Exceptions.NotFoundException("Value to set not found in list!"); } @@ -639,6 +656,12 @@ protected void Select(string value) // call on-value-change event DoOnValueChange(); + + // trigger same-value selected event + if ((value == prevValue) && (value != null)) + { + OnSameValueSelected?.Invoke(this); + } } /// @@ -678,13 +701,25 @@ protected void Select(int index, bool relativeToScrollbar = false) index += _scrollbar.Value; } + // store previous index. we use it to test same-selection. + var prevIndex = _index; + // index not changed? skip - if (!AllowReselectValue && index == _index) { return; } + if (!AllowReselectValue && index == _index) + { + // invoke select same value event + if (index == prevIndex) + { + OnSameValueSelected?.Invoke(this); + } + // stop here + return; + } // make sure legal index - if (index >= -1 && index >= _valuesList.Count) + if ((index >= -1) && (index >= _valuesList.Count)) { - if (UserInterface.Active.SilentSoftErrors) return; + if (UserInterface.Active.SilentSoftErrors) { return; } throw new Exceptions.NotFoundException("Invalid list index to select!"); } @@ -694,6 +729,12 @@ protected void Select(int index, bool relativeToScrollbar = false) // call on-value-change event DoOnValueChange(); + + // invoke select same value event + if (index == prevIndex) + { + OnSameValueSelected?.Invoke(this); + } } /// diff --git a/GeonBit.UI/Source/Utils/MessageBox.cs b/GeonBit.UI/Source/Utils/MessageBox.cs index 45dac0d..4c87a6c 100644 --- a/GeonBit.UI/Source/Utils/MessageBox.cs +++ b/GeonBit.UI/Source/Utils/MessageBox.cs @@ -299,6 +299,12 @@ public static MessageBoxHandle OpenSaveFileDialog(string path, Func + filesList.OnSameValueSelected = (Entity entity) => { // on second click enter folder - if ((filesList.SelectedIndex == prevSelectedIndex) && (filesList.SelectedValue != null)) + if (filesList.SelectedValue != null) { // previous path to check if changed path var prevPath = currPath; @@ -386,7 +397,6 @@ void UpdateFilesList() if (prevPath != currPath) { UpdateFilesList(); - prevSelectedIndex = -1; } } }; @@ -400,9 +410,6 @@ void UpdateFilesList() { filenameInput.Value = filesList.SelectedValue; } - - // to detect double click - prevSelectedIndex = filesList.SelectedIndex; }; // return relative and full selected path @@ -472,14 +479,24 @@ void UpdateFilesList() var saveBtn = handle.Buttons[0]; saveBtn.BeforeDraw = (Entity entity) => { + // check if got any file name saveBtn.Enabled = !string.IsNullOrEmpty(filenameInput.Value); + + // if got filename, do advance checks if (saveBtn.Enabled) { + // get full path and check if override file var paths = GetSelectedAndFullPath(); if (!options.HasFlag(FileDialogOptions.AllowOverride) && File.Exists(paths.Item2)) { saveBtn.Enabled = false; } + + // check if a folder is selected + if (!options.HasFlag(FileDialogOptions.CanPickFolders) && Directory.Exists(paths.Item2)) + { + saveBtn.Enabled = false; + } } }; @@ -489,6 +506,26 @@ void UpdateFilesList() // return the handle return handle; } + + /// + /// Open a dialog to select file for loading. + /// + /// Path to start dialog in. + /// Callback to trigger when a file was selected. Return true to close dialog, false to keep it opened. + /// Callback to trigger when the user hit cancel. + /// File dialog flags. + /// Optional method to filter file names. Return false to hide files. + /// Optional method to filter folder names. Return false to hide folders. + /// File dialog title. + /// Optional message to show above files. + /// String to show on the load file button. + /// String to show on the cancel button. + /// Message box handle. + public static MessageBoxHandle OpenLoadFileDialog(string path, Func onSelected, Action onCancel = null!, FileDialogOptions options = FileDialogOptions.Default, Func filterFiles = null!, Func filterFolders = null!, string title = "Open File..", string message = null!, string loadButtonTxt = "Open File", string cancelButtonTxt = "Cancel") + { + options |= FileDialogOptions.MustSelectExistingFile; + return OpenSaveFileDialog(path, onSelected, onCancel, options, filterFiles, filterFolders, title, message, loadButtonTxt, cancelButtonTxt, null); + } } /// @@ -543,6 +580,16 @@ public enum FileDialogOptions /// ShowDirectoryPath = 1 << 4, + /// + /// Will only allow picking up existing files. + /// + MustSelectExistingFile = 1 << 5, + + /// + /// Can select folders and not just files. + /// + CanPickFolders = 1 << 6, + /// /// Default file dialog options. /// diff --git a/README.md b/README.md index 7640b04..43f795e 100644 --- a/README.md +++ b/README.md @@ -2104,6 +2104,7 @@ For older MonoGame versions, see [tag 2.1.0.0](https://github.com/RonenNess/Geon - Added built-in files dialog (save / load file). - Renamed and rearranged text validators + added file name validator. - Added icons support to Lists and Drop Down entities. +- Added 'OnSameValueSelected' event to lists. IMPORTANT MIGRATION NOTICE! If you want to use the new files dialog, you must include the new textures that were added to theme: `textures/file_icon.png` and `textures/folder_icon.png`.