Skip to content

Commit

Permalink
Rename ComboBox to DropDownBox;
Browse files Browse the repository at this point in the history
Introduce actual ComboBox with text input nad proposals;
  • Loading branch information
onepiecefreak3 committed Aug 25, 2024
1 parent 3fc5872 commit 3ae0e05
Show file tree
Hide file tree
Showing 3 changed files with 214 additions and 27 deletions.
159 changes: 133 additions & 26 deletions ImGui.Forms/Controls/ComboBox.cs
Original file line number Diff line number Diff line change
@@ -1,24 +1,40 @@
using System;
using System.Collections.Generic;
using System.Linq;
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;
using Veldrid;

// Initial code: https://github.com/ocornut/imgui/issues/2057

namespace ImGui.Forms.Controls
{
public class ComboBox<TItem> : Component
{
private string _input = string.Empty;

public IList<ComboBoxItem<TItem>> Items { get; } = new List<ComboBoxItem<TItem>>();

public ComboBoxItem<TItem> SelectedItem { get; set; }

public SizeValue Width { get; set; } = SizeValue.Content;

#region Events
/// <summary>
/// Get or set the max count of characters allowed in the input.
/// </summary>
public uint MaxCharacters { get; set; } = 256;

/// <summary>
/// Get or set the max count of items in the drop down.
/// </summary>
public uint MaxShowItems { get; set; } = 10;

#region Events

public event EventHandler SelectedItemChanged;

#endregion
Expand All @@ -34,47 +50,138 @@ public override Size GetSize()
return new Size(width, height);
}

protected override void UpdateInternal(Rectangle contentRect)
protected override unsafe void UpdateInternal(Rectangle contentRect)
{
ImGuiNET.ImGui.SetNextItemWidth(contentRect.Width);
//Check if both strings matches
uint maxShowItems = MaxShowItems;
if (maxShowItems == 0)
maxShowItems = (uint)Items.Count;

ImGuiNET.ImGui.PushID(Id);

var selectedName = SelectedItem?.Name ?? string.Empty;
if (ImGuiNET.ImGui.BeginCombo($"##combo{Id}", selectedName))
bool isFinal = ImGuiNET.ImGui.InputText("##in", ref _input, MaxCharacters, ImGuiInputTextFlags.CallbackAlways | ImGuiInputTextFlags.EnterReturnsTrue, Propose);
if (isFinal)
{
for (var i = 0; i < Items.Count; i++)
ComboBoxItem<TItem> selectedItem = Items.FirstOrDefault(i => i.Name == _input);
if (SelectedItem != selectedItem)
{
if (ImGuiNET.ImGui.Selectable(Items[i].Name, SelectedItem == Items[i]))
{
var hasChanged = SelectedItem != Items[i];
SelectedItem = Items[i];
SelectedItem = selectedItem;
OnSelectedItemChanged();
}
}

if (hasChanged)
OnSelectedItemChanged();
ImGuiNET.ImGui.OpenPopupOnItemClick("combobox"); // Enable right-click
Vector2 pos = ImGuiNET.ImGui.GetItemRectMin();
Vector2 size = ImGuiNET.ImGui.GetItemRectSize();

ImGuiNET.ImGui.SameLine(0, 0);
if (ImGuiNET.ImGui.ArrowButton("##openCombo", ImGuiDir.Down))
{
ImGuiNET.ImGui.OpenPopup("combobox");
}
ImGuiNET.ImGui.OpenPopupOnItemClick("combobox"); // Enable right-click

pos.Y += size.Y;
size.X += ImGuiNET.ImGui.GetItemRectSize().Y;
size.Y += 5 + size.Y * maxShowItems;
ImGuiNET.ImGui.SetNextWindowPos(pos);
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)
{
if (!ImGuiNET.ImGui.Selectable(item.Name))
continue;

_input = item.Name;

if (SelectedItem != item)
{
SelectedItem = item;
OnSelectedItemChanged();
}
}

ImGuiNET.ImGui.EndCombo();
ImGuiNET.ImGui.EndPopup();
}

ImGuiNET.ImGui.PopID();
}

private void OnSelectedItemChanged()
{
SelectedItemChanged?.Invoke(this, new EventArgs());
SelectedItemChanged?.Invoke(this, EventArgs.Empty);
}
}

public class ComboBoxItem<TItem>
{
public TItem Content { get; }

public LocalizedString Name { get; }

public ComboBoxItem(TItem content, LocalizedString name = default)
private bool Identical(string buf, string item)
{
Content = content;
Name = name.IsEmpty ? (LocalizedString)content.ToString() : name;
//Check if the item length is shorter or equal --> exclude
if (buf.Length >= item.Length)
return false;

for (var i = 0; i < buf.Length; ++i)
// set the current pos if matching or return the pos if not
if (buf[i] != item[i])
return false;

// Complete match
// and the item size is greater --> include
return true;
}

public static implicit operator ComboBoxItem<TItem>(TItem o) => new(o);
private unsafe int Propose(ImGuiInputTextCallbackData* data)
{
var dataPtr = new ImGuiInputTextCallbackDataPtr(data);

// We don't want to "preselect" anything
if (dataPtr.BufTextLen == 0)
return 0;

// We need to give the user a chance to remove wrong input
if (dataPtr.EventKey == ImGuiKey.Backspace)
{
// We delete the last char automatically, since it is what the user wants to delete, but only if there is something (selected/marked/hovered)
// FIXME: This worked fine, when not used as helper function
if (data->SelectionEnd == data->SelectionStart)
return 0;

if (data->BufTextLen <= 0)
return 0; //...and the buffer isn't empty

if (data->CursorPos > 0) //...and the cursor not at pos 0
dataPtr.DeleteChars(data->CursorPos - 1, 1);

return 0;
}

if (dataPtr.EventKey == ImGuiKey.Delete)
return 0;

string bufferString = Marshal.PtrToStringUTF8(dataPtr.Buf);
foreach (ComboBoxItem<TItem> item in Items)
{
if (!Identical(bufferString, item.Name))
continue;

int cursor = data->CursorPos;

//Insert the first match
dataPtr.DeleteChars(0, data->BufTextLen);
dataPtr.InsertChars(0, item.Name);

//Reset the cursor position
data->CursorPos = cursor;

//Select the text, so the user can simply go on writing
data->SelectionStart = cursor;
data->SelectionEnd = data->BufTextLen;

break;
}

return 0;
}
}
}
80 changes: 80 additions & 0 deletions ImGui.Forms/Controls/DropDownBox.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
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
{
public IList<ComboBoxItem<TItem>> Items { get; } = new List<ComboBoxItem<TItem>>();

public ComboBoxItem<TItem> SelectedItem { get; set; }

public SizeValue Width { get; set; } = SizeValue.Content;

#region Events

public event EventHandler SelectedItemChanged;

#endregion

public override Size GetSize()
{
var maxWidth = Items.Select(x => FontResource.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 = FontResource.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);
}
}
2 changes: 1 addition & 1 deletion ImGui.Forms/ImGui.Forms.nuspec
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<package >
<metadata>
<id>Imgui.Forms</id>
<version>1.0.60</version>
<version>1.0.61</version>
<description>A WinForms-inspired object-oriented framework around Dear ImGui (https://github.com/ocornut/imgui)</description>

<authors>onepiecefreak</authors>
Expand Down

0 comments on commit 3ae0e05

Please sign in to comment.