From 9fcdbc3637d8d0932a48ee85560034b99c55aa12 Mon Sep 17 00:00:00 2001 From: onepiecefreak3 <realonepiecefreak@gmx.de> Date: Fri, 11 Oct 2024 21:06:50 +0200 Subject: [PATCH] Add font property to various controls; Optimize most components to reflect new wiki; Bump to 1.2.0; --- ImGui.Forms/Controls/ArrowButton.cs | 11 +- ImGui.Forms/Controls/Base/Component.cs | 24 +- ImGui.Forms/Controls/Button.cs | 7 +- ImGui.Forms/Controls/CheckBox.cs | 32 +- ImGui.Forms/Controls/ComboBox.cs | 44 ++- ImGui.Forms/Controls/DropDownBox.cs | 84 ------ ImGui.Forms/Controls/Expander.cs | 11 +- ImGui.Forms/Controls/ImageButton.cs | 2 +- ImGui.Forms/Controls/Label.cs | 2 +- ImGui.Forms/Controls/Layouts/StackLayout.cs | 8 +- ImGui.Forms/Controls/Layouts/TableCell.cs | 8 +- ImGui.Forms/Controls/Layouts/TableLayout.cs | 32 +- ImGui.Forms/Controls/Layouts/ZLayout.cs | 4 +- ImGui.Forms/Controls/Lists/ActivableList.cs | 9 +- ImGui.Forms/Controls/Lists/BaseList.cs | 182 ------------ ImGui.Forms/Controls/Lists/DataTable.cs | 17 +- ImGui.Forms/Controls/Lists/ImageList.cs | 188 ------------ ImGui.Forms/Controls/Lists/ImageListItem.cs | 24 -- ImGui.Forms/Controls/Lists/List.cs | 276 +++++++++++++++++- ImGui.Forms/Controls/Menu/MenuBar.cs | 20 +- ImGui.Forms/Controls/Menu/MenuBarButton.cs | 24 +- ImGui.Forms/Controls/Menu/MenuBarCheckBox.cs | 22 +- ImGui.Forms/Controls/Menu/MenuBarMenu.cs | 33 ++- ImGui.Forms/Controls/Menu/MenuBarRadio.cs | 18 +- ImGui.Forms/Controls/MultiLineTextBox.cs | 7 +- ImGui.Forms/Controls/PictureBox.cs | 17 +- ImGui.Forms/Controls/ProgressBar.cs | 5 +- ImGui.Forms/Controls/TabControl.cs | 4 +- ImGui.Forms/Controls/TextBox.cs | 10 +- ImGui.Forms/Controls/Tree/TreeNode.cs | 2 +- ImGui.Forms/Controls/ZoomablePictureBox.cs | 7 +- ImGui.Forms/Form.cs | 6 +- ImGui.Forms/ImGui.Forms.nuspec | 2 +- ImGui.Forms/Localization/BaseLocalizer.cs | 6 + ImGui.Forms/Localization/ILocalizer.cs | 1 + ImGui.Forms/Modals/IO/ComboInputBox.cs | 55 ++-- ImGui.Forms/Modals/IO/InputBox.cs | 24 +- ImGui.Forms/Modals/IO/OpenFileDialog.cs | 54 ++-- ImGui.Forms/Modals/IO/SaveFileDialog.cs | 47 +-- ImGui.Forms/Modals/IO/SelectFolderDialog.cs | 26 +- ImGui.Forms/Modals/MessageBox.cs | 46 ++- ImGui.Forms/Modals/Modal.cs | 31 +- .../Resources/LocalizationResources.cs | 63 ++++ ImGui.Forms/Resources/TextMeasurer.cs | 1 - 44 files changed, 728 insertions(+), 768 deletions(-) delete mode 100644 ImGui.Forms/Controls/DropDownBox.cs delete mode 100644 ImGui.Forms/Controls/Lists/BaseList.cs delete mode 100644 ImGui.Forms/Controls/Lists/ImageList.cs delete mode 100644 ImGui.Forms/Controls/Lists/ImageListItem.cs create mode 100644 ImGui.Forms/Resources/LocalizationResources.cs diff --git a/ImGui.Forms/Controls/ArrowButton.cs b/ImGui.Forms/Controls/ArrowButton.cs index f3563b8..e707847 100644 --- a/ImGui.Forms/Controls/ArrowButton.cs +++ b/ImGui.Forms/Controls/ArrowButton.cs @@ -1,4 +1,5 @@ using System; +using System.Numerics; using ImGui.Forms.Controls.Base; using ImGui.Forms.Models; using ImGui.Forms.Models.IO; @@ -18,6 +19,8 @@ public class ArrowButton : Component public ImGuiDir Direction { get; set; } + public Vector2 Padding { get; set; } = new(4, 3); + #endregion #region Events @@ -33,15 +36,15 @@ public ArrowButton(ImGuiDir direction = ImGuiDir.None) public override Size GetSize() { - var padding = ImGuiNET.ImGui.GetStyle().FramePadding; - - return new Size((int)Math.Ceiling(ButtonSizeX_ + padding.X * 2), (int)Math.Ceiling(ButtonSizeY_ + padding.Y * 2)); + return new Size((int)Math.Ceiling(ButtonSizeX_ + Padding.X * 2), (int)Math.Ceiling(ButtonSizeY_ + Padding.Y * 2)); } protected override void UpdateInternal(Rectangle contentRect) { var enabled = Enabled; + ImGuiNET.ImGui.PushStyleVar(ImGuiStyleVar.FramePadding, Padding); + if (!enabled) { ImGuiNET.ImGui.PushStyleColor(ImGuiCol.Button, ImGuiNET.ImGui.GetColorU32(ImGuiCol.TextDisabled)); @@ -54,6 +57,8 @@ protected override void UpdateInternal(Rectangle contentRect) if (!enabled) ImGuiNET.ImGui.PopStyleColor(3); + + ImGuiNET.ImGui.PopStyleVar(); } private void OnClicked() diff --git a/ImGui.Forms/Controls/Base/Component.cs b/ImGui.Forms/Controls/Base/Component.cs index 54eebcf..65d5497 100644 --- a/ImGui.Forms/Controls/Base/Component.cs +++ b/ImGui.Forms/Controls/Base/Component.cs @@ -9,11 +9,12 @@ namespace ImGui.Forms.Controls.Base { - // TODO: Due to executive inconsistency, remove ApplyStyles/RemoveStyles methods and apply styles in Update/Size methods directly public abstract class Component { private bool _tabInactive; + #region Properties + /// <summary> /// The Id for this component. /// </summary> @@ -40,6 +41,8 @@ public abstract class Component /// </summary> public bool ShowBorder { get; set; } + #endregion + #region Events /// <summary> @@ -119,13 +122,14 @@ protected bool IsTabInactiveCore() /// Gets the finalized width of this component. /// </summary> /// <param name="parentWidth">The width of the parent component.</param> + /// <param name="parentHeight">The height of the parent component.</param> /// <param name="layoutCorrection">A correctional value for layouts.</param> /// <returns>The finalized width of this component.</returns> - public int GetWidth(int parentWidth, float layoutCorrection = 1f) + public int GetWidth(int parentWidth, int parentHeight, float layoutCorrection = 1f) { var width = GetSize().Width; return width.IsContentAligned ? - GetContentWidth(parentWidth, layoutCorrection) : + GetContentWidth(parentWidth, parentHeight, layoutCorrection) : GetDimension(width, parentWidth, layoutCorrection); } @@ -133,37 +137,39 @@ public int GetWidth(int parentWidth, float layoutCorrection = 1f) /// Gets the width of this component, if it's content aligned. /// </summary> /// <param name="parentWidth">The width of the parent component.</param> + /// <param name="parentHeight">The height of the parent component.</param> /// <param name="layoutCorrection">A correctional value for layouts.</param> /// <returns>The content width of this component.</returns> - protected virtual int GetContentWidth(int parentWidth, float layoutCorrection = 1f) => 0; + protected virtual int GetContentWidth(int parentWidth, int parentHeight, float layoutCorrection = 1f) => 0; /// <summary> /// Gets the finalized height of this component. /// </summary> + /// <param name="parentWidth">The width of the parent component.</param> /// <param name="parentHeight">The height of the parent component.</param> /// <param name="layoutCorrection">A correctional value for layouts.</param> /// <returns>The finalized height of this component.</returns> - public int GetHeight(int parentHeight, float layoutCorrection = 1f) + public int GetHeight(int parentWidth, int parentHeight, float layoutCorrection = 1f) { var height = GetSize().Height; return height.IsContentAligned ? - GetContentHeight(parentHeight, layoutCorrection) : + GetContentHeight(parentWidth, parentHeight, layoutCorrection) : GetDimension(height, parentHeight, layoutCorrection); } /// <summary> /// Gets the height of this component, if it's content aligned. /// </summary> + /// <param name="parentWidth">The width of the parent component.</param> /// <param name="parentHeight">The height of the parent component.</param> /// <param name="layoutCorrection">A correctional value for layouts.</param> /// <returns>The content height of this component.</returns> - protected virtual int GetContentHeight(int parentHeight, float layoutCorrection = 1f) => 0; + protected virtual int GetContentHeight(int parentWidth, int parentHeight, float layoutCorrection = 1f) => 0; /// <summary> /// Gets the absolute or relative size of the component. Use <see cref="GetWidth"/> or <see cref="GetHeight"/> to get the finalized size. /// </summary> /// <returns>The absolute or relative size of this component.</returns> - /// TODO: Make gettable property, default value Size.Content public abstract Size GetSize(); /// <summary> @@ -243,7 +249,7 @@ private void OnDragDrop(DragDropEvent[] events) /// <param name="maxDimensionValue">The maximum to calculate relative <see cref="SizeValue"/>s against.</param> /// <param name="correction">The corrective value to calculate relative <see cref="SizeValue"/>s against.</param> /// <returns></returns> - internal static int GetDimension(SizeValue dimensionValue, int maxDimensionValue, float correction = 1f) + protected internal static int GetDimension(SizeValue dimensionValue, int maxDimensionValue, float correction = 1f) { if (dimensionValue.IsAbsolute) return (int)Math.Min(dimensionValue.Value, maxDimensionValue); diff --git a/ImGui.Forms/Controls/Button.cs b/ImGui.Forms/Controls/Button.cs index 074029d..4f9493c 100644 --- a/ImGui.Forms/Controls/Button.cs +++ b/ImGui.Forms/Controls/Button.cs @@ -16,6 +16,7 @@ public class Button : Component public LocalizedString Text { get; set; } public LocalizedString Tooltip { get; set; } + public FontResource Font { get; set; } public KeyCommand KeyAction { get; set; } @@ -23,8 +24,6 @@ public class Button : Component public SizeValue Width { get; set; } = SizeValue.Content; - public FontResource Font { get; set; } - #endregion #region Events @@ -58,7 +57,7 @@ protected override void UpdateInternal(Rectangle contentRect) ApplyStyles(enabled, font); - if ((ImGuiNET.ImGui.Button(EscapeText(), new Vector2(contentRect.Width, contentRect.Height)) || IsKeyDown(KeyAction)) && Enabled) + if ((ImGuiNET.ImGui.Button(EscapeText(), contentRect.Size) || IsKeyDown(KeyAction)) && Enabled) OnClicked(); if (Tooltip is { IsEmpty: false } && IsHoveredCore()) @@ -100,7 +99,7 @@ private void RemoveStyles(bool enabled, FontResource font) protected void OnClicked() { - Clicked?.Invoke(this, new EventArgs()); + Clicked?.Invoke(this, EventArgs.Empty); } protected string EscapeText() diff --git a/ImGui.Forms/Controls/CheckBox.cs b/ImGui.Forms/Controls/CheckBox.cs index 9c26947..5f20530 100644 --- a/ImGui.Forms/Controls/CheckBox.cs +++ b/ImGui.Forms/Controls/CheckBox.cs @@ -15,12 +15,12 @@ public class CheckBox : Component #region Properties public LocalizedString Text { get; set; } - public LocalizedString Tooltip { get; set; } + public FontResource Font { get; set; } public bool Checked { - get=> _checked; + get => _checked; set { _checked = value; @@ -43,19 +43,26 @@ public CheckBox(LocalizedString text = default) public override Size GetSize() { + ApplyStyles(Enabled, Font); + var size = TextMeasurer.MeasureText(Text); - return new Size((int)(Math.Ceiling(size.X) + 21 + ImGuiNET.ImGui.GetStyle().ItemInnerSpacing.X), (int)Math.Max(Math.Ceiling(size.Y), 21)); + var width = (int)(Math.Ceiling(size.X) + 21 + ImGuiNET.ImGui.GetStyle().ItemInnerSpacing.X); + var height = (int)Math.Max(Math.Ceiling(size.Y), 21); + + RemoveStyles(Enabled, Font); + + return new Size(width, height); } protected override void UpdateInternal(Rectangle contentRect) { - // TODO: Draw checkbox manually, when disabled, to work around checkmark flickering ImGuiNET.ImGui.SetNextItemWidth(contentRect.Width); var check = Checked; var enabled = Enabled; + var font = Font; - ApplyStyles(enabled); + ApplyStyles(enabled, font); if (IsHovering(contentRect) && !string.IsNullOrEmpty(Tooltip)) ImGuiNET.ImGui.SetTooltip(Tooltip); @@ -63,10 +70,10 @@ protected override void UpdateInternal(Rectangle contentRect) if (ImGuiNET.ImGui.Checkbox(Text, ref check) && Enabled) Checked = check; - RemoveStyles(enabled); + RemoveStyles(enabled, font); } - private void ApplyStyles(bool enabled) + private void ApplyStyles(bool enabled, FontResource font) { if (!enabled) { @@ -75,10 +82,17 @@ private void ApplyStyles(bool enabled) ImGuiNET.ImGui.PushStyleColor(ImGuiCol.FrameBgActive, ImGuiNET.ImGui.GetColorU32(ImGuiCol.TextDisabled)); ImGuiNET.ImGui.PushStyleColor(ImGuiCol.FrameBgHovered, ImGuiNET.ImGui.GetColorU32(ImGuiCol.TextDisabled)); } + + ImFontPtr? fontPtr = font?.GetPointer(); + if (fontPtr != null) + ImGuiNET.ImGui.PushFont(fontPtr.Value); } - private void RemoveStyles(bool enabled) + private void RemoveStyles(bool enabled, FontResource font) { + if (font?.GetPointer() != null) + ImGuiNET.ImGui.PopFont(); + if (!enabled) ImGuiNET.ImGui.PopStyleColor(4); } @@ -90,7 +104,7 @@ private bool IsHovering(Rectangle contentRect) private void OnCheckChanged() { - CheckChanged?.Invoke(this, new EventArgs()); + CheckChanged?.Invoke(this, EventArgs.Empty); } } } diff --git a/ImGui.Forms/Controls/ComboBox.cs b/ImGui.Forms/Controls/ComboBox.cs index 49db731..f993c40 100644 --- a/ImGui.Forms/Controls/ComboBox.cs +++ b/ImGui.Forms/Controls/ComboBox.cs @@ -4,6 +4,7 @@ using System.Numerics; using System.Runtime.InteropServices; using ImGui.Forms.Controls.Base; +using ImGui.Forms.Localization; using ImGui.Forms.Models; using ImGui.Forms.Resources; using ImGuiNET; @@ -15,13 +16,24 @@ namespace ImGui.Forms.Controls { public class ComboBox<TItem> : Component { + private const int ButtonSizeX_ = 11; + private string _input = string.Empty; + private DropDownItem<TItem> _selected; #region Properties - public IList<ComboBoxItem<TItem>> Items { get; } = new List<ComboBoxItem<TItem>>(); + public IList<DropDownItem<TItem>> Items { get; } = new List<DropDownItem<TItem>>(); - public ComboBoxItem<TItem> SelectedItem { get; set; } + public DropDownItem<TItem> SelectedItem + { + get => _selected; + set + { + _selected = value; + _input = value.Name; + } + } public SizeValue Width { get; set; } = SizeValue.Content; @@ -46,7 +58,7 @@ public class ComboBox<TItem> : Component public override Size GetSize() { var maxWidth = Items.Select(x => TextMeasurer.GetCurrentLineWidth(x.Name)).DefaultIfEmpty(0).Max() + (int)ImGuiNET.ImGui.GetStyle().ItemInnerSpacing.X * 2; - var arrowWidth = 20; + int arrowWidth = (int)(ButtonSizeX_ + ImGuiNET.ImGui.GetStyle().FramePadding.X * 2); SizeValue width = Width.IsContentAligned ? maxWidth + arrowWidth : Width; var height = TextMeasurer.GetCurrentLineHeight() + (int)ImGuiNET.ImGui.GetStyle().ItemInnerSpacing.Y * 2; @@ -63,10 +75,13 @@ protected override unsafe void UpdateInternal(Rectangle contentRect) ImGuiNET.ImGui.PushID(Id); + var arrowWidth = ButtonSizeX_ + ImGuiNET.ImGui.GetStyle().FramePadding.X * 2; + ImGuiNET.ImGui.SetNextItemWidth(contentRect.Width - arrowWidth); + bool isFinal = ImGuiNET.ImGui.InputText("##in", ref _input, MaxCharacters, ImGuiInputTextFlags.CallbackAlways | ImGuiInputTextFlags.EnterReturnsTrue, Propose); if (isFinal) { - ComboBoxItem<TItem> selectedItem = Items.FirstOrDefault(i => i.Name == _input); + DropDownItem<TItem> selectedItem = Items.FirstOrDefault(i => i.Name == _input); if (SelectedItem != selectedItem) { SelectedItem = selectedItem; @@ -92,9 +107,7 @@ protected override unsafe void UpdateInternal(Rectangle contentRect) ImGuiNET.ImGui.SetNextWindowSize(size); if (ImGuiNET.ImGui.BeginPopup("combobox", ImGuiWindowFlags.NoMove)) { - ImGuiNET.ImGui.Text("Select an item"); - ImGuiNET.ImGui.Separator(); - foreach (ComboBoxItem<TItem> item in Items) + foreach (DropDownItem<TItem> item in Items) { if (!ImGuiNET.ImGui.Selectable(item.Name)) continue; @@ -159,7 +172,7 @@ private unsafe int Propose(ImGuiInputTextCallbackData* data) int prevDiff = -1; string? itemName = null; - foreach (ComboBoxItem<TItem> item in Items) + foreach (DropDownItem<TItem> item in Items) { if (!Identical(bufferString, item.Name, out int diff)) continue; @@ -215,4 +228,19 @@ private bool Identical(string buf, string item, out int diff) return true; } } + + public class DropDownItem<TItem> + { + public TItem Content { get; } + + public LocalizedString Name { get; } + + public DropDownItem(TItem content, LocalizedString name = default) + { + Content = content; + Name = name.IsEmpty ? (LocalizedString)content.ToString() : name; + } + + public static implicit operator DropDownItem<TItem>(TItem o) => new(o); + } } diff --git a/ImGui.Forms/Controls/DropDownBox.cs b/ImGui.Forms/Controls/DropDownBox.cs deleted file mode 100644 index 072e1ed..0000000 --- a/ImGui.Forms/Controls/DropDownBox.cs +++ /dev/null @@ -1,84 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using ImGui.Forms.Controls.Base; -using ImGui.Forms.Localization; -using ImGui.Forms.Models; -using ImGui.Forms.Resources; -using Veldrid; - -namespace ImGui.Forms.Controls -{ - public class DropDownBox<TItem> : Component - { - #region Properties - - public IList<ComboBoxItem<TItem>> Items { get; } = new List<ComboBoxItem<TItem>>(); - - public ComboBoxItem<TItem> SelectedItem { get; set; } - - public SizeValue Width { get; set; } = SizeValue.Content; - - #endregion - - #region Events - - public event EventHandler SelectedItemChanged; - - #endregion - - public override Size GetSize() - { - var maxWidth = Items.Select(x => TextMeasurer.GetCurrentLineWidth(x.Name)).DefaultIfEmpty(0).Max() + (int)ImGuiNET.ImGui.GetStyle().ItemInnerSpacing.X * 2; - var arrowWidth = 20; - - SizeValue width = Width.IsContentAligned ? maxWidth + arrowWidth : Width; - var height = TextMeasurer.GetCurrentLineHeight() + (int)ImGuiNET.ImGui.GetStyle().ItemInnerSpacing.Y * 2; - - return new Size(width, height); - } - - protected override void UpdateInternal(Rectangle contentRect) - { - ImGuiNET.ImGui.SetNextItemWidth(contentRect.Width); - - var selectedName = SelectedItem?.Name ?? string.Empty; - if (ImGuiNET.ImGui.BeginCombo($"##combo{Id}", selectedName)) - { - for (var i = 0; i < Items.Count; i++) - { - if (ImGuiNET.ImGui.Selectable(Items[i].Name, SelectedItem == Items[i])) - { - var hasChanged = SelectedItem != Items[i]; - SelectedItem = Items[i]; - - if (hasChanged) - OnSelectedItemChanged(); - } - } - - ImGuiNET.ImGui.EndCombo(); - } - } - - private void OnSelectedItemChanged() - { - SelectedItemChanged?.Invoke(this, new EventArgs()); - } - } - - public class ComboBoxItem<TItem> - { - public TItem Content { get; } - - public LocalizedString Name { get; } - - public ComboBoxItem(TItem content, LocalizedString name = default) - { - Content = content; - Name = name.IsEmpty ? (LocalizedString)content.ToString() : name; - } - - public static implicit operator ComboBoxItem<TItem>(TItem o) => new(o); - } -} diff --git a/ImGui.Forms/Controls/Expander.cs b/ImGui.Forms/Controls/Expander.cs index 7ca91b9..9da0028 100644 --- a/ImGui.Forms/Controls/Expander.cs +++ b/ImGui.Forms/Controls/Expander.cs @@ -13,11 +13,10 @@ public class Expander : Component #region Properties public LocalizedString Caption { get; set; } + public Size Size { get; set; } = Size.WidthAlign; public Component Content { get; set; } - public int ContentHeight { get; set; } = 200; - public bool Expanded { get; set; } #endregion @@ -36,7 +35,13 @@ public Expander(Component content, LocalizedString caption = default) public override Size GetSize() { - return new Size(1f, (int)(GetHeaderHeight() + (Expanded ? ImGuiNET.ImGui.GetStyle().ItemSpacing.X + ContentHeight : 0))); + SizeValue height = Size.Height.IsContentAligned + ? Expanded + ? SizeValue.Absolute((int)(GetHeaderHeight() + ImGuiNET.ImGui.GetStyle().ItemSpacing.X + 200)) + : SizeValue.Absolute(GetHeaderHeight()) + : Size.Height; + + return new Size(Size.Width, height); } protected override void UpdateInternal(Rectangle contentRect) diff --git a/ImGui.Forms/Controls/ImageButton.cs b/ImGui.Forms/Controls/ImageButton.cs index 36e9399..1bd596e 100644 --- a/ImGui.Forms/Controls/ImageButton.cs +++ b/ImGui.Forms/Controls/ImageButton.cs @@ -58,7 +58,7 @@ protected override void UpdateInternal(Rectangle contentRect) var enabled = Enabled; ApplyStyles(enabled); - if ((nint)Image != nint.Zero) + if (Image != null && (nint)Image != nint.Zero) { if ((ImGuiNET.ImGui.ImageButton($"##{Id}", (nint)Image, GetImageSize()) || IsKeyDown(KeyAction)) && Enabled) OnClicked(); diff --git a/ImGui.Forms/Controls/Label.cs b/ImGui.Forms/Controls/Label.cs index c2f972f..405f123 100644 --- a/ImGui.Forms/Controls/Label.cs +++ b/ImGui.Forms/Controls/Label.cs @@ -18,7 +18,7 @@ public class Label : Component public FontResource Font { get; set; } - public float LineDistance { get; set; } + public int LineDistance { get; set; } public ThemedColor TextColor { get; set; } diff --git a/ImGui.Forms/Controls/Layouts/StackLayout.cs b/ImGui.Forms/Controls/Layouts/StackLayout.cs index b916ea0..6f9bbc8 100644 --- a/ImGui.Forms/Controls/Layouts/StackLayout.cs +++ b/ImGui.Forms/Controls/Layouts/StackLayout.cs @@ -73,14 +73,14 @@ public StackLayout() public override Size GetSize() => _size; - protected override int GetContentWidth(int parentWidth, float layoutCorrection = 1) + protected override int GetContentWidth(int parentWidth, int parentHeight, float layoutCorrection = 1) { - return _tableLayout.GetWidth(parentWidth, layoutCorrection); + return _tableLayout.GetWidth(parentWidth, parentHeight, layoutCorrection); } - protected override int GetContentHeight(int parentHeight, float layoutCorrection = 1) + protected override int GetContentHeight(int parentWidth, int parentHeight, float layoutCorrection = 1) { - return _tableLayout.GetHeight(parentHeight, layoutCorrection); + return _tableLayout.GetHeight(parentWidth, parentHeight, layoutCorrection); } protected override void UpdateInternal(Rectangle contentRect) diff --git a/ImGui.Forms/Controls/Layouts/TableCell.cs b/ImGui.Forms/Controls/Layouts/TableCell.cs index 9677c33..db8bcbb 100644 --- a/ImGui.Forms/Controls/Layouts/TableCell.cs +++ b/ImGui.Forms/Controls/Layouts/TableCell.cs @@ -47,18 +47,18 @@ public TableCell(Component component) Content = component; } - public int GetWidth(int parentWidth, float layoutCorrection = 1f) + public int GetWidth(int parentWidth, int parentHeight, float layoutCorrection = 1f) { if (Size.Width.IsContentAligned) - return Content?.GetWidth(parentWidth, layoutCorrection) ?? 0; + return Content?.GetWidth(parentWidth, parentHeight, layoutCorrection) ?? 0; return Component.GetDimension(Size.Width, parentWidth, layoutCorrection); } - public int GetHeight(int parentHeight, float layoutCorrection = 1f) + public int GetHeight(int parentWidth, int parentHeight, float layoutCorrection = 1f) { if (Size.Height.IsContentAligned) - return Content?.GetHeight(parentHeight, layoutCorrection) ?? 0; + return Content?.GetHeight(parentWidth, parentHeight, layoutCorrection) ?? 0; return Component.GetDimension(Size.Height, parentHeight, layoutCorrection); } diff --git a/ImGui.Forms/Controls/Layouts/TableLayout.cs b/ImGui.Forms/Controls/Layouts/TableLayout.cs index 7ee747e..499b5dd 100644 --- a/ImGui.Forms/Controls/Layouts/TableLayout.cs +++ b/ImGui.Forms/Controls/Layouts/TableLayout.cs @@ -36,15 +36,15 @@ public TableLayout() public override Size GetSize() => Size; - protected override int GetContentWidth(int parentWidth, float layoutCorrection = 1f) + protected override int GetContentWidth(int parentWidth, int parentHeight, float layoutCorrection = 1f) { - var widths = GetColumnWidths(parentWidth, layoutCorrection); + var widths = GetColumnWidths(parentWidth, parentHeight, layoutCorrection); return Math.Min(parentWidth, widths.Sum(x => x) + (widths.Length - 1) * (int)Spacing.X); } - protected override int GetContentHeight(int parentHeight, float layoutCorrection = 1) + protected override int GetContentHeight(int parentWidth, int parentHeight, float layoutCorrection = 1) { - var heights = GetRowHeights(parentHeight, layoutCorrection); + var heights = GetRowHeights(parentWidth, parentHeight, layoutCorrection); return Math.Min(parentHeight, heights.Sum(x => x) + (heights.Length - 1) * (int)Spacing.Y); } @@ -75,8 +75,8 @@ public TableCell GetCell(int row, int col) protected override void UpdateInternal(Rectangle contentRect) { - var localWidths = GetColumnWidths(contentRect.Width, 1f); - var localHeights = GetRowHeights(contentRect.Height, 1f); + var localWidths = GetColumnWidths(contentRect.Width, contentRect.Height, 1f); + var localHeights = GetRowHeights(contentRect.Width, contentRect.Height, 1f); var totalWidth = localWidths.Sum() + Spacing.X * (localWidths.Length - 1); var totalHeight = localHeights.Sum() + Spacing.Y * (localHeights.Length - 1); @@ -116,8 +116,8 @@ protected override void UpdateInternal(Rectangle contentRect) // Apply cell alignment var cellInternalSize = cell?.Content?.GetSize() ?? Size.Parent; - var cellInternalWidth = cellInternalSize.Width.IsAbsolute ? cell?.Content?.GetWidth(cellWidth) ?? 0 : cellWidth; - var cellInternalHeight = cellInternalSize.Height.IsAbsolute ? cell?.Content?.GetHeight(cellHeight) ?? 0 : cellHeight; + var cellInternalWidth = cellInternalSize.Width.IsAbsolute ? cell?.Content?.GetWidth(cellWidth, cellHeight) ?? 0 : cellWidth; + var cellInternalHeight = cellInternalSize.Height.IsAbsolute ? cell?.Content?.GetHeight(cellWidth, cellHeight) ?? 0 : cellHeight; var cellXOffset = 0f; var cellYOffset = 0f; @@ -195,7 +195,7 @@ protected override void SetTabInactiveCore() #region Width calculation - private int[] GetColumnWidths(int componentWidth, float layoutCorrection) + private int[] GetColumnWidths(int componentWidth, int componentHeight, float layoutCorrection) { var maxColumnCount = GetMaxColumnCount(); var result = Enumerable.Repeat(-1, maxColumnCount).ToArray(); @@ -217,7 +217,7 @@ private int[] GetColumnWidths(int componentWidth, float layoutCorrection) var widthValue = (int)cell.Size.Width.Value; var maxValue = widthValue < 0 ? - cell.Content.GetWidth(componentWidth, layoutCorrection) : + cell.Content.GetWidth(componentWidth, componentHeight, layoutCorrection) : widthValue; maxValue = Math.Min(availableWidth, maxValue); @@ -256,7 +256,7 @@ private int[] GetColumnWidths(int componentWidth, float layoutCorrection) maxIsAbsolute = true; var maxValue = cellWidth.Value < 0 ? - cell.GetWidth(componentWidth, layoutCorrection) : + cell.GetWidth(componentWidth, componentHeight, layoutCorrection) : (int)cellWidth.Value; if (maxValue > maxCellWidth) @@ -266,7 +266,7 @@ private int[] GetColumnWidths(int componentWidth, float layoutCorrection) } maxIsAbsolute = false; - maxCellWidth = cell.GetWidth(availableWidth, widthCorrection); + maxCellWidth = cell.GetWidth(availableWidth, componentHeight, widthCorrection); } // If max width is not absolute, do nothing @@ -305,7 +305,7 @@ private float GetMaxRelativeWidth(int column) #region Height calculation - private int[] GetRowHeights(int componentHeight, float layoutCorrection) + private int[] GetRowHeights(int componentWidth, int componentHeight, float layoutCorrection) { var result = Enumerable.Repeat(-1, Rows.Count).ToArray(); @@ -324,7 +324,7 @@ private int[] GetRowHeights(int componentHeight, float layoutCorrection) if (cell?.Content == null) continue; var maxValue = cell.Size.Height.IsContentAligned ? - cell.Content.GetHeight(componentHeight, layoutCorrection) : + cell.Content.GetHeight(componentWidth, componentHeight, layoutCorrection) : (int)cell.Size.Height.Value; if (!Size.Height.IsContentAligned) maxValue = Math.Min(availableHeight, maxValue); @@ -365,7 +365,7 @@ private int[] GetRowHeights(int componentHeight, float layoutCorrection) maxIsAbsolute = true; var maxValue = cellHeight.IsContentAligned ? - cell.GetHeight(componentHeight, layoutCorrection) : + cell.GetHeight(componentWidth, componentHeight, layoutCorrection) : cellHeight.Value; if (!Size.Height.IsContentAligned) maxValue = Math.Min(availableHeight, maxValue); @@ -377,7 +377,7 @@ private int[] GetRowHeights(int componentHeight, float layoutCorrection) } maxIsAbsolute = false; - maxCellHeight = cell.GetHeight(availableHeight, heightCorrection); + maxCellHeight = cell.GetHeight(componentWidth, availableHeight, heightCorrection); } // If max height is not absolute, do nothing diff --git a/ImGui.Forms/Controls/Layouts/ZLayout.cs b/ImGui.Forms/Controls/Layouts/ZLayout.cs index aec9aea..545c045 100644 --- a/ImGui.Forms/Controls/Layouts/ZLayout.cs +++ b/ImGui.Forms/Controls/Layouts/ZLayout.cs @@ -36,8 +36,8 @@ protected override void UpdateInternal(Rectangle contentRect) if (!(item?.Visible ?? false)) continue; - var itemWidth = item.GetWidth(contentRect.Width); - var itemHeight = item.GetHeight(contentRect.Height); + var itemWidth = item.GetWidth(contentRect.Width, contentRect.Height); + var itemHeight = item.GetHeight(contentRect.Width,contentRect.Height); // Wrap positions if (x + itemWidth + ItemSpacing.X >= contentRect.Width) diff --git a/ImGui.Forms/Controls/Lists/ActivableList.cs b/ImGui.Forms/Controls/Lists/ActivableList.cs index 1fc0f7b..10848d8 100644 --- a/ImGui.Forms/Controls/Lists/ActivableList.cs +++ b/ImGui.Forms/Controls/Lists/ActivableList.cs @@ -5,20 +5,21 @@ namespace ImGui.Forms.Controls.Lists { - public class ActivableList : BaseList<ActivableComponent> + public class ActivableList<TItem> : List<TItem> + where TItem : ActivableComponent { - protected override void OnItemAdded(ItemEventArgs<ActivableComponent> e) + protected override void OnItemAdded(ItemEventArgs<TItem> e) { e.Item.Activated += Item_Activated; } - protected override void OnItemSet(ItemSetEventArgs<ActivableComponent> e) + protected override void OnItemSet(ItemSetEventArgs<TItem> e) { e.PreviousItem.Activated -= Item_Activated; e.Item.Activated += Item_Activated; } - protected override void OnItemRemoved(ItemEventArgs<ActivableComponent> e) + protected override void OnItemRemoved(ItemEventArgs<TItem> e) { e.Item.Activated -= Item_Activated; } diff --git a/ImGui.Forms/Controls/Lists/BaseList.cs b/ImGui.Forms/Controls/Lists/BaseList.cs deleted file mode 100644 index 8c57649..0000000 --- a/ImGui.Forms/Controls/Lists/BaseList.cs +++ /dev/null @@ -1,182 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Numerics; -using ImGui.Forms.Controls.Base; -using ImGui.Forms.Controls.Layouts; -using ImGuiNET; -using Veldrid; -using Size = ImGui.Forms.Models.Size; - -namespace ImGui.Forms.Controls.Lists -{ - public abstract class BaseList<TItem> : Component - where TItem : Component - { - private bool _scrollToLast; - - public IList<TItem> Items { get; protected set; } - public Alignment Alignment { get; set; } = Alignment.Vertical; - - public Size Size { get; set; } = Size.Content; - public int ItemSpacing { get; set; } - public Vector2 Padding { get; set; } - public bool ScrollToLastItem { get; set; } - - public override Size GetSize() => Size; - - public BaseList() - { - var items = new ObservableList<TItem>(); - - items.ItemInserted += Items_NewItem; - items.ItemAdded += Items_NewItem; - items.ItemSet += Items_ItemSet; - items.ItemRemoved += Items_ItemRemoved; - - Items = items; - } - - protected override int GetContentWidth(int parentWidth, float layoutCorrection = 1) - { - var widths = Items.Select(x => x.GetWidth(parentWidth, layoutCorrection)).DefaultIfEmpty(Size.Width.IsParentAligned ? parentWidth : 0).ToArray(); - - var totalWidth = Alignment == Alignment.Horizontal ? - widths.Sum() + Math.Max(0, Items.Count - 1) * ItemSpacing : - widths.Max(); - return Math.Min(parentWidth, (int)(totalWidth + Padding.X * 2)); - } - - protected override int GetContentHeight(int parentHeight, float layoutCorrection = 1) - { - var heights = Items.Select(x => x.GetHeight(parentHeight, layoutCorrection)).DefaultIfEmpty(Size.Height.IsParentAligned ? parentHeight : 0).ToArray(); - - var totalHeight = Alignment == Alignment.Vertical ? - heights.Sum() + Math.Max(0, Items.Count - 1) * ItemSpacing : - heights.Max(); - return Math.Min(parentHeight, (int)(totalHeight + Padding.Y * 2)); - } - - protected override void UpdateInternal(Rectangle contentRect) - { - var localItems = Items?.ToArray() ?? Array.Empty<Component>(); - - var listDimension = localItems.Sum(i => GetDimension(i, contentRect)) + Math.Max(0, localItems.Length - 1) * ItemSpacing + (int)(GetPadding() * 2); - var scrollableDimension = GetScrollableDimension(contentRect); - - if (ImGuiNET.ImGui.BeginChild($"{Id}", contentRect.Size, ImGuiChildFlags.None)) - { - if (_scrollToLast) - { - SetScroll(listDimension - scrollableDimension); - _scrollToLast = false; - } - - var scrollbarDimension = listDimension > scrollableDimension ? (int)ImGuiNET.ImGui.GetStyle().ScrollbarSize : 0; - var (scrollX, scrollY) = (-(int)ImGuiNET.ImGui.GetScrollX(), -(int)ImGuiNET.ImGui.GetScrollY()); - - var (x, y) = (Padding.X, Padding.Y); - - if (ImGuiNET.ImGui.BeginChild($"{Id}_in", GetInnerSize(listDimension, contentRect))) - { - for (var i = 0; i < localItems.Length; i++) - { - var itemWidth = localItems[i].GetWidth(contentRect.Width - (Alignment == Alignment.Vertical ? scrollbarDimension : 0) - (int)(Padding.X * 2)); - var itemHeight = localItems[i].GetHeight(contentRect.Height - (Alignment == Alignment.Horizontal ? scrollbarDimension : 0) - (int)(Padding.Y * 2)); - - ImGuiNET.ImGui.SetCursorPos(new Vector2(x, y)); - if (ImGuiNET.ImGui.BeginChild($"{Id}_itm{i}", new Vector2(itemWidth, itemHeight))) - localItems[i].Update(new Rectangle((int)(contentRect.X + x + scrollX), (int)(contentRect.Y + y + scrollY), itemWidth, itemHeight)); - - ImGuiNET.ImGui.EndChild(); - - if (Alignment == Alignment.Vertical) - y += itemHeight + ItemSpacing; - else - x += itemWidth + ItemSpacing; - } - } - - ImGuiNET.ImGui.EndChild(); - } - - ImGuiNET.ImGui.EndChild(); - } - - protected override void SetTabInactiveCore() - { - foreach (TItem item in Items) - item?.SetTabInactiveInternal(); - } - - #region Selection methods - - private int GetDimension(Component component, Rectangle contentRect) - { - return Alignment == Alignment.Vertical - ? component.GetHeight(contentRect.Height) - : component.GetWidth(contentRect.Width); - } - - private float GetPadding() - { - return Alignment == Alignment.Vertical - ? Padding.Y - : Padding.X; - } - - private int GetScrollableDimension(Rectangle contentRect) - { - return Alignment == Alignment.Vertical - ? contentRect.Height - : contentRect.Width; - } - - private Vector2 GetInnerSize(int listDimension, Rectangle contentRect) - { - return Alignment == Alignment.Vertical ? - new Vector2(contentRect.Width, listDimension) : - new Vector2(listDimension, contentRect.Height); - } - - private void SetScroll(float scroll) - { - scroll = Math.Max(0, scroll); - - if (Alignment == Alignment.Vertical) - ImGuiNET.ImGui.SetScrollY(scroll); - else - ImGuiNET.ImGui.SetScrollX(scroll); - } - - #endregion - - #region Observable events - - private void Items_NewItem(object sender, ItemEventArgs<TItem> e) - { - OnItemAdded(e); - } - - private void Items_ItemSet(object sender, ItemSetEventArgs<TItem> e) - { - OnItemSet(e); - } - - private void Items_ItemRemoved(object sender, ItemEventArgs<TItem> e) - { - OnItemRemoved(e); - } - - protected virtual void OnItemAdded(ItemEventArgs<TItem> e) - { - _scrollToLast = ScrollToLastItem; - } - - protected virtual void OnItemSet(ItemSetEventArgs<TItem> e) { } - - protected virtual void OnItemRemoved(ItemEventArgs<TItem> e) { } - - #endregion - } -} diff --git a/ImGui.Forms/Controls/Lists/DataTable.cs b/ImGui.Forms/Controls/Lists/DataTable.cs index 3b725b7..8b9806b 100644 --- a/ImGui.Forms/Controls/Lists/DataTable.cs +++ b/ImGui.Forms/Controls/Lists/DataTable.cs @@ -1,10 +1,8 @@ using System; using System.Collections.Generic; -using System.Drawing; using System.Linq; using ImGui.Forms.Controls.Base; using ImGui.Forms.Controls.Menu; -using ImGui.Forms.Extensions; using ImGuiNET; using Rectangle = Veldrid.Rectangle; using Size = ImGui.Forms.Models.Size; @@ -14,13 +12,13 @@ namespace ImGui.Forms.Controls.Lists public class DataTable<TData> : Component { private (int, int) _clickedCell = (-1, -1); - private readonly IList<int> _selectedIndexes = new List<int>(); + private readonly IList<int> _selectedIndexes = new System.Collections.Generic.List<int>(); - private IList<DataTableRow<TData>> _rows = new List<DataTableRow<TData>>(); + private IList<DataTableRow<TData>> _rows = new System.Collections.Generic.List<DataTableRow<TData>>(); #region Properties - public IList<DataTableColumn<TData>> Columns { get; } = new List<DataTableColumn<TData>>(); + public IList<DataTableColumn<TData>> Columns { get; } = new System.Collections.Generic.List<DataTableColumn<TData>>(); public IList<DataTableRow<TData>> Rows { @@ -57,19 +55,16 @@ public IList<DataTableRow<TData>> Rows #endregion - public override Size GetSize() - { - return Size; - } + public override Size GetSize() => Size; protected override void UpdateInternal(Rectangle contentRect) { - var localRows = _rows ?? new List<DataTableRow<TData>>(); + var localRows = _rows ?? new System.Collections.Generic.List<DataTableRow<TData>>(); var flags = ImGuiTableFlags.BordersV; if (IsResizable) flags |= ImGuiTableFlags.Resizable; - if (ImGuiNET.ImGui.BeginChild($"{Id}c")) + if (ImGuiNET.ImGui.BeginChild($"{Id}c", contentRect.Size)) { if (ImGuiNET.ImGui.BeginTable($"{Id}t", Columns.Count, flags)) { diff --git a/ImGui.Forms/Controls/Lists/ImageList.cs b/ImGui.Forms/Controls/Lists/ImageList.cs deleted file mode 100644 index 68ce421..0000000 --- a/ImGui.Forms/Controls/Lists/ImageList.cs +++ /dev/null @@ -1,188 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Numerics; -using ImGui.Forms.Controls.Base; -using ImGui.Forms.Controls.Layouts; -using ImGui.Forms.Extensions; -using ImGui.Forms.Factories; -using ImGui.Forms.Models; -using ImGui.Forms.Resources; -using ImGui.Forms.Support; -using ImGuiNET; -using Veldrid; - -namespace ImGui.Forms.Controls.Lists -{ - public class ImageList : Component - { - // HINT: ImGuiCol.Header is used for selected items - // HINT: ImGuiCol.HeaderHovered is used for hovered items - // HINT: ImGuiCol.HeaderActive is used for hovered and clicked items - - public IList<ImageListItem> Items { get; } - public ImageListItem SelectedItem { get; set; } - - public Vector2 ThumbnailSize { get; set; } = new(30, 30); - public bool ShowThumbnailBorder { get; set; } - - public FontResource Font { get; set; } - - public Vector2 Padding { get; set; } - public int ItemPadding { get; set; } - - public Size Size { get; set; } = Size.Parent; - - #region Events - - public event EventHandler SelectedItemChanged; - - #endregion - - public ImageList() - { - var items = new ObservableList<ImageListItem>(); - items.ItemAdded += Items_ItemAdded; - items.ItemRemoved += Items_ItemRemoved; - items.ItemSet += Items_ItemSet; - items.ItemInserted += Items_ItemInserted; - - Items = items; - } - - public override Size GetSize() => Size; - - protected override void UpdateInternal(Rectangle contentRect) - { - ImageListItem selectedItem = null; - - if (ImGuiNET.ImGui.BeginChild($"##{Id}_out", new Vector2(contentRect.Width, contentRect.Height), ImGuiChildFlags.None)) - { - var textHeight = TextMeasurer.GetCurrentLineHeight(Font); - - var itemHeight = Math.Max((int)ThumbnailSize.Y, textHeight); - var itemDimensions = new Vector2(contentRect.Width - Padding.X * 2, itemHeight); - var contentPos = new Vector2(contentRect.X + Padding.X, contentRect.Y + Padding.Y); - var scrollY = ImGuiNET.ImGui.GetScrollY(); - - var localItems = Items.ToArray(); - - if (ImGuiNET.ImGui.BeginChild($"##{Id}_in", new Vector2(contentRect.Width, Padding.Y * 2 + localItems.Length * (itemHeight + ItemPadding)), ImGuiChildFlags.None, ImGuiWindowFlags.NoScrollbar)) - { - for (var i = 0; i < localItems.Length; i++) - { - var item = localItems[i]; - var itemId = IdFactory.Get(item); - - var itemPos = new Vector2(Padding.X, i * itemHeight + Padding.Y + i * ItemPadding); - var contentItemPos = contentPos + itemPos; - var contentScrollPos = contentItemPos - new Vector2(0, scrollY); - var contentScrollEndPos = contentScrollPos + itemDimensions; - - // Create dummy control for states - ImGuiNET.ImGui.PushID(itemId); - ImGuiSupport.Dummy(itemId, itemPos - new Vector2(0, scrollY), itemDimensions); - - // Create item states - var isItemSelected = item == SelectedItem; - var isItemHovered = ImGuiNET.ImGui.IsItemHovered() && - (int)(ImGuiNET.ImGui.GetMousePos().Y - contentPos.Y - Padding.Y + scrollY) / - (itemHeight + ItemPadding) == i; - var isItemClicked = isItemHovered && ImGuiNET.ImGui.IsMouseDown(ImGuiMouseButton.Left); - - ImGuiNET.ImGui.PopID(); - - // Set selected item locally - if (isItemHovered && ImGuiNET.ImGui.IsMouseClicked(ImGuiMouseButton.Left)) - selectedItem = item; - - // Add background color for selection - var color = isItemClicked ? ImGuiNET.ImGui.GetColorU32(ImGuiCol.HeaderActive) : - isItemHovered ? ImGuiNET.ImGui.GetColorU32(ImGuiCol.HeaderHovered) : - isItemSelected ? ImGuiNET.ImGui.GetColorU32(ImGuiCol.Header) : 0; - - if (isItemHovered || isItemSelected) - ImGuiNET.ImGui.GetWindowDrawList().AddRectFilled(contentScrollPos, contentScrollEndPos, color); - - // Add thumbnail - if (item.Image != null && ThumbnailSize.X != 0 && ThumbnailSize.Y != 0) - { - var imgPos = contentScrollPos; - var thumbnailRect = new Vector2(ThumbnailSize.X, ThumbnailSize.Y); - - if (item.RetainAspectRatio) - { - var heightSmaller = item.Image.Height < item.Image.Width; - var ratio = heightSmaller ? - (float)item.Image.Height / item.Image.Width : - (float)item.Image.Width / item.Image.Height; - - var retainedWidth = !heightSmaller ? ThumbnailSize.Y * ratio : ThumbnailSize.X; - var retainedHeight = heightSmaller ? ThumbnailSize.X * ratio : ThumbnailSize.Y; - - imgPos = heightSmaller ? - new Vector2(imgPos.X, imgPos.Y + (ThumbnailSize.Y - retainedHeight) / 2f) : - new Vector2(imgPos.X + (ThumbnailSize.X - retainedWidth) / 2f, imgPos.Y); - thumbnailRect = new Vector2(retainedWidth, retainedHeight); - } - - ImGuiNET.ImGui.GetWindowDrawList().AddImage((nint)item.Image, imgPos, imgPos + thumbnailRect); - - if (ShowThumbnailBorder) - ImGuiNET.ImGui.GetWindowDrawList().AddRect(contentScrollPos, contentScrollPos + new Vector2(ThumbnailSize.X, ThumbnailSize.Y), Style.GetColor(ImGuiCol.Border).ToUInt32(), 0); - } - - // Add text - var textPos = contentScrollPos + new Vector2(ThumbnailSize.X + 2, (itemHeight - textHeight) / 2f); - - ImFontPtr? fontPtr = Font?.GetPointer(); - if (fontPtr != null) - ImGuiNET.ImGui.GetWindowDrawList().AddText(fontPtr.Value, Font.Data.Size, textPos, 0xFFFFFFFF, item.Text); - else - ImGuiNET.ImGui.GetWindowDrawList().AddText(textPos, 0xFFFFFFFF, item.Text); - } - } - - ImGuiNET.ImGui.EndChild(); - } - - ImGuiNET.ImGui.EndChild(); - - // Invoke selected item change event - if (selectedItem != null && SelectedItem != selectedItem) - { - SelectedItem = selectedItem; - OnSelectedItemChanged(); - } - } - - #region Event Invokers - - private void OnSelectedItemChanged() - { - SelectedItemChanged?.Invoke(this, new EventArgs()); - } - - #endregion - - #region Event Methods - - private void Items_ItemAdded(object sender, ItemEventArgs<ImageListItem> e) - { - } - - private void Items_ItemRemoved(object sender, ItemEventArgs<ImageListItem> e) - { - } - - private void Items_ItemInserted(object sender, ItemEventArgs<ImageListItem> e) - { - } - - private void Items_ItemSet(object sender, ItemEventArgs<ImageListItem> e) - { - } - - #endregion - } -} diff --git a/ImGui.Forms/Controls/Lists/ImageListItem.cs b/ImGui.Forms/Controls/Lists/ImageListItem.cs deleted file mode 100644 index 694c112..0000000 --- a/ImGui.Forms/Controls/Lists/ImageListItem.cs +++ /dev/null @@ -1,24 +0,0 @@ -using ImGui.Forms.Localization; -using ImGui.Forms.Resources; - -namespace ImGui.Forms.Controls.Lists -{ - public class ImageListItem - { - private ThemedImageResource _baseImg; - - public ThemedImageResource Image - { - get => _baseImg; - set - { - _baseImg?.Destroy(); - _baseImg = value; - } - } - - public bool RetainAspectRatio { get; set; } = true; - - public LocalizedString Text { get; set; } - } -} diff --git a/ImGui.Forms/Controls/Lists/List.cs b/ImGui.Forms/Controls/Lists/List.cs index a070ad2..58ed5c1 100644 --- a/ImGui.Forms/Controls/Lists/List.cs +++ b/ImGui.Forms/Controls/Lists/List.cs @@ -1,8 +1,280 @@ -using ImGui.Forms.Controls.Base; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Numerics; +using ImGui.Forms.Controls.Base; +using ImGui.Forms.Controls.Layouts; +using ImGuiNET; +using Veldrid; +using Size = ImGui.Forms.Models.Size; namespace ImGui.Forms.Controls.Lists { - public class List : BaseList<Component> + public class List<TItem> : Component where TItem : Component { + private bool _scrollToLast; + + #region Properties + + public IList<TItem> Items { get; protected set; } + public TItem SelectedItem { get; set; } + + public Alignment Alignment { get; set; } = Alignment.Vertical; + + public Size Size { get; set; } = Size.Content; + + public int ItemSpacing { get; set; } + public Vector2 Padding { get; set; } + + public bool ScrollToLastItem { get; set; } + + public bool IsSelectable { get; set; } = false; + + #endregion + + #region Events + + public event EventHandler SelectedItemChanged; + + #endregion + + public List() + { + var items = new ObservableList<TItem>(); + + items.ItemInserted += Items_NewItem; + items.ItemAdded += Items_NewItem; + items.ItemSet += Items_ItemSet; + items.ItemRemoved += Items_ItemRemoved; + + Items = items; + } + + public override Size GetSize() => Size; + + protected override void UpdateInternal(Rectangle contentRect) + { + TItem selectedItem = null; + var localItems = Items?.ToArray() ?? Array.Empty<TItem>(); + + var listDimension = localItems.Sum(i => GetDimension(i, contentRect)) + Math.Max(0, localItems.Length - 1) * ItemSpacing + (int)(GetPadding() * 2); + var scrollableDimension = GetScrollableDimension(contentRect); + + var flags = ImGuiWindowFlags.None; + if (Alignment == Alignment.Horizontal) + flags |= ImGuiWindowFlags.HorizontalScrollbar; + + if (ImGuiNET.ImGui.BeginChild($"{Id}", contentRect.Size, ImGuiChildFlags.None, flags)) + { + if (_scrollToLast) + { + SetScroll(listDimension - scrollableDimension); + _scrollToLast = false; + } + + var scrollbarDimension = listDimension > scrollableDimension ? (int)ImGuiNET.ImGui.GetStyle().ScrollbarSize : 0; + var scroll = new Vector2(-(int)ImGuiNET.ImGui.GetScrollX(), -(int)ImGuiNET.ImGui.GetScrollY()); + + var (x, y) = (Padding.X, Padding.Y); + var contentPos = new Vector2(contentRect.X + x, contentRect.Y + y); + + if (ImGuiNET.ImGui.BeginChild($"{Id}_in", GetInnerSize(listDimension, contentRect), ImGuiChildFlags.None)) + { + for (var i = 0; i < localItems.Length; i++) + { + var item = localItems[i]; + + var itemWidth = item.GetWidth(contentRect.Width - (Alignment == Alignment.Vertical ? scrollbarDimension : 0) - (int)(Padding.X * 2), contentRect.Height); + var itemHeight = item.GetHeight(contentRect.Width, contentRect.Height - (Alignment == Alignment.Horizontal ? scrollbarDimension : 0) - (int)(Padding.Y * 2)); + var itemSize = new Vector2(itemWidth, itemHeight); + + var contentItemPos = new Vector2(contentRect.X + x, contentRect.Y + y); + var contentItemScrollPos = contentItemPos + scroll; + var contentItemScrollEndPos = contentItemScrollPos + itemSize; + + var contentScrollPos = contentPos + scroll; + + // Create item states + var isItemSelected = item == SelectedItem; + var isItemHovered = ImGuiNET.ImGui.IsMouseHoveringRect(contentRect.Position, contentRect.Position + contentRect.Size) + && (Alignment == Alignment.Vertical + ? (int)(ImGuiNET.ImGui.GetMousePos().Y - contentScrollPos.Y) / (itemHeight + ItemSpacing) == i + : (int)(ImGuiNET.ImGui.GetMousePos().X - contentScrollPos.X) / (itemWidth + ItemSpacing) == i); + var isItemClicked = isItemHovered && ImGuiNET.ImGui.IsMouseDown(ImGuiMouseButton.Left); + + // Set selected item locally + if (IsSelectable && isItemHovered && ImGuiNET.ImGui.IsMouseClicked(ImGuiMouseButton.Left)) + selectedItem = item; + + // Add background color for selection + var color = isItemClicked + ? ImGuiNET.ImGui.GetColorU32(ImGuiCol.HeaderActive) + : isItemHovered + ? ImGuiNET.ImGui.GetColorU32(ImGuiCol.HeaderHovered) + : isItemSelected + ? ImGuiNET.ImGui.GetColorU32(ImGuiCol.Header) + : 0; + + if (IsSelectable && (isItemHovered || isItemSelected)) + ImGuiNET.ImGui.GetWindowDrawList().AddRectFilled(contentItemScrollPos, contentItemScrollEndPos, color); + + ImGuiNET.ImGui.SetCursorPos(new Vector2(x, y)); + if (ImGuiNET.ImGui.BeginChild($"{Id}_itm{i}", itemSize)) + item.Update(new Rectangle((int)contentItemScrollPos.X, (int)contentItemScrollPos.Y, itemWidth, itemHeight)); + + ImGuiNET.ImGui.EndChild(); + + if (Alignment == Alignment.Vertical) + y += itemHeight + ItemSpacing; + else + x += itemWidth + ItemSpacing; + } + } + + ImGuiNET.ImGui.EndChild(); + } + + ImGuiNET.ImGui.EndChild(); + + // Invoke selected item change event + if (IsSelectable && selectedItem != null && SelectedItem != selectedItem) + { + SelectedItem = selectedItem; + OnSelectedItemChanged(); + } + } + + protected override int GetContentWidth(int parentWidth, int parentHeight, float layoutCorrection = 1f) + { + // If no items, return 0 + if (Items.Count <= 0) + return 0; + + int totalWidth = GetTotalWidth(parentWidth, parentHeight, layoutCorrection); + int totalHeight = GetTotalHeight(parentWidth, parentHeight, layoutCorrection); + + // Allocate for scroll bar + if (Alignment == Alignment.Vertical && totalHeight > GetHeight(parentWidth, parentHeight, layoutCorrection)) + totalWidth += (int)ImGuiNET.ImGui.GetStyle().ScrollbarSize; + + return Math.Min(parentWidth, (int)(totalWidth + Padding.X * 2)); + } + + private int GetTotalWidth(int parentWidth, int parentHeight, float layoutCorrection) + { + int[] widths = Items.Select(x => x.GetWidth(parentWidth, parentHeight, layoutCorrection)).ToArray(); + + // Base on alignment, sum widths or take maximum + return Alignment == Alignment.Horizontal ? + widths.Sum() + (Items.Count - 1) * ItemSpacing : + widths.Max(); + } + + protected override int GetContentHeight(int parentWidth, int parentHeight, float layoutCorrection = 1f) + { + // If no items, return 0 + if (Items.Count <= 0) + return 0; + + int totalWidth = GetTotalWidth(parentWidth, parentHeight, layoutCorrection); + int totalHeight = GetTotalHeight(parentWidth, parentHeight, layoutCorrection); + + // Allocate for scroll bar + if (Alignment == Alignment.Horizontal && totalWidth > GetWidth(parentWidth, parentHeight, layoutCorrection)) + totalHeight += (int)ImGuiNET.ImGui.GetStyle().ScrollbarSize; + + return Math.Min(parentHeight, (int)(totalHeight + Padding.X * 2)); + } + + private int GetTotalHeight(int parentWidth, int parentHeight, float layoutCorrection) + { + int[] heights = Items.Select(x => x.GetHeight(parentWidth, parentHeight, layoutCorrection)).ToArray(); + + // Base on alignment, sum heights or take maximum + return Alignment == Alignment.Vertical ? + heights.Sum() + (Items.Count - 1) * ItemSpacing : + heights.Max(); + } + + protected override void SetTabInactiveCore() + { + foreach (TItem item in Items) + item?.SetTabInactiveInternal(); + } + + private void OnSelectedItemChanged() + { + SelectedItemChanged?.Invoke(this, EventArgs.Empty); + } + + #region Selection methods + + private int GetDimension(Component component, Rectangle contentRect) + { + return Alignment == Alignment.Vertical + ? component.GetHeight(contentRect.Width, contentRect.Height) + : component.GetWidth(contentRect.Width, contentRect.Height); + } + + private float GetPadding() + { + return Alignment == Alignment.Vertical + ? Padding.Y + : Padding.X; + } + + private int GetScrollableDimension(Rectangle contentRect) + { + return Alignment == Alignment.Vertical + ? contentRect.Height + : contentRect.Width; + } + + private Vector2 GetInnerSize(int listDimension, Rectangle contentRect) + { + return Alignment == Alignment.Vertical ? + new Vector2(contentRect.Width, listDimension) : + new Vector2(listDimension, contentRect.Height); + } + + private void SetScroll(float scroll) + { + scroll = Math.Max(0, scroll); + + if (Alignment == Alignment.Vertical) + ImGuiNET.ImGui.SetScrollY(scroll); + else + ImGuiNET.ImGui.SetScrollX(scroll); + } + + #endregion + + #region Observable events + + private void Items_NewItem(object sender, ItemEventArgs<TItem> e) + { + OnItemAdded(e); + } + + private void Items_ItemSet(object sender, ItemSetEventArgs<TItem> e) + { + OnItemSet(e); + } + + private void Items_ItemRemoved(object sender, ItemEventArgs<TItem> e) + { + OnItemRemoved(e); + } + + protected virtual void OnItemAdded(ItemEventArgs<TItem> e) + { + _scrollToLast = ScrollToLastItem; + } + + protected virtual void OnItemSet(ItemSetEventArgs<TItem> e) { } + + protected virtual void OnItemRemoved(ItemEventArgs<TItem> e) { } + + #endregion } } diff --git a/ImGui.Forms/Controls/Menu/MenuBar.cs b/ImGui.Forms/Controls/Menu/MenuBar.cs index e980fe1..70fb53b 100644 --- a/ImGui.Forms/Controls/Menu/MenuBar.cs +++ b/ImGui.Forms/Controls/Menu/MenuBar.cs @@ -5,7 +5,7 @@ namespace ImGui.Forms.Controls.Menu { - public class MenuBar + public abstract class MenuBar { private readonly bool _isMain; @@ -15,10 +15,6 @@ public class MenuBar public int Height => GetHeight(); - protected MenuBar() : this(false) - { - } - protected MenuBar(bool isMain) { _isMain = isMain; @@ -31,8 +27,8 @@ public void Update() ImGuiNET.ImGui.PushFont(fontPtr.Value); // Begin menu bar - bool isMenuOpen = _isMain ? - ImGuiNET.ImGui.BeginMainMenuBar() : + bool isMenuOpen = _isMain ? + ImGuiNET.ImGui.BeginMainMenuBar() : ImGuiNET.ImGui.BeginMenuBar(); if (isMenuOpen) { @@ -48,7 +44,7 @@ public void Update() foreach (var child in Items) child.UpdateEvents(); } - + if (fontPtr != null) ImGuiNET.ImGui.PopFont(); } @@ -59,9 +55,11 @@ private int GetHeight() if (fontPtr != null) ImGuiNET.ImGui.PushFont(fontPtr.Value); - // HINT: It's currently unknown where those 3 pixels come from, but they have to be added to get the correct size of the menu - var height = Items.Max(x => x.Height) + 3; - + var height = Items.Count > 0 + ? Items.Max(x => x.Height) : + // HINT: It's currently unknown where those 3 pixels come from, but they have to be added to get the correct size of the menu + (int)(TextMeasurer.GetCurrentLineHeight() + ImGuiNET.ImGui.GetStyle().FramePadding.Y * 2) + 3; + if (fontPtr != null) ImGuiNET.ImGui.PopFont(); diff --git a/ImGui.Forms/Controls/Menu/MenuBarButton.cs b/ImGui.Forms/Controls/Menu/MenuBarButton.cs index 038efd9..a41efa2 100644 --- a/ImGui.Forms/Controls/Menu/MenuBarButton.cs +++ b/ImGui.Forms/Controls/Menu/MenuBarButton.cs @@ -2,6 +2,7 @@ using ImGui.Forms.Localization; using ImGui.Forms.Models.IO; using ImGui.Forms.Resources; +using ImGuiNET; namespace ImGui.Forms.Controls.Menu { @@ -11,6 +12,8 @@ public class MenuBarButton : MenuBarItem public LocalizedString Text { get; set; } + public FontResource Font { get; set; } + public KeyCommand KeyAction { get; set; } public override int Height => GetHeight(); @@ -23,7 +26,7 @@ public class MenuBarButton : MenuBarItem #endregion - public MenuBarButton(LocalizedString text) + public MenuBarButton(LocalizedString text = default) { Text = text; } @@ -31,7 +34,7 @@ public MenuBarButton(LocalizedString text) protected override void UpdateInternal() { // Add menu button - if (ImGuiNET.ImGui.MenuItem(Text, Enabled) || IsKeyDown(KeyAction)) + if ((ImGuiNET.ImGui.MenuItem(Text, Enabled) || IsKeyDown(KeyAction)) && Enabled) // Execute click event, if set Clicked?.Invoke(this, EventArgs.Empty); } @@ -39,7 +42,7 @@ protected override void UpdateInternal() protected override void UpdateEventsInternal() { // Add menu button - if (IsKeyDown(KeyAction)) + if (IsKeyDown(KeyAction) && Enabled) // Execute click event, if set Clicked?.Invoke(this, EventArgs.Empty); } @@ -49,11 +52,24 @@ private int GetHeight() ApplyStyles(); var textSize = TextMeasurer.MeasureText(Text); - var height = (int)(textSize.Y + ImGuiNET.ImGui.GetStyle().FramePadding.Y); + var height = (int)(textSize.Y + ImGuiNET.ImGui.GetStyle().FramePadding.Y * 2); RemoveStyles(); return height; } + + protected override void ApplyStyles() + { + ImFontPtr? fontPtr = Font?.GetPointer(); + if (fontPtr != null) + ImGuiNET.ImGui.PushFont(fontPtr.Value); + } + + protected override void RemoveStyles() + { + if (Font?.GetPointer() != null) + ImGuiNET.ImGui.PopFont(); + } } } diff --git a/ImGui.Forms/Controls/Menu/MenuBarCheckBox.cs b/ImGui.Forms/Controls/Menu/MenuBarCheckBox.cs index 13bd366..f874835 100644 --- a/ImGui.Forms/Controls/Menu/MenuBarCheckBox.cs +++ b/ImGui.Forms/Controls/Menu/MenuBarCheckBox.cs @@ -1,6 +1,7 @@ using System; using ImGui.Forms.Localization; using ImGui.Forms.Resources; +using ImGuiNET; namespace ImGui.Forms.Controls.Menu { @@ -12,6 +13,8 @@ public class MenuBarCheckBox : MenuBarItem public LocalizedString Text { get; set; } + public FontResource Font { get; set; } + public override int Height => GetHeight(); public bool Checked @@ -32,7 +35,7 @@ public bool Checked #endregion - public MenuBarCheckBox(LocalizedString text) + public MenuBarCheckBox(LocalizedString text = default) { Text = text; } @@ -54,16 +57,29 @@ private int GetHeight() ApplyStyles(); var textSize = TextMeasurer.MeasureText(Text); - var height = (int)(textSize.Y + ImGuiNET.ImGui.GetStyle().FramePadding.Y); + var height = (int)(textSize.Y + ImGuiNET.ImGui.GetStyle().FramePadding.Y * 2); RemoveStyles(); return height; } + protected override void ApplyStyles() + { + ImFontPtr? fontPtr = Font?.GetPointer(); + if (fontPtr != null) + ImGuiNET.ImGui.PushFont(fontPtr.Value); + } + + protected override void RemoveStyles() + { + if (Font?.GetPointer() != null) + ImGuiNET.ImGui.PopFont(); + } + private void OnCheckedChanged() { - CheckChanged?.Invoke(this, new EventArgs()); + CheckChanged?.Invoke(this, EventArgs.Empty); } } } diff --git a/ImGui.Forms/Controls/Menu/MenuBarMenu.cs b/ImGui.Forms/Controls/Menu/MenuBarMenu.cs index a0361ad..1e1532b 100644 --- a/ImGui.Forms/Controls/Menu/MenuBarMenu.cs +++ b/ImGui.Forms/Controls/Menu/MenuBarMenu.cs @@ -12,6 +12,8 @@ public class MenuBarMenu : MenuBarItem public LocalizedString Text { get; set; } + public FontResource Font { get; set; } + public Vector2 Padding { get; set; } = new(8, 8); public IList<MenuBarItem> Items { get; } = new List<MenuBarItem>(); @@ -20,16 +22,13 @@ public class MenuBarMenu : MenuBarItem #endregion - public MenuBarMenu(LocalizedString text) + public MenuBarMenu(LocalizedString text = default) { Text = text; } protected override void UpdateInternal() { - ImGuiNET.ImGui.PushStyleVar(ImGuiStyleVar.WindowPadding, new Vector2(4, 5)); - ImGuiNET.ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, Padding); - if (ImGuiNET.ImGui.BeginMenu(Text, Enabled)) { foreach (var item in Items) @@ -39,13 +38,13 @@ protected override void UpdateInternal() } else UpdateEventsInternal(); - - - ImGuiNET.ImGui.PopStyleVar(2); } protected override void UpdateEventsInternal() { + if (!Enabled) + return; + foreach (var item in Items) item.UpdateEvents(); } @@ -55,11 +54,29 @@ private int GetHeight() ApplyStyles(); var textSize = TextMeasurer.MeasureText(Text); - var height = (int)(textSize.Y + ImGuiNET.ImGui.GetStyle().FramePadding.Y); + var height = (int)(textSize.Y + ImGuiNET.ImGui.GetStyle().FramePadding.Y * 2); RemoveStyles(); return height; } + + protected override void ApplyStyles() + { + ImGuiNET.ImGui.PushStyleVar(ImGuiStyleVar.WindowPadding, new Vector2(4, 5)); + ImGuiNET.ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, Padding); + + ImFontPtr? fontPtr = Font?.GetPointer(); + if (fontPtr != null) + ImGuiNET.ImGui.PushFont(fontPtr.Value); + } + + protected override void RemoveStyles() + { + if (Font?.GetPointer() != null) + ImGuiNET.ImGui.PopFont(); + + ImGuiNET.ImGui.PopStyleVar(2); + } } } diff --git a/ImGui.Forms/Controls/Menu/MenuBarRadio.cs b/ImGui.Forms/Controls/Menu/MenuBarRadio.cs index fbb8a62..65f6b43 100644 --- a/ImGui.Forms/Controls/Menu/MenuBarRadio.cs +++ b/ImGui.Forms/Controls/Menu/MenuBarRadio.cs @@ -19,6 +19,12 @@ public LocalizedString Text set => _radioMenu.Text = value; } + public FontResource Font + { + get => _radioMenu.Font; + set => _radioMenu.Font = value; + } + public IList<MenuBarCheckBox> CheckItems { get; } public MenuBarCheckBox SelectedItem { get; private set; } @@ -37,7 +43,7 @@ public override bool Enabled #endregion - public MenuBarRadio(LocalizedString text) + public MenuBarRadio(LocalizedString text = default) { var checkItems = new ObservableList<MenuBarCheckBox>(); checkItems.ItemAdded += CheckItems_ItemAdded; @@ -106,15 +112,15 @@ private void CheckItems_ItemSet(object sender, ItemEventArgs<MenuBarCheckBox> e) private void Item_CheckChanged(object sender, EventArgs e) { - foreach (var language in _radioMenu.Items.Cast<MenuBarCheckBox>()) + foreach (MenuBarCheckBox checkbox in _radioMenu.Items.Cast<MenuBarCheckBox>()) { - language.CheckChanged -= Item_CheckChanged; - language.Checked = language == sender; - language.CheckChanged += Item_CheckChanged; + checkbox.CheckChanged -= Item_CheckChanged; + checkbox.Checked = checkbox == sender; + checkbox.CheckChanged += Item_CheckChanged; } SelectedItem = (MenuBarCheckBox)sender; - SelectedItemChanged?.Invoke(this, new EventArgs()); + SelectedItemChanged?.Invoke(this, EventArgs.Empty); } } } diff --git a/ImGui.Forms/Controls/MultiLineTextBox.cs b/ImGui.Forms/Controls/MultiLineTextBox.cs index c720b1d..7d9f67c 100644 --- a/ImGui.Forms/Controls/MultiLineTextBox.cs +++ b/ImGui.Forms/Controls/MultiLineTextBox.cs @@ -12,6 +12,11 @@ public class MultiLineTextBox : Component #region Properties + /// <summary> + /// The size of this component. + /// </summary> + public Size Size { get; set; } = Size.Parent; + /// <summary> /// The text that was set or changed in this component. /// </summary> @@ -43,7 +48,7 @@ public string Text #endregion - public override Size GetSize() => Size.Parent; + public override Size GetSize() => Size; protected override void UpdateInternal(Rectangle contentRect) { diff --git a/ImGui.Forms/Controls/PictureBox.cs b/ImGui.Forms/Controls/PictureBox.cs index b5646ce..8f08656 100644 --- a/ImGui.Forms/Controls/PictureBox.cs +++ b/ImGui.Forms/Controls/PictureBox.cs @@ -1,5 +1,4 @@ -using System.Numerics; -using ImGui.Forms.Controls.Base; +using ImGui.Forms.Controls.Base; using ImGui.Forms.Models; using ImGui.Forms.Resources; using Veldrid; @@ -12,6 +11,8 @@ public class PictureBox : Component #region Properties + public Size Size { get; set; } = Size.Content; + public ThemedImageResource Image { get => _baseImg; @@ -31,7 +32,15 @@ public PictureBox(ThemedImageResource image = default) public override Size GetSize() { - return new Size(_baseImg?.Width ?? 0, _baseImg?.Height ?? 0); + SizeValue width = Size.Width.IsContentAligned + ? SizeValue.Absolute(_baseImg?.Width ?? 0) + : Size.Width; + + SizeValue height = Size.Height.IsContentAligned + ? SizeValue.Absolute(_baseImg?.Height ?? 0) + : Size.Height; + + return new Size(width, height); } protected override void UpdateInternal(Rectangle contentRect) @@ -39,7 +48,7 @@ protected override void UpdateInternal(Rectangle contentRect) if (_baseImg == null || (nint)_baseImg == nint.Zero) return; - ImGuiNET.ImGui.Image((nint)Image, new Vector2(_baseImg.Width, _baseImg.Height)); + ImGuiNET.ImGui.Image((nint)Image, contentRect.Size); } } } diff --git a/ImGui.Forms/Controls/ProgressBar.cs b/ImGui.Forms/Controls/ProgressBar.cs index c419a9e..26e4815 100644 --- a/ImGui.Forms/Controls/ProgressBar.cs +++ b/ImGui.Forms/Controls/ProgressBar.cs @@ -30,10 +30,7 @@ public class ProgressBar : Component #endregion - public override Size GetSize() - { - return Size; - } + public override Size GetSize() => Size; protected override void UpdateInternal(Rectangle contentRect) { diff --git a/ImGui.Forms/Controls/TabControl.cs b/ImGui.Forms/Controls/TabControl.cs index 171fba4..7268599 100644 --- a/ImGui.Forms/Controls/TabControl.cs +++ b/ImGui.Forms/Controls/TabControl.cs @@ -93,8 +93,8 @@ protected override async void UpdateInternal(Rectangle contentRect) // Draw content of tab page var yPos = (int)ImGuiNET.ImGui.GetCursorPosY(); - var pageWidth = page.Content.GetWidth(contentRect.Width); - var pageHeight = page.Content.GetHeight(contentRect.Height - yPos); + var pageWidth = page.Content.GetWidth(contentRect.Width, contentRect.Height - yPos); + var pageHeight = page.Content.GetHeight(contentRect.Width, contentRect.Height - yPos); if (ImGuiNET.ImGui.BeginChild($"##{Id}-in", new Vector2(pageWidth, pageHeight), ImGuiChildFlags.None, ImGuiWindowFlags.None)) page.Content.Update(new Rectangle(contentRect.X, contentRect.Y + yPos, pageWidth, pageHeight)); diff --git a/ImGui.Forms/Controls/TextBox.cs b/ImGui.Forms/Controls/TextBox.cs index 951eefb..f741a31 100644 --- a/ImGui.Forms/Controls/TextBox.cs +++ b/ImGui.Forms/Controls/TextBox.cs @@ -131,13 +131,13 @@ protected override void UpdateInternal(Rectangle contentRect) { if (ImGuiNET.ImGui.InputText($"##{Id}", ref _text, MaxCharacters, flags)) OnTextChanged(); + } - // Check if InputText is active and lost focus - if (!ImGuiNET.ImGui.IsItemActive() && _activePreviousFrame) - OnFocusLost(); + // Check if item is active and lost focus + if (!ImGuiNET.ImGui.IsItemActive() && _activePreviousFrame) + OnFocusLost(); - _activePreviousFrame = ImGuiNET.ImGui.IsItemActive(); - } + _activePreviousFrame = ImGuiNET.ImGui.IsItemActive(); if (isReadonly || !enabled) ImGuiNET.ImGui.PopStyleColor(3); diff --git a/ImGui.Forms/Controls/Tree/TreeNode.cs b/ImGui.Forms/Controls/Tree/TreeNode.cs index 7cbf6d8..9ac9a33 100644 --- a/ImGui.Forms/Controls/Tree/TreeNode.cs +++ b/ImGui.Forms/Controls/Tree/TreeNode.cs @@ -42,7 +42,7 @@ public class TreeNode<TNodeData> : TreeNode public bool IsRoot => Parent?._isRoot ?? true; - public new IList<TreeNode<TNodeData>> Nodes => _nodes; + public IList<TreeNode<TNodeData>> Nodes => _nodes; public TreeNode<TNodeData> Parent { get; private set; } diff --git a/ImGui.Forms/Controls/ZoomablePictureBox.cs b/ImGui.Forms/Controls/ZoomablePictureBox.cs index db56355..08f9601 100644 --- a/ImGui.Forms/Controls/ZoomablePictureBox.cs +++ b/ImGui.Forms/Controls/ZoomablePictureBox.cs @@ -29,6 +29,8 @@ public ThemedImageResource Image } } + public Size Size { get; set; } = Size.Parent; + #endregion #region Events @@ -42,10 +44,7 @@ public ZoomablePictureBox(ThemedImageResource image = default) Image = image; } - public override Size GetSize() - { - return Size.Parent; - } + public override Size GetSize() => Size; public void Zoom(float scale) { diff --git a/ImGui.Forms/Form.cs b/ImGui.Forms/Form.cs index d09903e..52b51fb 100644 --- a/ImGui.Forms/Form.cs +++ b/ImGui.Forms/Form.cs @@ -132,8 +132,8 @@ internal void Update() ImGuiNET.ImGui.SetWindowPos(new Vector2(0, menuHeight)); var contentPos = ImGuiNET.ImGui.GetCursorScreenPos(); - var contentWidth = Content?.GetWidth(Width - (int)Padding.X * 2) ?? 0; - var contentHeight = Content?.GetHeight(Height - (int)Padding.Y * 2 - menuHeight) ?? 0; + var contentWidth = Content?.GetWidth(Width - (int)Padding.X * 2, Height - (int)Padding.Y * 2 - menuHeight) ?? 0; + var contentHeight = Content?.GetHeight(Width - (int)Padding.X * 2, Height - (int)Padding.Y * 2 - menuHeight) ?? 0; var contentRect = new Veldrid.Rectangle((int)contentPos.X, (int)contentPos.Y, contentWidth, contentHeight); Content?.Update(contentRect); @@ -175,7 +175,7 @@ internal void OnLoad() internal async Task OnClosing(ClosingEventArgs e) { - if (Closing == null) + if (Closing == null) return; await Closing?.Invoke(this, e); diff --git a/ImGui.Forms/ImGui.Forms.nuspec b/ImGui.Forms/ImGui.Forms.nuspec index 73aad12..223a9ae 100644 --- a/ImGui.Forms/ImGui.Forms.nuspec +++ b/ImGui.Forms/ImGui.Forms.nuspec @@ -2,7 +2,7 @@ <package > <metadata> <id>Imgui.Forms</id> - <version>1.1.8</version> + <version>1.2.0</version> <description>A WinForms-inspired object-oriented framework around Dear ImGui (https://github.com/ocornut/imgui)</description> <authors>onepiecefreak</authors> diff --git a/ImGui.Forms/Localization/BaseLocalizer.cs b/ImGui.Forms/Localization/BaseLocalizer.cs index c619613..2620a88 100644 --- a/ImGui.Forms/Localization/BaseLocalizer.cs +++ b/ImGui.Forms/Localization/BaseLocalizer.cs @@ -42,6 +42,12 @@ public void ChangeLocale(string locale) SetCurrentLocale(locale); } + public bool TryLocalize(string localizationId, out string localization, params object[] args) + { + localization = Localize(localizationId, args); + return localization != UndefinedValue; + } + public string Localize(string localizationId, params object[] args) { // Return localization of current locale diff --git a/ImGui.Forms/Localization/ILocalizer.cs b/ImGui.Forms/Localization/ILocalizer.cs index dfbb199..6b45d53 100644 --- a/ImGui.Forms/Localization/ILocalizer.cs +++ b/ImGui.Forms/Localization/ILocalizer.cs @@ -11,6 +11,7 @@ public interface ILocalizer string GetLanguageName(string locale); void ChangeLocale(string locale); + bool TryLocalize(string localizationId, out string localization, params object[] args); string Localize(string localizationId, params object[] args); } } diff --git a/ImGui.Forms/Modals/IO/ComboInputBox.cs b/ImGui.Forms/Modals/IO/ComboInputBox.cs index f75f26f..0b14a1d 100644 --- a/ImGui.Forms/Modals/IO/ComboInputBox.cs +++ b/ImGui.Forms/Modals/IO/ComboInputBox.cs @@ -1,21 +1,18 @@ using System; using System.Linq; -using System.Numerics; using System.Threading.Tasks; using ImGui.Forms.Controls; using ImGui.Forms.Controls.Layouts; using ImGui.Forms.Localization; using ImGui.Forms.Models; using ImGui.Forms.Models.IO; +using ImGui.Forms.Resources; using Veldrid; namespace ImGui.Forms.Modals.IO { public class ComboInputBox : Modal { - private const string Ok_ = "Ok"; - private const string Cancel_ = "Cancel"; - private const int ButtonWidth_ = 75; private readonly ComboBox<LocalizedString> _comboBox; @@ -26,15 +23,24 @@ private ComboInputBox(LocalizedString caption, LocalizedString text, LocalizedSt { #region Controls - var okButton = new Button { Text = Ok_, Width = ButtonWidth_ }; - var cancelButton = new Button { Text = Cancel_, Width = ButtonWidth_ }; + var okButton = new Button { Text = LocalizationResources.Ok(), Width = ButtonWidth_ }; + var cancelButton = new Button { Text = LocalizationResources.Cancel(), Width = ButtonWidth_ }; var label = new Label { Text = text }; - _comboBox = new ComboBox<LocalizedString>(); + _comboBox = new ComboBox<LocalizedString> { MaxShowItems = 2 }; foreach (LocalizedString item in items) _comboBox.Items.Add(item); + var buttonLayout = new StackLayout { Alignment = Alignment.Horizontal, Size = Size.Content, ItemSpacing = 5 }; + buttonLayout.Items.Add(okButton); + buttonLayout.Items.Add(cancelButton); + + var mainLayout = new StackLayout { Alignment = Alignment.Vertical, Size = Size.Content, ItemSpacing = 5 }; + mainLayout.Items.Add(label); + mainLayout.Items.Add(_comboBox); + mainLayout.Items.Add(new StackItem(buttonLayout) { HorizontalAlignment = HorizontalAlignment.Right }); + #endregion #region Events @@ -59,33 +65,14 @@ private ComboInputBox(LocalizedString caption, LocalizedString text, LocalizedSt Result = DialogResult.Cancel; Caption = caption; - Content = new StackLayout - { - Alignment = Alignment.Vertical, - ItemSpacing = 4, - Size = new Size(SizeValue.Parent, SizeValue.Content), - Items = - { - label, - _comboBox, - new StackLayout - { - Alignment = Alignment.Horizontal, - HorizontalAlignment = HorizontalAlignment.Right, - ItemSpacing = 4, - Size=new Size(SizeValue.Parent, SizeValue.Content), - Items = - { - okButton, - cancelButton - } - } - } - }; - - var width = Application.Instance.MainForm.Width * .8f; - var height = Content.GetHeight(Application.Instance.MainForm.Height); - Size = new Vector2(width, height); + Content = mainLayout; + + var mainSize = Application.Instance.MainForm.Size; + + var width = mainLayout.GetWidth((int)mainSize.X, (int)mainSize.Y); + var height = mainLayout.GetHeight((int)mainSize.X, (int)mainSize.Y); + + Size = new Size(SizeValue.Absolute(width), SizeValue.Absolute(height)); } private void ComboBox_SelectedItemChanged(object sender, EventArgs e) diff --git a/ImGui.Forms/Modals/IO/InputBox.cs b/ImGui.Forms/Modals/IO/InputBox.cs index dc6fa99..e3607f0 100644 --- a/ImGui.Forms/Modals/IO/InputBox.cs +++ b/ImGui.Forms/Modals/IO/InputBox.cs @@ -1,38 +1,35 @@ using System; -using System.Numerics; using System.Threading.Tasks; using ImGui.Forms.Controls; using ImGui.Forms.Controls.Layouts; using ImGui.Forms.Localization; using ImGui.Forms.Models; using ImGui.Forms.Models.IO; +using ImGui.Forms.Resources; using Veldrid; namespace ImGui.Forms.Modals.IO { public class InputBox : Modal { - private const string Ok_ = "Ok"; - private const string Cancel_ = "Cancel"; - private const int ButtonWidth_ = 75; private readonly TextBox _textBox; public string Input { get; private set; } - private InputBox(LocalizedString caption, LocalizedString text, string preset, LocalizedString? placeHolder, int maxCharacters) + private InputBox(LocalizedString caption, LocalizedString text, string preset, LocalizedString? placeholder, int maxCharacters) { #region Controls - var okButton = new Button { Text = Ok_, Width = ButtonWidth_ }; - var cancelButton = new Button { Text = Cancel_, Width = ButtonWidth_ }; + var okButton = new Button { Text = LocalizationResources.Ok(), Width = ButtonWidth_ }; + var cancelButton = new Button { Text = LocalizationResources.Cancel(), Width = ButtonWidth_ }; var label = new Label { Text = text }; _textBox = new TextBox(); - if (placeHolder != null) - _textBox.Placeholder = placeHolder.Value; + if (placeholder != null) + _textBox.Placeholder = placeholder.Value; if (maxCharacters >= 0) _textBox.MaxCharacters = (uint)maxCharacters; @@ -63,7 +60,7 @@ private InputBox(LocalizedString caption, LocalizedString text, string preset, L { Alignment = Alignment.Vertical, ItemSpacing = 4, - Size = new Size(SizeValue.Parent, SizeValue.Content), + Size = Size.WidthAlign, Items = { label, @@ -73,7 +70,7 @@ private InputBox(LocalizedString caption, LocalizedString text, string preset, L Alignment = Alignment.Horizontal, HorizontalAlignment = HorizontalAlignment.Right, ItemSpacing = 4, - Size=new Size(SizeValue.Parent, SizeValue.Content), + Size=Size.WidthAlign, Items = { okButton, @@ -83,9 +80,8 @@ private InputBox(LocalizedString caption, LocalizedString text, string preset, L } }; - var width = Application.Instance.MainForm.Width * .8f; - var height = Content.GetHeight(Application.Instance.MainForm.Height); - Size = new Vector2(width, height); + var height = Content.GetHeight(Application.Instance.MainForm.Width, Application.Instance.MainForm.Height); + Size = new Size(SizeValue.Relative(.8f), SizeValue.Absolute(height)); } private void TextBox_TextChanged(object sender, EventArgs e) diff --git a/ImGui.Forms/Modals/IO/OpenFileDialog.cs b/ImGui.Forms/Modals/IO/OpenFileDialog.cs index 222ae3c..155dc33 100644 --- a/ImGui.Forms/Modals/IO/OpenFileDialog.cs +++ b/ImGui.Forms/Modals/IO/OpenFileDialog.cs @@ -3,12 +3,12 @@ using System.Globalization; using System.IO; using System.Linq; -using System.Numerics; using ImGui.Forms.Controls; using ImGui.Forms.Controls.Layouts; using ImGui.Forms.Controls.Lists; using ImGui.Forms.Controls.Tree; using ImGui.Forms.Models; +using ImGui.Forms.Resources; using ImGui.Forms.Support; using ImGuiNET; @@ -16,6 +16,9 @@ namespace ImGui.Forms.Modals.IO { public class OpenFileDialog : Modal { + private const string ItemTypeDirectory_ = "D"; + private const string ItemTypeFile_ = "F"; + private History<string> _dictHistory; private string _currentDir; @@ -53,19 +56,19 @@ public OpenFileDialog() _backBtn = new ArrowButton { Direction = ImGuiDir.Left, Enabled = false }; _forBtn = new ArrowButton { Direction = ImGuiDir.Right, Enabled = false }; _dirTextBox = new TextBox { Width = .7f }; - _searchTextBox = new TextBox { Width = .3f, Placeholder = "Search..." }; + _searchTextBox = new TextBox { Width = .3f, Placeholder = LocalizationResources.Search() }; _selectedFileTextBox = new TextBox { Width = 1f }; _fileFilters = new ComboBox<FileFilter>(); _treeView = new TreeView<string> { Size = new Size(.3f, 1f) }; _fileTable = new DataTable<FileEntry> { Size = new Size(.7f, 1f) }; - _fileTable.Columns.Add(new DataTableColumn<FileEntry>(x => x.Name, "Name")); - _fileTable.Columns.Add(new DataTableColumn<FileEntry>(x => x.Type, "Type")); - _fileTable.Columns.Add(new DataTableColumn<FileEntry>(x => x.DateModified.ToString(CultureInfo.CurrentCulture), "Date modified")); + _fileTable.Columns.Add(new DataTableColumn<FileEntry>(x => x.Name, LocalizationResources.ItemName())); + _fileTable.Columns.Add(new DataTableColumn<FileEntry>(x => x.Type, LocalizationResources.ItemType())); + _fileTable.Columns.Add(new DataTableColumn<FileEntry>(x => x.DateModified.ToString(CultureInfo.CurrentCulture), LocalizationResources.ItemDateModified())); - var cnlBtn = new Button { Text = "Cancel", Width = 80 }; - _openBtn = new Button { Text = "Open", Width = 80, Enabled = false }; + var cancelBtn = new Button { Text = LocalizationResources.Cancel(), Width = 80 }; + _openBtn = new Button { Text = LocalizationResources.Ok(), Width = 80, Enabled = false }; #endregion @@ -85,16 +88,14 @@ public OpenFileDialog() _fileFilters.SelectedItemChanged += _fileFilters_SelectedItemChanged; - cnlBtn.Clicked += CnlBtn_Clicked; + cancelBtn.Clicked += CnlBtn_Clicked; _openBtn.Clicked += OpenBtnClicked; #endregion Result = DialogResult.Cancel; - var width = (int)Math.Ceiling(Application.Instance.MainForm.Width * .9f); - var height = (int)Math.Ceiling(Application.Instance.MainForm.Height * .8f); - Size = new Vector2(width, height); + Size = new Size(SizeValue.Relative(.9f), SizeValue.Relative(.8f)); Content = new StackLayout { @@ -105,7 +106,7 @@ public OpenFileDialog() new StackLayout { Alignment = Alignment.Horizontal, - Size = new Size(SizeValue.Parent, SizeValue.Content), + Size = Size.WidthAlign, ItemSpacing = 5, Items = { @@ -128,12 +129,11 @@ public OpenFileDialog() new StackLayout { Alignment = Alignment.Horizontal, - HorizontalAlignment = HorizontalAlignment.Right, - Size = new Size(7f, SizeValue.Content), + Size = Size.WidthAlign, ItemSpacing = 5, Items = { - new Label {Text = "File name:"}, + new Label {Text = LocalizationResources.SelectedFile()}, _selectedFileTextBox, _fileFilters } @@ -142,12 +142,12 @@ public OpenFileDialog() { Alignment = Alignment.Horizontal, HorizontalAlignment = HorizontalAlignment.Right, - Size = new Size(SizeValue.Parent, SizeValue.Content), + Size = Size.WidthAlign, ItemSpacing = 5, Items = { new StackItem(_openBtn), - new StackItem(cnlBtn) + new StackItem(cancelBtn) } } } @@ -164,8 +164,10 @@ protected override void ShowInternal() _selectedFileTextBox.Text = InitialFileName; // Initialize file tree and file view + var desktopDir = Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory); var userDir = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); - _treeView.Nodes.Add(new TreeNode<string> { Text = "Desktop", Data = Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory), Nodes = { new TreeNode<string>() } }); + + _treeView.Nodes.Add(new TreeNode<string> { Text = Path.GetFileName(desktopDir), Data = desktopDir, Nodes = { new TreeNode<string>() } }); _treeView.Nodes.Add(new TreeNode<string> { Text = Path.GetFileName(userDir), Data = userDir, Nodes = { new TreeNode<string>() } }); UpdateFileView(); @@ -192,7 +194,7 @@ private string GetNodePath(TreeNode<string> node) private IEnumerable<FileEntry> GetDirectories(string dir) { return Directory.EnumerateDirectories(dir).Select(x => new FileEntry - { Name = Path.GetFileName(x), Type = "D", DateModified = Directory.GetLastAccessTime(x) }); + { Name = Path.GetFileName(x), Type = ItemTypeDirectory_, DateModified = Directory.GetLastAccessTime(x) }); } private IEnumerable<FileEntry> GetFiles(string dir) @@ -205,9 +207,13 @@ private IEnumerable<FileEntry> GetFiles(string dir) // Apply extension filter, if set if (!useSearch && _fileFilters.SelectedItem != null) - files = files.Where(f => GetFileExtensions(f).Any(e => _fileFilters.SelectedItem.Content.Extensions.Contains(e))); + { + IList<string> extensions = _fileFilters.SelectedItem.Content.Extensions; + if (extensions.All(e => e != "*")) + files = files.Where(f => GetFileExtensions(f).Any(e => extensions.Contains(e))); + } - return files.Select(x => new FileEntry { Name = Path.GetFileName(x), Type = "F", DateModified = File.GetLastWriteTime(x) }); + return files.Select(x => new FileEntry { Name = Path.GetFileName(x), Type = ItemTypeFile_, DateModified = File.GetLastWriteTime(x) }); } private string[] GetFileExtensions(string filePath) @@ -254,7 +260,7 @@ private void UpdateButtonEnablement() private void Filters_ItemAdded(object sender, ItemEventArgs<FileFilter> e) { - _fileFilters.Items.Add(new ComboBoxItem<FileFilter>(e.Item)); + _fileFilters.Items.Add(new DropDownItem<FileFilter>(e.Item)); } private void Filters_ItemRemoved(object sender, ItemEventArgs<FileFilter> e) @@ -264,12 +270,12 @@ private void Filters_ItemRemoved(object sender, ItemEventArgs<FileFilter> e) private void Filters_ItemInserted(object sender, ItemEventArgs<FileFilter> e) { - _fileFilters.Items.Insert(e.Index, new ComboBoxItem<FileFilter>(e.Item)); + _fileFilters.Items.Insert(e.Index, new DropDownItem<FileFilter>(e.Item)); } private void Filters_ItemSet(object sender, ItemEventArgs<FileFilter> e) { - _fileFilters.Items[e.Index] = new ComboBoxItem<FileFilter>(e.Item); + _fileFilters.Items[e.Index] = new DropDownItem<FileFilter>(e.Item); } private void _fileFilters_SelectedItemChanged(object sender, EventArgs e) diff --git a/ImGui.Forms/Modals/IO/SaveFileDialog.cs b/ImGui.Forms/Modals/IO/SaveFileDialog.cs index 01dca2b..2a23a4a 100644 --- a/ImGui.Forms/Modals/IO/SaveFileDialog.cs +++ b/ImGui.Forms/Modals/IO/SaveFileDialog.cs @@ -3,13 +3,13 @@ using System.Globalization; using System.IO; using System.Linq; -using System.Numerics; using System.Threading.Tasks; using ImGui.Forms.Controls; using ImGui.Forms.Controls.Layouts; using ImGui.Forms.Controls.Lists; using ImGui.Forms.Controls.Tree; using ImGui.Forms.Models; +using ImGui.Forms.Resources; using ImGui.Forms.Support; using ImGuiNET; @@ -17,6 +17,9 @@ namespace ImGui.Forms.Modals.IO { public class SaveFileDialog : Modal { + private const string ItemTypeDirectory_ = "D"; + private const string ItemTypeFile_ = "F"; + private History<string> _dictHistory; private string _currentDir; @@ -42,19 +45,19 @@ public SaveFileDialog(string filePath = null) _backBtn = new ArrowButton { Direction = ImGuiDir.Left, Enabled = false }; _forBtn = new ArrowButton { Direction = ImGuiDir.Right, Enabled = false }; _dirTextBox = new TextBox { Width = .7f }; - _searchTextBox = new TextBox { Width = .3f, Placeholder = "Search..." }; - _selectedFileTextBox = new TextBox { Width = .7f }; + _searchTextBox = new TextBox { Width = .3f, Placeholder = LocalizationResources.Search() }; + _selectedFileTextBox = new TextBox { Width = 1f }; _treeView = new TreeView<string> { Size = new Size(.3f, 1f) }; _treeView.NodeExpanded += _treeView_NodeExpanded; _fileTable = new DataTable<FileEntry> { Size = new Size(.7f, 1f) }; - _fileTable.Columns.Add(new DataTableColumn<FileEntry>(x => x.Name, "Name")); - _fileTable.Columns.Add(new DataTableColumn<FileEntry>(x => x.Type, "Type")); - _fileTable.Columns.Add(new DataTableColumn<FileEntry>(x => x.DateModified.ToString(CultureInfo.CurrentCulture), "Date modified")); + _fileTable.Columns.Add(new DataTableColumn<FileEntry>(x => x.Name, LocalizationResources.ItemName())); + _fileTable.Columns.Add(new DataTableColumn<FileEntry>(x => x.Type, LocalizationResources.ItemType())); + _fileTable.Columns.Add(new DataTableColumn<FileEntry>(x => x.DateModified.ToString(CultureInfo.CurrentCulture), LocalizationResources.ItemDateModified())); - var cnlBtn = new Button { Text = "Cancel", Width = 80 }; - _saveBtn = new Button { Text = "Save", Width = 80, Enabled = !string.IsNullOrEmpty(filePath) }; + var cancelBtn = new Button { Text = LocalizationResources.Cancel(), Width = 80 }; + _saveBtn = new Button { Text = LocalizationResources.Save(), Width = 80, Enabled = !string.IsNullOrEmpty(filePath) }; #endregion @@ -78,14 +81,12 @@ public SaveFileDialog(string filePath = null) _fileTable.DoubleClicked += _fileTable_DoubleClicked; _fileTable.SelectedRowsChanged += _fileTable_SelectedRowsChanged; - cnlBtn.Clicked += CnlBtn_Clicked; + cancelBtn.Clicked += CnlBtn_Clicked; _saveBtn.Clicked += SaveBtnClicked; #endregion - var width = (int)Math.Ceiling(Application.Instance.MainForm.Width * .9f); - var height = (int)Math.Ceiling(Application.Instance.MainForm.Height * .8f); - Size = new Vector2(width, height); + Size = new Size(SizeValue.Relative(.9f), SizeValue.Relative(.8f)); Content = new StackLayout { @@ -97,7 +98,7 @@ public SaveFileDialog(string filePath = null) new StackLayout { Alignment = Alignment.Horizontal, - Size = new Size(SizeValue.Parent, SizeValue.Content), + Size = Size.WidthAlign, ItemSpacing = 5, Items = { @@ -124,12 +125,11 @@ public SaveFileDialog(string filePath = null) new StackLayout { Alignment = Alignment.Horizontal, - HorizontalAlignment = HorizontalAlignment.Right, - Size = new Size(7f, SizeValue.Content), + Size = Size.WidthAlign, ItemSpacing = 5, Items = { - new Label {Text = "File name:"}, + new Label {Text = LocalizationResources.SelectedFile()}, _selectedFileTextBox } }, @@ -139,12 +139,12 @@ public SaveFileDialog(string filePath = null) { Alignment = Alignment.Horizontal, HorizontalAlignment = HorizontalAlignment.Right, - Size = new Size(SizeValue.Parent, SizeValue.Content), + Size = Size.WidthAlign, ItemSpacing = 5, Items = { new StackItem(_saveBtn), - new StackItem(cnlBtn) + new StackItem(cancelBtn) } } } @@ -160,10 +160,13 @@ protected override void ShowInternal() _dirTextBox.Text = _currentDir; // Initialize file tree and file view + var desktopDir = Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory); var userDir = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); - _treeView.Nodes.Add(new TreeNode<string> { Text = "Desktop", Data = Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory), Nodes = { new TreeNode<string>() } }); + + _treeView.Nodes.Add(new TreeNode<string> { Text = Path.GetFileName(desktopDir), Data = desktopDir, Nodes = { new TreeNode<string>() } }); _treeView.Nodes.Add(new TreeNode<string> { Text = Path.GetFileName(userDir), Data = userDir, Nodes = { new TreeNode<string>() } }); + UpdateFileView(); } @@ -188,7 +191,7 @@ private string GetNodePath(TreeNode<string> node) private IEnumerable<FileEntry> GetDirectories(string dir) { return Directory.EnumerateDirectories(dir).Select(x => new FileEntry - { Name = Path.GetFileName(x), Type = "D", DateModified = Directory.GetLastAccessTime(x) }); + { Name = Path.GetFileName(x), Type = ItemTypeDirectory_, DateModified = Directory.GetLastAccessTime(x) }); } private IEnumerable<FileEntry> GetFiles(string dir) @@ -197,7 +200,7 @@ private IEnumerable<FileEntry> GetFiles(string dir) var searchTerm = string.IsNullOrEmpty(_searchTextBox.Text) ? "*" : _searchTextBox.Text; var files = Directory.EnumerateFiles(dir, searchTerm); - return files.Select(x => new FileEntry { Name = Path.GetFileName(x), Type = "F", DateModified = File.GetLastWriteTime(x) }); + return files.Select(x => new FileEntry { Name = Path.GetFileName(x), Type = ItemTypeFile_, DateModified = File.GetLastWriteTime(x) }); } private async Task<bool> ShouldOverwrite(string path) @@ -205,7 +208,7 @@ private async Task<bool> ShouldOverwrite(string path) if (!File.Exists(path)) return true; - return await MessageBox.ShowYesNoAsync("File exists", $"Do you want to overwrite file {Path.GetFileName(path)}?") != DialogResult.No; + return await MessageBox.ShowYesNoAsync(LocalizationResources.ReplaceFileCaption(), LocalizationResources.ReplaceFileText(Path.GetFileName(path))) != DialogResult.No; } #endregion diff --git a/ImGui.Forms/Modals/IO/SelectFolderDialog.cs b/ImGui.Forms/Modals/IO/SelectFolderDialog.cs index 60a6be6..554f4be 100644 --- a/ImGui.Forms/Modals/IO/SelectFolderDialog.cs +++ b/ImGui.Forms/Modals/IO/SelectFolderDialog.cs @@ -2,22 +2,16 @@ using System.IO; using System.Linq; using System.Numerics; -using System.Threading.Tasks; using ImGui.Forms.Controls; using ImGui.Forms.Controls.Layouts; using ImGui.Forms.Controls.Tree; using ImGui.Forms.Models; +using ImGui.Forms.Resources; namespace ImGui.Forms.Modals.IO { public class SelectFolderDialog : Modal { - private const string Ok_ = "Ok"; - private const string Cancel_ = "Cancel"; - private const string NewFolder_ = "Create new folder"; - - private const int ButtonWidth_ = 75; - private TreeView<string> _treeView; private Button _newFolderButton; @@ -32,9 +26,9 @@ public SelectFolderDialog() _treeView = new TreeView<string>(); - _newFolderButton = new Button { Text = NewFolder_, Enabled = false }; - _okButton = new Button { Text = Ok_, Width = ButtonWidth_, Enabled = false }; - _cancelButton = new Button { Text = Cancel_, Width = ButtonWidth_ }; + _newFolderButton = new Button { Text = LocalizationResources.CreateFolderCaption(), Enabled = false, Padding = new Vector2(5, 2) }; + _okButton = new Button { Text = LocalizationResources.Ok(), Width = 80, Enabled = false }; + _cancelButton = new Button { Text = LocalizationResources.Cancel(), Width = 80 }; #endregion @@ -51,9 +45,7 @@ public SelectFolderDialog() #region Main content - var width = Application.Instance.MainForm.Width * .9f; - var height = Application.Instance.MainForm.Height * .8f; - Size = new Vector2(width, height); + Size = new Size(SizeValue.Relative(.9f), SizeValue.Relative(.8f)); Result = DialogResult.Cancel; Content = new StackLayout @@ -96,8 +88,10 @@ protected override void ShowInternal() Directory = GetInitialDirectory(); // Initialize file tree and file view + var desktopDir = Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory); var userDir = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); - _treeView.Nodes.Add(new TreeNode<string> { Text = "Desktop", Data = Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory), Nodes = { new TreeNode<string>() } }); + + _treeView.Nodes.Add(new TreeNode<string> { Text = Path.GetFileName(desktopDir), Data = desktopDir, Nodes = { new TreeNode<string>() } }); _treeView.Nodes.Add(new TreeNode<string> { Text = Path.GetFileName(userDir), Data = userDir, Nodes = { new TreeNode<string>() } }); foreach (var drive in DriveInfo.GetDrives()) @@ -109,7 +103,7 @@ protected override void ShowInternal() private async void _newFolderButton_Clicked(object sender, EventArgs e) { var path = GetNodePath(_treeView.SelectedNode); - var newFolderName = await InputBox.ShowAsync("Create folder", "New folder name:"); + var newFolderName = await InputBox.ShowAsync(LocalizationResources.CreateFolderCaption(), LocalizationResources.CreateFolderText()); if (string.IsNullOrEmpty(newFolderName)) return; @@ -171,7 +165,7 @@ private void _treeView_NodeExpanded(object sender, NodeEventArgs<string> e) #endregion - #region Support method + #region Support methods private string GetInitialDirectory() { diff --git a/ImGui.Forms/Modals/MessageBox.cs b/ImGui.Forms/Modals/MessageBox.cs index 34ae2c4..e6aa9c4 100644 --- a/ImGui.Forms/Modals/MessageBox.cs +++ b/ImGui.Forms/Modals/MessageBox.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Numerics; using System.Threading.Tasks; using ImGui.Forms.Controls; using ImGui.Forms.Controls.Base; @@ -13,11 +12,6 @@ namespace ImGui.Forms.Modals { public class MessageBox : Modal { - private const string Ok_ = "Ok"; - private const string Yes_ = "Yes"; - private const string No_ = "No"; - private const string Cancel_ = "Cancel"; - private const int ButtonWidth_ = 75; private MessageBox(LocalizedString caption, LocalizedString text, MessageBoxType type, MessageBoxButton buttons) @@ -76,27 +70,28 @@ private void CreateLayout(LocalizedString caption, LocalizedString text, Message var msgType = GetTypeImage(type); var msgLabel = new Label { Text = text }; - var messageLayout = new StackLayout { Alignment = Alignment.Horizontal, Size = Models.Size.Content, ItemSpacing = 5 }; + var messageLayout = new StackLayout { Alignment = Alignment.Horizontal, Size = Size.Content, ItemSpacing = 5 }; if (msgType != null) messageLayout.Items.Add(new StackItem(msgType) { VerticalAlignment = VerticalAlignment.Center }); messageLayout.Items.Add(new StackItem(msgLabel) { VerticalAlignment = VerticalAlignment.Center }); // Prepare buttons - var buttonLayout = new StackLayout { Alignment = Alignment.Horizontal, HorizontalAlignment = HorizontalAlignment.Right, Size = new Models.Size(SizeValue.Parent,SizeValue.Content), ItemSpacing = 5 }; + var buttonLayout = new StackLayout { Alignment = Alignment.Horizontal, Size = Size.Content, ItemSpacing = 5 }; foreach (var button in GetButtons(buttons)) buttonLayout.Items.Add(button); // Prepare main layout - var mainLayout = new StackLayout { Alignment = Alignment.Vertical, ItemSpacing = 5, Size = Models.Size.Content }; - mainLayout.Items.Add(new StackItem(messageLayout)); - mainLayout.Items.Add(buttonLayout); + var mainLayout = new StackLayout { Alignment = Alignment.Vertical, Size = Size.Content, ItemSpacing = 5 }; + mainLayout.Items.Add(messageLayout); + mainLayout.Items.Add(new StackItem(buttonLayout) { HorizontalAlignment = HorizontalAlignment.Right }); // Add modal - var mainSize = new Vector2(Application.Instance.MainForm.Width, Application.Instance.MainForm.Height); + var mainSize = Application.Instance.MainForm.Size; + + var width = mainLayout.GetWidth((int)mainSize.X, (int)mainSize.Y) + (msgType?.GetWidth((int)mainSize.X, (int)mainSize.Y) ?? 0) + messageLayout.ItemSpacing; + var height = mainLayout.GetHeight((int)mainSize.X, (int)mainSize.Y); - var width = msgLabel.GetWidth((int)mainSize.X) + (msgType?.GetWidth((int)mainSize.X) ?? 0) + messageLayout.ItemSpacing; - var height = mainLayout.GetHeight((int)mainSize.Y); - Size = new Vector2(width, height); + Size = new Size(SizeValue.Absolute(width), SizeValue.Absolute(height)); Content = mainLayout; } @@ -126,15 +121,16 @@ private IEnumerable<Button> GetButtons(MessageBoxButton buttons) { if (buttons.HasFlag(MessageBoxButton.Ok)) { - var okButton = new Button { Text = Ok_, Width = ButtonWidth_ }; - okButton.Clicked += (s, e) => Close(); + var okButton = new Button { Text = LocalizationResources.Ok(), Width = SizeValue.Absolute(ButtonWidth_) }; + okButton.Clicked += (_, _) => Close(); yield return okButton; } + if (buttons.HasFlag(MessageBoxButton.Yes)) { - var yesButton = new Button { Text = Yes_, Width = ButtonWidth_ }; - yesButton.Clicked += (s, e) => + var yesButton = new Button { Text = LocalizationResources.Yes(), Width = SizeValue.Absolute(ButtonWidth_) }; + yesButton.Clicked += (_, _) => { Result = DialogResult.Yes; Close(); @@ -142,10 +138,11 @@ private IEnumerable<Button> GetButtons(MessageBoxButton buttons) yield return yesButton; } + if (buttons.HasFlag(MessageBoxButton.No)) { - var noButton = new Button { Text = No_, Width = ButtonWidth_ }; - noButton.Clicked += (s, e) => + var noButton = new Button { Text = LocalizationResources.No(), Width = SizeValue.Absolute(ButtonWidth_) }; + noButton.Clicked += (_, _) => { Result = DialogResult.No; Close(); @@ -153,16 +150,17 @@ private IEnumerable<Button> GetButtons(MessageBoxButton buttons) yield return noButton; } + if (buttons.HasFlag(MessageBoxButton.Cancel)) { - var noButton = new Button { Text = Cancel_, Width = ButtonWidth_ }; - noButton.Clicked += (s, e) => + var cancelButton = new Button { Text = LocalizationResources.Cancel(), Width = SizeValue.Absolute(ButtonWidth_) }; + cancelButton.Clicked += (_, _) => { Result = DialogResult.Cancel; Close(); }; - yield return noButton; + yield return cancelButton; } } } diff --git a/ImGui.Forms/Modals/Modal.cs b/ImGui.Forms/Modals/Modal.cs index 4320864..f36239b 100644 --- a/ImGui.Forms/Modals/Modal.cs +++ b/ImGui.Forms/Modals/Modal.cs @@ -1,5 +1,4 @@ -using System; -using System.Numerics; +using System.Numerics; using System.Threading; using System.Threading.Tasks; using ImGui.Forms.Controls.Base; @@ -12,7 +11,7 @@ namespace ImGui.Forms.Modals { - public class Modal : Component + public abstract class Modal : Component { private CancellationTokenSource _tokenSource; private bool _shouldClose; @@ -30,19 +29,9 @@ public class Modal : Component protected DialogResult Result { get; set; } - public Vector2 Size { get; set; } = new(200, 80); - public int Width => (int)Size.X; - public int Height => (int)Size.Y; + public Size Size { get; set; } = new(SizeValue.Absolute(200), SizeValue.Absolute(80)); - public override Size GetSize() - { - return new Size(Width, Height); - } - - public int GetHeaderHeight() - { - return TextMeasurer.GetCurrentLineHeight(withDescent: true) + 6; - } + public override Size GetSize() => Size; protected override async void UpdateInternal(Rectangle contentRect) { @@ -116,6 +105,11 @@ public async void Close() _shouldClose = !await ShouldCancelClose(); } + private int GetHeaderHeight() + { + return TextMeasurer.GetCurrentLineHeight(withDescent: true) + 6; + } + // HINT: Only gets executed if _shouldClose is set to true private async Task CloseCore() { @@ -145,10 +139,13 @@ internal static void DrawModal(Modal modal) var form = Application.Instance.MainForm; - var modalPos = new Vector2((form.Width - modal.Width) / 2f, (form.Height - modal.Height - modal.GetHeaderHeight()) / 2f); + var modalWidth = GetDimension(modal.Size.Width, form.Width); + var modalHeight = GetDimension(modal.Size.Height, form.Height); + + var modalPos = new Vector2((form.Width - modalWidth) / 2f, (form.Height - modalHeight - modal.GetHeaderHeight()) / 2f); var contentPos = modalPos + new Vector2(form.Padding.X, modal.GetHeaderHeight() + form.Padding.Y); - var contentSize = new Vector2(modal.Width, modal.Height); + var contentSize = new Vector2(modalWidth, modalHeight); var modalSize = contentSize + new Vector2(form.Padding.X * 2, modal.GetHeaderHeight() + form.Padding.Y * 2); ImGuiNET.ImGui.SetNextWindowPos(modalPos); diff --git a/ImGui.Forms/Resources/LocalizationResources.cs b/ImGui.Forms/Resources/LocalizationResources.cs new file mode 100644 index 0000000..8d4a421 --- /dev/null +++ b/ImGui.Forms/Resources/LocalizationResources.cs @@ -0,0 +1,63 @@ +using ImGui.Forms.Localization; + +namespace ImGui.Forms.Resources +{ + static class LocalizationResources + { + private const string Ok_ = "Ok"; + private const string Cancel_ = "Cancel"; + private const string Yes_ = "Yes"; + private const string No_ = "No"; + private const string Save_ = "Save"; + private const string SearchPlaceholder_ = "Search..."; + private const string ItemName_ = "Name"; + private const string ItemType_ = "Type"; + private const string ItemDateModified_ = "Date Modified"; + private const string SelectedFile_ = "File Name:"; + private const string ReplaceFileCaption_ = "File exists"; + private const string ReplaceFileText_ = "Do you want to overwrite file {0}?"; + private const string CreateFolderCaption_ = "Create folder"; + private const string CreateFolderText_ = "New folder name:"; + + private const string OkIdentifier_ = "ImGui.Button.Ok"; + private const string CancelIdentifier_ = "ImGui.Button.Cancel"; + private const string YesIdentifier_ = "ImGui.Button.Yes"; + private const string NoIdentifier_ = "ImGui.Button.No"; + private const string SaveIdentifier_ = "ImGui.Button.Save"; + private const string SearchIdentifier_ = "ImGui.FileDialog.Search"; + private const string ItemNameIdentifier_ = "ImGui.FileDialog.Name"; + private const string ItemTypeIdentifier_ = "ImGui.FileDialog.Type"; + private const string ItemDateModifiedIdentifier_ = "ImGui.FileDialog.DateModified"; + private const string SelectedFileIdentifier_ = "ImGui.FileDialog.SelectedFile"; + private const string ReplaceFileCaptionIdentifier_ = "ImGui.FileDialog.ReplaceFile.Caption"; + private const string ReplaceFileTextIdentifier_ = "ImGui.FileDialog.ReplaceFile.Text"; + private const string CreateFolderCaptionIdentifier_ = "ImGui.FileDialog.CreateFolder.Caption"; + private const string CreateFolderTextIdentifier_ = "ImGui.FileDialog.CreateFolder.Text"; + + public static string Ok() => Localize(OkIdentifier_, Ok_); + public static string Cancel() => Localize(CancelIdentifier_, Cancel_); + public static string Yes() => Localize(YesIdentifier_, Yes_); + public static string No() => Localize(NoIdentifier_, No_); + public static string Save() => Localize(SaveIdentifier_, Save_); + public static string Search() => Localize(SearchIdentifier_, SearchPlaceholder_); + public static string ItemName() => Localize(ItemNameIdentifier_, ItemName_); + public static string ItemType() => Localize(ItemTypeIdentifier_, ItemType_); + public static string ItemDateModified() => Localize(ItemDateModifiedIdentifier_, ItemDateModified_); + public static string SelectedFile() => Localize(SelectedFileIdentifier_, SelectedFile_); + public static string ReplaceFileCaption() => Localize(ReplaceFileCaptionIdentifier_, ReplaceFileCaption_); + public static string ReplaceFileText(string path) => Localize(ReplaceFileTextIdentifier_, ReplaceFileText_, path); + public static string CreateFolderCaption() => Localize(CreateFolderCaptionIdentifier_, CreateFolderCaption_); + public static string CreateFolderText() => Localize(CreateFolderTextIdentifier_, CreateFolderText_); + + private static string Localize(string localizationId, string fallback, params object[] args) + { + ILocalizer localizer = Application.Instance.Localizer; + var text = string.Empty; + + if (!localizer?.TryLocalize(localizationId, out text, args) ?? true) + text = fallback; + + return text; + } + } +} diff --git a/ImGui.Forms/Resources/TextMeasurer.cs b/ImGui.Forms/Resources/TextMeasurer.cs index 66b5836..4de1ad4 100644 --- a/ImGui.Forms/Resources/TextMeasurer.cs +++ b/ImGui.Forms/Resources/TextMeasurer.cs @@ -1,7 +1,6 @@ using ImGuiNET; using System; using System.Numerics; -using static System.Net.Mime.MediaTypeNames; namespace ImGui.Forms.Resources {