Replaced C style array manipulation

This commit is contained in:
Bobby Lucero 2025-07-12 00:12:50 -04:00
parent e194ec7a3f
commit cdc54751a3
3 changed files with 111 additions and 239 deletions

View File

@ -149,37 +149,37 @@ namespace MicroUI
[System.Flags]
public enum Options
{
AlignCenter = 1 << 0, // 1
AlignRight = 1 << 1, // 2
NoInteract = 1 << 2, // 4
NoFrame = 1 << 3, // 8
NoResize = 1 << 4, // 16
NoScroll = 1 << 5, // 32
NoClose = 1 << 6, // 64
NoTitle = 1 << 7, // 128
HoldFocus = 1 << 8, // 256
AutoSize = 1 << 9, // 512
Popup = 1 << 10, // 1024
Closed = 1 << 11, // 2048
Expanded = 1 << 12 // 4096
AlignCenter = 1 << 0,
AlignRight = 1 << 1,
NoInteract = 1 << 2,
NoFrame = 1 << 3,
NoResize = 1 << 4,
NoScroll = 1 << 5,
NoClose = 1 << 6,
NoTitle = 1 << 7,
HoldFocus = 1 << 8,
AutoSize = 1 << 9,
Popup = 1 << 10,
Closed = 1 << 11,
Expanded = 1 << 12
}
[System.Flags]
public enum MouseButton
{
Left = 1 << 0, // 1
Right = 1 << 1, // 2
Middle = 1 << 2 // 4
Left = 1 << 0,
Right = 1 << 1,
Middle = 1 << 2
}
[System.Flags]
public enum KeyModifiers
{
Shift = 1 << 0, // 1
Ctrl = 1 << 1, // 2
Alt = 1 << 2, // 4
Backspace = 1 << 3, // 8
Return = 1 << 4 // 16
Shift = 1 << 0,
Ctrl = 1 << 1,
Alt = 1 << 2,
Backspace = 1 << 3,
Return = 1 << 4
}
public struct MuVec2
@ -364,7 +364,7 @@ namespace MicroUI
public class MuStyle
{
public object? Font { get; set; } // Replace 'object' with actual font type as needed
public object? Font { get; set; }
public MuVec2 Size { get; set; }
public int Padding { get; set; }
public int Spacing { get; set; }
@ -386,16 +386,13 @@ namespace MicroUI
public class MuContext
{
// Callbacks
public TextWidthDelegate? TextWidth;
public TextWidthDelegate? TextWidth;
public TextHeightDelegate? TextHeight;
public DrawFrameDelegate? DrawFrame;
// Styles
public MuStyle Style { get; set; } = new MuStyle();
// IDs and last widget info
public mu_Id Hover { get; set; } // assuming mu_Id is uint
public mu_Id Hover { get; set; }
public mu_Id Focus { get; set; }
public mu_Id LastId { get; set; }
public MuRect LastRect { get; set; }
@ -403,16 +400,13 @@ namespace MicroUI
public int UpdatedFocus { get; set; }
public int Frame { get; set; }
// Containers
public MuContainer? HoverRoot { get; set; }
public MuContainer? NextHoverRoot { get; set; }
public MuContainer? ScrollTarget { get; set; }
// Number edit buffer
private char[] numberEditBuf = new char[Constants.MaxFormatLength]; // e.g. 127 or 128
public mu_Id NumberEdit { get; set; }
// Stacks
public string numberEditText = "";
public FixedStack<MuCommand> CommandList { get; } = new FixedStack<MuCommand>(Constants.CommandListSize);
public FixedStack<MuContainer> RootList { get; } = new FixedStack<MuContainer>(Constants.RootListSize);
public FixedStack<MuContainer> ContainerStack { get; } = new FixedStack<MuContainer>(Constants.ContainerStackSize);
@ -420,12 +414,10 @@ namespace MicroUI
public FixedStack<uint> IdStack { get; } = new FixedStack<uint>(Constants.IdStackSize);
public FixedStack<MuLayout> LayoutStack { get; } = new FixedStack<MuLayout>(Constants.LayoutStackSize);
// Retained State Pools
public MuPoolItem[] ContainerPool { get; } = new MuPoolItem[Constants.ContainerPoolSize];
public MuContainer[] Containers { get; } = new MuContainer[Constants.ContainerPoolSize];
public MuPoolItem[] TreeNodePool { get; } = new MuPoolItem[Constants.TreeNodePoolSize];
// Input State
public MuVec2 MousePos { get; set; }
public MuVec2 LastMousePos { get; set; }
public MuVec2 MouseDelta { get; set; }
@ -434,7 +426,7 @@ namespace MicroUI
public int MousePressed { get; set; }
public int KeyDown { get; set; }
public int KeyPressed { get; set; }
public char[] inputText = new char[32];
public string inputText = "";
}
public static class MuAssert
@ -473,20 +465,20 @@ namespace MicroUI
ThumbSize = 8,
Colors = new MuColor[]
{
new MuColor(230, 230, 230, 255), // MU_COLOR_TEXT
new MuColor(25, 25, 25, 255), // MU_COLOR_BORDER
new MuColor(50, 50, 50, 255), // MU_COLOR_WINDOWBG
new MuColor(25, 25, 25, 255), // MU_COLOR_TITLEBG
new MuColor(240, 240, 240, 255), // MU_COLOR_TITLETEXT
new MuColor(0, 0, 0, 0), // MU_COLOR_PANELBG
new MuColor(75, 75, 75, 255), // MU_COLOR_BUTTON
new MuColor(95, 95, 95, 255), // MU_COLOR_BUTTONHOVER
new MuColor(115, 115, 115, 255), // MU_COLOR_BUTTONFOCUS
new MuColor(30, 30, 30, 255), // MU_COLOR_BASE
new MuColor(35, 35, 35, 255), // MU_COLOR_BASEHOVER
new MuColor(40, 40, 40, 255), // MU_COLOR_BASEFOCUS
new MuColor(43, 43, 43, 255), // MU_COLOR_SCROLLBASE
new MuColor(30, 30, 30, 255) // MU_COLOR_SCROLLTHUMB
new MuColor(230, 230, 230, 255),
new MuColor(25, 25, 25, 255),
new MuColor(50, 50, 50, 255),
new MuColor(25, 25, 25, 255),
new MuColor(240, 240, 240, 255),
new MuColor(0, 0, 0, 0),
new MuColor(75, 75, 75, 255),
new MuColor(95, 95, 95, 255),
new MuColor(115, 115, 115, 255),
new MuColor(30, 30, 30, 255),
new MuColor(35, 35, 35, 255),
new MuColor(40, 40, 40, 255),
new MuColor(43, 43, 43, 255),
new MuColor(30, 30, 30, 255)
}
};
@ -521,13 +513,10 @@ namespace MicroUI
public static void DrawFrame(MuContext ctx, MuRect rect, ColorType colorId)
{
// Clamp color ID to prevent index out of range
int colorIndex = MathUtil.Clamp((int)colorId, 0, (int)ColorType.Max - 1);
// Draw filled rectangle with given color
MuCommandList.DrawRect(ctx, rect, ctx.Style.Colors[colorIndex]);
// Early return for certain color IDs (skip border)
if (colorId == ColorType.ScrollBase ||
colorId == ColorType.ScrollThumb ||
colorId == ColorType.TitleBg)
@ -535,7 +524,6 @@ namespace MicroUI
return;
}
// Draw border if alpha > 0
if (ctx.Style.Colors[(int)ColorType.Border].A > 0)
{
MuCommandList.DrawBox(ctx, ExpandRect(rect, 1), ctx.Style.Colors[(int)ColorType.Border]);
@ -544,21 +532,16 @@ namespace MicroUI
public static void Init(MuContext ctx)
{
// Zeroing is not needed — C# classes are automatically zeroed/null-initialized.
// Assign the draw function delegate
ctx.DrawFrame = DrawFrame;
// Copy the default style (make sure it's cloned, not shared!)
ctx.Style = DefaultStyle;
// Initialize containers array with actual MuContainer objects
for (int i = 0; i < ctx.Containers.Length; i++)
{
ctx.Containers[i] = new MuContainer();
}
// Initialize container pool items
for (int i = 0; i < ctx.ContainerPool.Length; i++)
{
ctx.ContainerPool[i] = new MuPoolItem(0, 0);
@ -616,7 +599,7 @@ namespace MicroUI
}
ctx.KeyPressed = 0;
ctx.inputText[0] = '\0';
ctx.inputText = "";
ctx.MousePressed = 0;
ctx.ScrollDelta = new MuVec2(0, 0);
ctx.LastMousePos = ctx.MousePos;
@ -636,13 +619,11 @@ namespace MicroUI
else
{
var prev = ctx.RootList.Items[i - 1];
// Set previous container's tail jump destination to current container's head + 1
((MuJumpCommand)ctx.CommandList.Items[prev.TailIndex]).DestinationIndex = cnt.HeadIndex + 1;
}
if (i == n - 1)
{
// Set last container's tail jump to the end of the command list
((MuJumpCommand)ctx.CommandList.Items[cnt.TailIndex]).DestinationIndex = ctx.CommandList.Index;
}
}
@ -707,42 +688,35 @@ namespace MicroUI
{
MuRect cr = GetClipRect(ctx);
// Completely outside clip rect
if (r.X > cr.X + cr.W || r.X + r.W < cr.X ||
r.Y > cr.Y + cr.H || r.Y + r.H < cr.Y)
{
return ClipMode.All; // MU_CLIP_ALL
return ClipMode.All;
}
// Completely inside clip rect
if (r.X >= cr.X && r.X + r.W <= cr.X + cr.W &&
r.Y >= cr.Y && r.Y + r.H <= cr.Y + cr.H)
{
return 0; // no clipping
return 0;
}
// Partially clipped
return ClipMode.Part; // MU_CLIP_PART
return ClipMode.Part;
}
public static void PushLayout(MuContext ctx, MuRect body, MuVec2 scroll)
{
MuLayout layout = new MuLayout();
// Adjust body position by subtracting scroll offset
layout.Body = new MuRect(
body.X - scroll.X,
body.Y - scroll.Y,
body.W,
body.H);
// Initialize max to very negative values (like C's -0x1000000)
layout.Max = new MuVec2(-0x1000000, -0x1000000);
// Push the layout struct onto the layout stack
ctx.LayoutStack.Push(layout);
// Call mu_layout_row with count=1, widths pointer to width var, height=0
int[] widths = { 0 };
MuLayoutUtil.LayoutRow(ctx, 1, widths, 0);
}
@ -765,7 +739,6 @@ namespace MicroUI
layout.Max.Y - layout.Body.Y
);
// Pop container, layout, and id
ctx.ContainerStack.Pop();
ctx.LayoutStack.Pop();
PopId(ctx);
@ -786,7 +759,6 @@ namespace MicroUI
public static MuContainer? GetContainer(MuContext ctx, uint id, int opt)
{
// Try to get existing container from pool
int idx = MuPool.Get(ctx, ctx.ContainerPool, id);
if (idx >= 0)
{
@ -797,17 +769,14 @@ namespace MicroUI
return ctx.Containers[idx];
}
// If we're looking for a closed container and it doesn't exist, return null
if ((opt & (int)Options.Closed) != 0)
{
return null;
}
// Container not found in pool: init new container
idx = MuPool.Init(ctx, ctx.ContainerPool, id);
var cnt = ctx.Containers[idx];
// Reset all fields (equivalent to memset(cnt, 0, sizeof(*cnt)))
cnt.HeadIndex = -1;
cnt.TailIndex = -1;
cnt.Rect = new MuRect(0, 0, 0, 0);
@ -845,7 +814,6 @@ namespace MicroUI
{
int n = -1;
int f = ctx.Frame;
// Find the item with the oldest last_update
for (int i = 0; i < items.Length; i++)
{
if (items[i].LastUpdate < f)
@ -904,21 +872,7 @@ namespace MicroUI
public static void InputText(MuContext ctx, string text)
{
// Find the current length of text in the buffer (up to first null terminator)
int len = 0;
while (len < ctx.inputText.Length && ctx.inputText[len] != '\0')
len++;
int size = text.Length;
if (len + size + 1 > ctx.inputText.Length) // +1 for null terminator
throw new Exception("Input text buffer overflow");
// Copy text into the buffer at the current position
for (int i = 0; i < size && (len + i) < ctx.inputText.Length - 1; i++)
ctx.inputText[len + i] = text[i];
// Null-terminate
ctx.inputText[len + size] = '\0';
ctx.inputText += text;
}
}
@ -939,16 +893,14 @@ namespace MicroUI
var cmd = commands[index];
if (cmd is MuJumpCommand jump)
{
// Jump to the destination index
if (jump.DestinationIndex < 0 || jump.DestinationIndex >= count)
return null; // Invalid jump, end iteration
return null;
index = jump.DestinationIndex;
continue;
}
// Return the current command and advance index
return commands[index++];
}
return null; // End of command list
return null;
}
public static MuJumpCommand PushJump(MuContext ctx, int destinationIndex = -1)
@ -965,7 +917,6 @@ namespace MicroUI
public static void DrawRect(MuContext ctx, MuRect rect, MuColor color)
{
// Intersect with current clip rect
rect = MicroUI.IntersectRects(rect, MicroUI.GetClipRect(ctx));
if (rect.W > 0 && rect.H > 0)
{
@ -992,10 +943,8 @@ namespace MicroUI
if (clipped == ClipMode.All) return;
if (clipped == ClipMode.Part) SetClip(ctx, MicroUI.GetClipRect(ctx));
// Add command
MuCommandList.Push(ctx, new MuTextCommand(font, pos, color, str));
// Reset clipping if it was set
if (clipped != 0) SetClip(ctx, MicroUI.UnclippedRect);
}
@ -1011,7 +960,6 @@ namespace MicroUI
}
}
// Layout next type enum (matches C enum { RELATIVE = 1, ABSOLUTE = 2 })
enum LayoutNextType
{
Relative = 1,
@ -1028,10 +976,8 @@ namespace MicroUI
public static void LayoutEndColumn(MuContext ctx)
{
// Get the child layout (top of stack)
ref var b = ref MicroUI.GetLayout(ctx);
ctx.LayoutStack.Pop();
// Get the parent layout (now top of stack)
ref var a = ref MicroUI.GetLayout(ctx);
a.Position = new MuVec2(
@ -1056,7 +1002,7 @@ namespace MicroUI
}
layout.Items = items;
layout.Position = new MuVec2(layout.Indent, layout.NextRow);
layout.Size = new MuVec2(layout.Size.X, height); // Only Y is updated
layout.Size = new MuVec2(layout.Size.X, height);
layout.ItemIndex = 0;
}
@ -1098,17 +1044,14 @@ namespace MicroUI
}
else
{
// handle next row
if (layout.ItemIndex == layout.Items)
{
LayoutRow(ctx, layout.Items, null, layout.Size.Y);
}
// position
res.X = layout.Position.X;
res.Y = layout.Position.Y;
// size
res.W = layout.Items > 0 ? layout.Widths[layout.ItemIndex] : layout.Size.X;
res.H = layout.Size.Y;
if (res.W == 0) res.W = style.Size.X + style.Padding * 2;
@ -1119,18 +1062,15 @@ namespace MicroUI
layout.ItemIndex++;
}
// update position
layout.Position = new MuVec2(
layout.Position.X + res.W + style.Spacing,
layout.Position.Y
);
layout.NextRow = MathUtil.Max(layout.NextRow, res.Y + res.H + style.Spacing);
// apply body offset
res.X += layout.Body.X;
res.Y += layout.Body.Y;
// update max position
layout.Max = new MuVec2(
MathUtil.Max(layout.Max.X, res.X + res.W),
MathUtil.Max(layout.Max.Y, res.Y + res.H)
@ -1146,52 +1086,37 @@ namespace MicroUI
public static void PushContainerBody(MuContext ctx, MuContainer cnt, MuRect body, int opt)
{
// If scrolling is enabled, handle scrollbars (stub for now)
if ((opt & (int)Options.NoScroll) == 0)
{
// TODO: Implement scrollbars(ctx, cnt, ref body);
}
// Push layout for the container body, with padding and scroll offset
MicroUI.PushLayout(ctx, MicroUI.ExpandRect(body, -ctx.Style.Padding), cnt.Scroll);
// Store the body rect in the container
cnt.Body = body;
}
public static void BeginRootContainer(MuContext ctx, MuContainer cnt)
{
// Push container onto container stack
ctx.ContainerStack.Push(cnt);
// Push container to roots list and push head command
ctx.RootList.Push(cnt);
cnt.HeadIndex = ctx.CommandList.Index; // Store the current command index as head
MuCommandList.PushJump(ctx); // Push a jump command (will be set up later)
cnt.HeadIndex = ctx.CommandList.Index;
MuCommandList.PushJump(ctx);
// Set as hover root if the mouse is overlapping this container and it has a
// higher zindex than the current hover root
if (MicroUI.RectOverlapsVec2(cnt.Rect, ctx.MousePos) &&
(ctx.NextHoverRoot == null || cnt.ZIndex > ctx.NextHoverRoot.ZIndex))
{
ctx.NextHoverRoot = cnt;
}
// Clipping is reset here in case a root-container is made within
// another root-container's begin/end block; this prevents the inner
// root-container being clipped to the outer
MicroUI.PushClipRect(ctx, MicroUI.UnclippedRect);
}
public static void EndRootContainer(MuContext ctx)
{
// Push tail 'goto' jump command and set head 'skip' command. The final steps
// on setting up these are done in MicroUI.End()
MuContainer cnt = MicroUI.GetCurrentContainer(ctx);
cnt.TailIndex = ctx.CommandList.Index; // Store the current command index as tail
MuCommandList.PushJump(ctx); // Push a jump command (will be set up later)
cnt.TailIndex = ctx.CommandList.Index;
MuCommandList.PushJump(ctx);
// Set the head jump command to point to the current command index
// (This will be finalized in MicroUI.End())
if (cnt.HeadIndex >= 0 && cnt.HeadIndex < ctx.CommandList.Index)
{
var headJump = ctx.CommandList.Items[cnt.HeadIndex] as MuJumpCommand;
@ -1201,47 +1126,38 @@ namespace MicroUI
}
}
// Pop base clip rect and container
MicroUI.PopClipRect(ctx);
MicroUI.PopContainer(ctx);
}
public static bool BeginWindowEx(MuContext ctx, string title, MuRect rect, int opt)
{
// 1. Get/Init Container
uint id = MicroUI.GetId(ctx, System.Text.Encoding.UTF8.GetBytes(title));
MuContainer? cnt = MicroUI.GetContainer(ctx, id, opt);
if (cnt == null || !cnt.Open) return false;
// 2. Push ID
MicroUI.PushId(ctx, System.Text.Encoding.UTF8.GetBytes(title));
// 3. Set Initial Rect
if (cnt.Rect.W == 0) cnt.Rect = rect;
// 4. Begin Root Container
MuControl.BeginRootContainer(ctx, cnt);
rect = cnt.Rect;
var body = rect;
// 5. Draw Window Frame
if ((opt & (int)Options.NoFrame) == 0)
{
ctx.DrawFrame?.Invoke(ctx, rect, ColorType.WindowBg);
}
// 6. Draw Title Bar
if ((opt & (int)Options.NoTitle) == 0)
{
var tr = rect;
tr.H = ctx.Style.TitleHeight;
ctx.DrawFrame?.Invoke(ctx, tr, ColorType.TitleBg);
// Title text and drag
uint titleId = MicroUI.GetId(ctx, System.Text.Encoding.UTF8.GetBytes("!title"));
MuControl.UpdateControl(ctx, titleId, tr, opt);
MuControl.DrawControlText(ctx, title, tr, ColorType.TitleText, opt);
// Move window if dragging title bar
if (titleId == ctx.Focus && (ctx.MouseDown & (int)MouseButton.Left) != 0)
{
cnt.Rect = new MuRect(
@ -1252,11 +1168,9 @@ namespace MicroUI
);
}
// Adjust body for title bar
body.Y += tr.H;
body.H -= tr.H;
// Close button
if ((opt & (int)Options.NoClose) == 0)
{
uint closeId = MicroUI.GetId(ctx, System.Text.Encoding.UTF8.GetBytes("!close"));
@ -1271,24 +1185,19 @@ namespace MicroUI
}
}
// 7. Push Container Body Layout
MuControl.PushContainerBody(ctx, cnt, body, opt); // Implement or comment out
MuControl.PushContainerBody(ctx, cnt, body, opt);
// 8. Resize handle
if ((opt & (int)Options.NoResize) == 0)
{
int sz = ctx.Style.TitleHeight;
uint resizeId = MicroUI.GetId(ctx, System.Text.Encoding.UTF8.GetBytes("!resize"));
var r = new MuRect(rect.X + rect.W - sz, rect.Y + rect.H - sz, sz, sz);
MuControl.UpdateControl(ctx, resizeId, r, opt);
// Chevron/arrow design for resize handle
int pad = 4;
var baseColor = ctx.Style.Colors[(int)ColorType.Base];
// Horizontal bar (bottom edge)
MuCommandList.Push(ctx, new MuRectCommand(
new MuRect(r.X + r.W - pad - 8, r.Y + r.H - pad - 2, 8, 2), baseColor));
// Vertical bar (right edge)
MuCommandList.Push(ctx, new MuRectCommand(
new MuRect(r.X + r.W - pad - 2, r.Y + r.H - pad - 8, 2, 8), baseColor));
if (resizeId == ctx.Focus && (ctx.MouseDown & (int)MouseButton.Left) != 0)
@ -1302,7 +1211,6 @@ namespace MicroUI
}
}
// 9. Auto-size
if ((opt & (int)Options.AutoSize) != 0)
{
var r = MicroUI.GetLayout(ctx).Body;
@ -1314,16 +1222,13 @@ namespace MicroUI
);
}
// 10. Popup close
if ((opt & (int)Options.Popup) != 0 && ctx.MousePressed != 0 && ctx.HoverRoot != cnt)
{
cnt.Open = false;
}
// 11. Push Clip Rect
MicroUI.PushClipRect(ctx, cnt.Body);
// 12. Return Active
return true;
}
@ -1333,21 +1238,18 @@ namespace MicroUI
MuControl.EndRootContainer(ctx);
}
// Helper functions for controls
public static void DrawControlFrame(MuContext ctx, uint id, MuRect rect, ColorType colorId, int opt)
{
if ((opt & (int)Options.NoFrame) != 0) { return; }
// Adjust color based on state: normal, hover, focus
if (ctx.Focus == id)
{
colorId += 2; // Focus state
colorId += 2;
}
else if (ctx.Hover == id)
{
colorId += 1; // Hover state
colorId += 1;
}
// else: normal state (colorId unchanged)
ctx.DrawFrame?.Invoke(ctx, rect, colorId);
}
@ -1391,7 +1293,6 @@ namespace MicroUI
private static bool InHoverRoot(MuContext ctx)
{
// Check if we're in the hover root container
return ctx.HoverRoot == null || ctx.HoverRoot == MicroUI.GetCurrentContainer(ctx);
}
@ -1439,13 +1340,11 @@ namespace MicroUI
MuRect r = MuLayoutUtil.LayoutNext(ctx);
UpdateControl(ctx, id, r, opt);
// Handle click
if ((ctx.MousePressed & (int)MouseButton.Left) != 0 && ctx.Focus == id)
{
res |= ResultFlags.Submit;
}
// Draw
DrawControlFrame(ctx, id, r, ColorType.Button, opt);
if (!string.IsNullOrEmpty(label))
{
@ -1464,7 +1363,6 @@ namespace MicroUI
ResultFlags res = 0;
uint id;
// Create ID that includes both label and state
if (!string.IsNullOrEmpty(label))
{
var idData = System.Text.Encoding.UTF8.GetBytes(label + state.ToString());
@ -1478,14 +1376,12 @@ namespace MicroUI
MuRect r = MuLayoutUtil.LayoutNext(ctx);
UpdateControl(ctx, id, r, 0);
// Handle click
if ((ctx.MousePressed & (int)MouseButton.Left) != 0 && ctx.Focus == id)
{
state = !state;
res |= ResultFlags.Change;
}
// Draw checkbox box
var box = new MuRect(r.X, r.Y, r.H, r.H);
DrawControlFrame(ctx, id, box, ColorType.Base, 0);
@ -1494,7 +1390,6 @@ namespace MicroUI
MuCommandList.DrawIcon(ctx, (int)IconType.Check, box, ctx.Style.Colors[(int)ColorType.Text]);
}
// Draw label
if (!string.IsNullOrEmpty(label))
{
var labelRect = new MuRect(r.X + box.W, r.Y, r.W - box.W, r.H);
@ -1546,33 +1441,23 @@ namespace MicroUI
DrawControlText(ctx, text, r, ColorType.Text, 0);
}
// Helper method for number textbox functionality
private static bool NumberTextbox(MuContext ctx, ref float value, MuRect r, uint id)
{
// Check if we should enter text input mode (Shift+Click)
if ((ctx.MousePressed & (int)MouseButton.Left) != 0 &&
(ctx.KeyDown & (int)KeyModifiers.Shift) != 0 &&
ctx.Hover == id)
{
ctx.NumberEdit = id;
// Convert number to string for editing using the same format as C version
var numberStr = value.ToString("G3"); // MU_REAL_FMT is "%.3g"
// Copy to input buffer (simplified - in real implementation you'd need a proper buffer)
for (int i = 0; i < Math.Min(numberStr.Length, ctx.inputText.Length); i++)
{
ctx.inputText[i] = numberStr[i];
}
ctx.numberEditText = value.ToString("G3");
}
if (ctx.NumberEdit == id)
{
// For now, we'll implement a simplified textbox
// In a full implementation, you'd use TextboxRaw here
var result = TextboxRaw(ctx, ctx.inputText, ctx.inputText.Length, id, r, 0);
var result = TextboxRaw(ctx, ref ctx.numberEditText, id, r, 0);
if ((result & ResultFlags.Submit) != 0 || ctx.Focus != id)
{
// Parse the number back
if (float.TryParse(new string(ctx.inputText).Trim('\0'), out float newValue))
if (float.TryParse(ctx.numberEditText, out float newValue))
{
value = newValue;
}
@ -1580,51 +1465,31 @@ namespace MicroUI
}
else
{
return true; // Still in text input mode
return true;
}
}
return false;
}
public static MuResult TextboxRaw(MuContext ctx, char[] buf, int bufSize, uint id, MuRect r, int opt)
public static MuResult TextboxRaw(MuContext ctx, ref string value, uint id, MuRect r, int opt)
{
ResultFlags res = 0;
UpdateControl(ctx, id, r, opt | (int)Options.HoldFocus);
if (ctx.Focus == id)
{
// Handle text input
int len = 0;
while (len < bufSize && buf[len] != '\0') len++;
// Get actual length of text in input buffer (up to first null terminator)
int inputLen = 0;
while (inputLen < ctx.inputText.Length && ctx.inputText[inputLen] != '\0') inputLen++;
int n = Math.Min(bufSize - len - 1, inputLen);
if (n > 0)
if (ctx.inputText.Length > 0)
{
for (int i = 0; i < n; i++)
{
if (len + i < bufSize)
buf[len + i] = ctx.inputText[i];
}
len += n;
if (len < bufSize) buf[len] = '\0';
value += ctx.inputText;
res |= ResultFlags.Change;
}
// Handle backspace
if ((ctx.KeyPressed & (int)KeyModifiers.Backspace) != 0 && len > 0)
if ((ctx.KeyPressed & (int)KeyModifiers.Backspace) != 0 && value.Length > 0)
{
// Skip UTF-8 continuation bytes (simplified for C#)
while (len > 0 && (buf[len - 1] & 0xC0) == 0x80) len--;
if (len > 0) len--;
buf[len] = '\0';
value = value.Length > 0 ? value.Substring(0, value.Length - 1) : "";
res |= ResultFlags.Change;
}
// Handle return
if ((ctx.KeyPressed & (int)KeyModifiers.Return) != 0)
{
MicroUI.SetFocus(ctx, 0);
@ -1632,37 +1497,30 @@ namespace MicroUI
}
}
// Draw
DrawControlFrame(ctx, id, r, ColorType.Base, opt);
string displayText = new string(buf).Trim('\0');
DrawControlText(ctx, displayText, r, ColorType.Text, opt);
DrawControlText(ctx, value, r, ColorType.Text, opt);
return new MuResult(res);
}
public static MuResult SliderEx(MuContext ctx, string name, ref float value, float low, float high, float step, string format, int opt)
{
char[] buf = new char[Constants.MaxFormatLength + 1];
MuRect thumb;
int x, w;
ResultFlags res = 0;
float last = value, v = last;
// Generate ID from the name, just like BeginWindowEx does
uint id = MicroUI.GetId(ctx, System.Text.Encoding.UTF8.GetBytes(name));
MuRect baseRect = MuLayoutUtil.LayoutNext(ctx);
// Handle text input mode
if (NumberTextbox(ctx, ref v, baseRect, id))
{
return new MuResult(res);
}
// Handle normal mode
UpdateControl(ctx, id, baseRect, opt);
// Handle input - EXACTLY match C version logic
if (ctx.Focus == id && (ctx.MouseDown | ctx.MousePressed) == (int)MouseButton.Left)
{
v = low + (ctx.MousePos.X - baseRect.X) * (high - low) / baseRect.W;
@ -1672,61 +1530,48 @@ namespace MicroUI
}
}
// Clamp and store value, update res
value = v = MathUtil.Clamp(v, low, high);
if (last != v) { res |= ResultFlags.Change; }
// Draw base
DrawControlFrame(ctx, id, baseRect, ColorType.Base, opt);
// Draw thumb
w = ctx.Style.ThumbSize;
x = (int)((v - low) * (baseRect.W - w) / (high - low));
thumb = new MuRect(baseRect.X + x, baseRect.Y, w, baseRect.H);
DrawControlFrame(ctx, id, thumb, ColorType.Button, opt);
// Draw text
string text = v.ToString(format);
DrawControlText(ctx, text, baseRect, ColorType.Text, opt);
return new MuResult(res);
}
// Remove the caller information overload since we're using explicit names now
public static MuResult NumberEx(MuContext ctx, string name, ref float value, float step, string format, int opt)
{
char[] buf = new char[Constants.MaxFormatLength + 1];
ResultFlags res = 0;
// Generate ID from the name, just like BeginWindowEx does
uint id = MicroUI.GetId(ctx, System.Text.Encoding.UTF8.GetBytes(name));
MuRect baseRect = MuLayoutUtil.LayoutNext(ctx);
float last = value;
// Handle text input mode
if (NumberTextbox(ctx, ref value, baseRect, id))
{
return new MuResult(res);
}
// Handle normal mode
UpdateControl(ctx, id, baseRect, opt);
// Handle input - EXACTLY match C version logic
if (ctx.Focus == id && ctx.MouseDown == (int)MouseButton.Left)
{
value += ctx.MouseDelta.X * step;
}
// Set flag if value changed
if (value != last) { res |= ResultFlags.Change; }
// Draw base
DrawControlFrame(ctx, id, baseRect, ColorType.Base, opt);
// Draw text
string text = value.ToString(format);
DrawControlText(ctx, text, baseRect, ColorType.Text, opt);

View File

@ -8,9 +8,10 @@ class Program
{
// Key state tracking for key up events
private static HashSet<int> pressedKeys = new HashSet<int>();
// Persistent textbox buffer
private static char[] textboxBuf = new char[32];
private static bool textboxBufInitialized = false;
// Persistent textbox value
private static string textboxValue = "test";
// Second number value for testing multiple number controls
private static float secondValue = 25.0f;
// Track focus changes
private static uint lastFocus = 0;
@ -89,6 +90,16 @@ class Program
}
}
// Handle Shift key state (for Shift+Click functionality)
if (Raylib.IsKeyDown(KeyboardKey.LeftShift) || Raylib.IsKeyDown(KeyboardKey.RightShift))
{
ctx.KeyDown |= (int)KeyModifiers.Shift;
}
else
{
ctx.KeyDown &= ~(int)KeyModifiers.Shift;
}
// Handle key up events for modifier keys
var keysToRemove = new List<int>();
foreach (int pressedKey in pressedKeys)
@ -254,26 +265,36 @@ class Program
{
MuLayoutUtil.LayoutRow(ctx, 1, new int[] { 120 }, 0);
float test = 0;
var sliderResult = MuControl.SliderEx(ctx, "main_slider", ref sliderValue, 0.0f, 100.0f, 1.0f, "F1", 0);
//var sliderResult = MuControl.SliderEx(ctx, "main_slider", ref sliderValue, 0.0f, 100.0f, 1.0f, "F1", 0);
MuControl.SliderEx(ctx, "test_slider", ref test, 0.0f, 100.0f, 1.0f, "F1", 0);
if ((sliderResult & ResultFlags.Change) != 0)
{
Console.WriteLine($"Slider value changed to: {sliderValue}");
}
// if ((sliderResult & ResultFlags.Change) != 0)
// {
// Console.WriteLine($"Slider value changed to: {sliderValue}");
// }
MuControl.Text(ctx, "Other controls here...");
// Test TextboxRaw
if (!textboxBufInitialized) {
string initial = "Edit me!";
for (int i = 0; i < initial.Length && i < textboxBuf.Length; i++) textboxBuf[i] = initial[i];
textboxBufInitialized = true;
}
var textboxRect = MuLayoutUtil.LayoutNext(ctx);
var textboxResult = MuControl.TextboxRaw(ctx, textboxBuf, textboxBuf.Length, 12345, textboxRect, 0);
var textboxResult = MuControl.TextboxRaw(ctx, ref textboxValue, 12345, textboxRect, 0);
if ((textboxResult & ResultFlags.Change) != 0)
{
string value = new string(textboxBuf).TrimEnd('\0');
Console.WriteLine($"Textbox changed: {value}");
Console.WriteLine($"Textbox changed: {textboxValue}");
}
// Test NumberEx (number textbox)
MuControl.Text(ctx, "Number control (Shift+Click to edit):");
var numberResult = MuControl.NumberEx(ctx, "test_number", ref sliderValue, 1.0f, "F2", 0);
if ((numberResult & ResultFlags.Change) != 0)
{
Console.WriteLine($"Number changed to: {sliderValue}");
}
// Test second NumberEx (number textbox)
MuControl.Text(ctx, "Second number control (Shift+Click to edit):");
var secondNumberResult = MuControl.NumberEx(ctx, "test_number2", ref secondValue, 0.5f, "F2", 0);
if ((secondNumberResult & ResultFlags.Change) != 0)
{
Console.WriteLine($"Second number changed to: {secondValue}");
}
}

View File

@ -358,6 +358,12 @@ void window(mu_Context* ctx, float* value) {
if (mu_textbox_raw(ctx, textbox_buf, sizeof(textbox_buf), 12345, textbox_rect, 0)) {
printf("Textbox changed: %s\n", textbox_buf);
}
// Test NumberEx (number textbox)
mu_layout_row(ctx, 1, (int[]){120}, 0);
if (mu_number_ex(ctx, value, 1.0f, "%.1f", 0) & MU_RES_SUBMIT) {
printf("Number changed to: %.1f\n", *value);
}
}
// --- Right column ---