Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Prowl.Editor/MainMenuBar.cs
Original file line number Diff line number Diff line change
Expand Up @@ -133,11 +133,13 @@ private static void RenderDropdown(Paper paper, string id, List<MenuItem> items,
{
paper.Box($"{id}_chk_{index}")
.Width(24)
.Alignment(TextAlignment.MiddleLeft)
.Text(item.IsChecked ? "\u2713" : "", font)
.TextColor(textColor)
.FontSize(EditorTheme.FontSize);

paper.Box($"{id}_lbl_{index}")
.Alignment(TextAlignment.MiddleLeft)
.Text(displayLabel, font)
.TextColor(textColor)
.FontSize(EditorTheme.FontSize);
Expand All @@ -150,6 +152,7 @@ private static void RenderDropdown(Paper paper, string id, List<MenuItem> items,
{
paper.Box($"{id}_arr_{index}")
.Width(20)
.Alignment(TextAlignment.MiddleLeft)
.Margin(0, 4, 0, 0)
.Text("\u25B6", font)
.TextColor(textColor)
Expand Down
194 changes: 154 additions & 40 deletions Prowl.Editor/Panels/ConsolePanel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,24 @@ public class ConsolePanel : DockPanel

private const float ToolbarHeight = 26f;
private const int MaxMessages = 500;
private const float RowHeight = 20f;
private const float IconWidth = 14f;
private const float TimeWidth = 52f;

private float FullRowHeight => _multiLine ? MultiLineRowHeight : RowHeight;

private float RowHeight = 26f;
private float MultiLineRowHeight => RowHeight * 1.8f;
private const float IconWidth = 16f;
private float TimeWidth => 68f;
private const float CountWidth = 30f;
private const float FontSize = 10f;
private const float FontSize = 13f;

private static readonly List<LogEntry> _messages = new();
private static bool _subscribed;

// Settings
private bool _showTime = false;
private bool _collapse = true;
private bool _multiLine = false;

// Filters
private bool _showInfo = true;
private bool _showWarnings = true;
Expand All @@ -39,6 +48,7 @@ public class ConsolePanel : DockPanel
// Cached filtered list (rebuilt when messages or filters change)
private static int _lastMessageCount;
private static int _lastFilterHash;
private static bool _lastCollapseState;
private static readonly List<int> _filteredIndices = new();


Expand All @@ -58,6 +68,7 @@ internal struct LogEntry
public Prowl.Scribe.TextLayout? TimeLayout;
public Prowl.Scribe.TextLayout? MessageLayout;
public Prowl.Scribe.TextLayout? CountLayout;
public Prowl.Scribe.TextLayout? StackTraceLayout;
}

public ConsolePanel()
Expand Down Expand Up @@ -111,10 +122,12 @@ public override void OnGUI(Paper paper, float width, float height)
var font = EditorTheme.DefaultFont;
if (font == null) return;

using (paper.Column("con_root").Size(width, height).Enter())
using (paper.Column("con_root")
.Size(width, height)
.Enter())
{
DrawToolbar(paper, font, width);
DrawMessages(paper, font, width, height - 33);
DrawMessages(paper, font, width-2, height - 44);
}
}

Expand All @@ -123,13 +136,14 @@ private void DrawToolbar(Paper paper, FontFile font, float width)
using (paper.Row("con_toolbar")
.Height(ToolbarHeight)
.Margin(4, 4, 4, 0)
.RowBetween(4)
.RowBetween(12)
.Margin(8)
.Enter())
{
EditorGUI.Button(paper, "con_clear", "Clear", width: 50)
.OnValueChanged(_ => { _messages.Clear(); _filteredIndices.Clear(); });

paper.Box("con_sep1").Width(1).Height(20).BackgroundColor(EditorTheme.Ink200);
paper.Box("con_sep1").Width(1).Height(24).BackgroundColor(EditorTheme.Ink200);

int infoCount = 0, warnCount = 0, errCount = 0;
foreach (var m in _messages)
Expand All @@ -139,46 +153,86 @@ private void DrawToolbar(Paper paper, FontFile font, float width)
else errCount += m.Count;
}

using (paper.Row("buttons").Enter())
EditorGUI.ToggleButton(paper, "con_collapse", "Collapse", _collapse, fitWidth: true).OnValueChanged(newValue =>
{
EditorGUI.ToggleButton(paper, "con_info", $"{EditorIcons.CircleInfo} {infoCount}", _showInfo)
_collapse = newValue;
});

using (paper.Row("buttons")
.RowBetween(8)
.Enter())
{
GetToggleStyle(LogSeverity.Normal, out Color infoTextColor, out Color infoBgColor);
EditorGUI.ToggleButton(paper, "con_info", $"{EditorIcons.CircleInfo} {infoCount}", _showInfo,
fitWidth: true, textColorOverride: _showInfo ? infoTextColor : EditorTheme.Ink300, bgColorOverride: infoBgColor)
.OnValueChanged(v => _showInfo = v);
EditorGUI.ToggleButton(paper, "con_warn", $"{EditorIcons.TriangleExclamation} {warnCount}", _showWarnings)

GetToggleStyle(LogSeverity.Warning, out Color warnTextColor, out Color warnBgColor);
EditorGUI.ToggleButton(paper, "con_warn", $"{EditorIcons.TriangleExclamation} {warnCount}", _showWarnings,
fitWidth: true, textColorOverride: _showWarnings ? warnTextColor : EditorTheme.Ink300, bgColorOverride: warnBgColor)
.OnValueChanged(v => _showWarnings = v);
EditorGUI.ToggleButton(paper, "con_err", $"{EditorIcons.CircleExclamation} {errCount}", _showErrors)

GetToggleStyle(LogSeverity.Error, out Color errorTextColor, out Color errorBgColor);
EditorGUI.ToggleButton(paper, "con_err", $"{EditorIcons.CircleExclamation} {errCount}", _showErrors,
fitWidth: true, textColorOverride: _showErrors ? errorTextColor : EditorTheme.Ink300, bgColorOverride: errorBgColor)
.OnValueChanged(v => _showErrors = v);
}

EditorGUI.SearchBar(paper, "con_search", _searchText, "Filter...")
.OnValueChanged(v => _searchText = v);

EditorGUI.ButtonSquareWithHandle(paper, "con_settingsButton", $"{EditorIcons.Gear}", out var settingsButton)
.OnValueChanged((clicked) =>
{
ContextMenuHelper.OpenContextMenu(paper, "con_settings", settingsButton);
});

ContextMenuHelper.ContextMenu(paper, "con_settings", menu =>
{
menu.Submenu("Log Tests", (subMenu) =>
{
subMenu.Item("Log", () => Debug.Log($"This is a Normal Log."))
.Item("LogWarning", () => Debug.LogWarning($"This is a Warning Log."))
.Item("LogError", () => Debug.LogError($"This is an Error Log."))
.Item("LogSuccess", () => Debug.LogSuccess($"This is a Success Log."));
}, EditorIcons.Flask)
.Separator()
.Toggle("Show Time", () => _showTime = !_showTime, () => _showTime)
.Toggle("Multi Line", () => _multiLine = !_multiLine, () => _multiLine)
;
}, settingsButton);
}
}

private void DrawMessages(Paper paper, FontFile font, float width, float height)
{
// Rebuild filtered list when needed
int filterHash = HashCode.Combine(_showInfo, _showWarnings, _showErrors, _searchText);
if (_lastMessageCount != _messages.Count || _lastFilterHash != filterHash)
if (_lastMessageCount != _messages.Count || _lastFilterHash != filterHash || _lastCollapseState != _collapse)
{
_lastMessageCount = _messages.Count;
_lastFilterHash = filterHash;
_lastCollapseState = _collapse;
RebuildFilteredList();
}

int visibleCount = _filteredIndices.Count;
float totalContentHeight = visibleCount * RowHeight;
float totalContentHeight = visibleCount * FullRowHeight;

using (ScrollView.Begin(paper, "con_scroll", width, height))
using (ScrollView.Begin(paper, "con_scroll", width, height, forceScrollbar:true))
{
// Single element for ALL messages — fixed height based on count
paper.Box("con_content")
.Width(width)
.Width(width - 10)
.Height(totalContentHeight)
.BackgroundColor(EditorTheme.Neutral100)
.Clip()
.OnClick(0, (_, e) =>
{
// Determine clicked row from mouse Y relative to content
float relY = (float)e.RelativePosition.Y;
int clickedRow = (int)(relY / RowHeight);
float totalRowHeight = FullRowHeight;
int clickedRow = (int)(relY / totalRowHeight);
if (clickedRow >= 0 && clickedRow < _filteredIndices.Count)
{
_selectedFilteredIndex = (_selectedFilteredIndex == clickedRow) ? -1 : clickedRow;
Expand Down Expand Up @@ -207,13 +261,14 @@ private void DrawMessages(Paper paper, FontFile font, float width, float height)
float contentTop = (float)contentRect.Min.Y;

// Calculate visible row range
int firstVisible = Math.Max(0, (int)((clipTop - contentTop) / RowHeight));
int lastVisible = Math.Min(visibleCount - 1, (int)((clipBottom - contentTop) / RowHeight));
int firstVisible = Math.Max(0, (int)((clipTop - contentTop) / FullRowHeight));
int lastVisible = Math.Min(visibleCount - 1, (int)((clipBottom - contentTop) / FullRowHeight));

// Draw all visible rows in one draw call using cached TextLayouts
paper.Draw(ref handle, (canvas, r) =>
{
float startX = (float)r.Min.X;
float paddedX = startX + 8;
float startY = (float)r.Min.Y;
float size = FontSize * 1.5f;

Expand All @@ -223,38 +278,63 @@ private void DrawMessages(Paper paper, FontFile font, float width, float height)
int msgIdx = _filteredIndices[vi];
if (msgIdx >= _messages.Count) continue;

var totalRowSize = FullRowHeight;

var msg = _messages[msgIdx];
float rowY = startY + vi * RowHeight;
float textY = rowY + RowHeight * 0.5f - size * 0.5f;
float rowY = startY + vi * totalRowSize;
float textY = rowY + totalRowSize * (_multiLine ? 0.25f : 0.5f) - size * 0.5f + 2;
float iconY = rowY + totalRowSize * 0.5f - size * 0.5f;

GetEntryStyle(msg.Severity, vi, out string icon, out Color textColor, out Color bgColor);

// Selection highlight
if (vi == _selectedFilteredIndex)
canvas.RectFilled(startX, rowY, (float)r.Size.X, RowHeight, Color.FromArgb(60, EditorTheme.Purple400));
canvas.RectFilled(startX, rowY, (float)r.Size.X, totalRowSize, Color.FromArgb(60, EditorTheme.Purple400));
else if (bgColor != Color.Transparent)
canvas.RectFilled(startX, rowY, (float)r.Size.X, RowHeight, bgColor);
canvas.RectFilled(startX, rowY, (float)r.Size.X, totalRowSize, bgColor);

canvas.RectFilled(startX+2, rowY+1, (float)4, totalRowSize-2, EditorGUI.LerpRGB(textColor, Color.Black, 0.5f));

// Create layouts lazily
msg.IconLayout ??= canvas.CreateLayout(icon, new Prowl.Scribe.TextLayoutSettings { Font = font, PixelSize = size });
msg.TimeLayout ??= canvas.CreateLayout(msg.TimeString, new Prowl.Scribe.TextLayoutSettings { Font = font, PixelSize = size });
msg.MessageLayout ??= canvas.CreateLayout(msg.Message, new Prowl.Scribe.TextLayoutSettings { Font = font, PixelSize = size });



float padStack = 4;
// Draw using cached layouts
canvas.DrawLayout(msg.IconLayout, startX + 4, textY, textColor);
canvas.DrawLayout(msg.TimeLayout, startX + IconWidth + 8, textY, EditorTheme.Ink200);
canvas.DrawLayout(msg.MessageLayout, startX + IconWidth + TimeWidth + 12, textY, textColor);
canvas.DrawLayout(msg.IconLayout, paddedX + padStack, iconY, textColor);
padStack += IconWidth + 10;

// Count badge
if (msg.Count > 1)
if (_showTime)
{
canvas.DrawLayout(msg.TimeLayout, paddedX + padStack, iconY, EditorTheme.Ink200);
padStack += TimeWidth + 4;
}

if (_multiLine)
{
msg.CountLayout ??= canvas.CreateLayout(msg.Count.ToString(), new Prowl.Scribe.TextLayoutSettings { Font = font, PixelSize = size });
float badgeW = msg.CountLayout.Size.X / 2f + 8; // Size is in scaled pixels
float badgeX = startX + (float)r.Size.X - badgeW - 6;
float badgeY = rowY + (RowHeight - 14) * 0.5f;
float stackSize = size * 0.8f;
float stackY = rowY + totalRowSize * (_multiLine ? 0.75f : 0.5f) - stackSize * 0.5f - 2;
msg.StackTraceLayout ??= canvas.CreateLayout(msg.StackTrace.StackFrames[0].ToString(), new Prowl.Scribe.TextLayoutSettings { Font = font, PixelSize = stackSize });
canvas.DrawLayout(msg.StackTraceLayout, paddedX + padStack+1, stackY, EditorGUI.LerpRGB(textColor,Color.Black,0.25f));
}

canvas.RoundedRectFilled(badgeX, badgeY, badgeW, 14, 7, Color.FromArgb(120, 80, 80, 85));
canvas.DrawLayout(msg.CountLayout, badgeX + 4, badgeY + 1, EditorTheme.Ink300);
canvas.DrawLayout(msg.MessageLayout, paddedX + padStack, textY, textColor);

// Count badge
if (_collapse && msg.Count > 1)
{
var textSize = size / 1.2f;
msg.CountLayout ??= canvas.CreateLayout(msg.Count.ToString(), new Prowl.Scribe.TextLayoutSettings { Font = font, PixelSize = textSize });
float badgeW = msg.CountLayout.Size.X + 8; // Size is in scaled pixels
float badgeH = RowHeight - 6;
float badgeX = startX + (float)r.Size.X - badgeW - 4;
float badgeY = rowY + (_multiLine ? MultiLineRowHeight*0.25f : 0) + 3;

canvas.RoundedRectFilled(badgeX, badgeY, badgeW, badgeH, badgeH, Color.FromArgb(120, 80, 80, 85));
canvas.DrawLayout(msg.CountLayout, badgeX + 4, badgeY + (badgeH - textSize) / 2f, EditorTheme.Ink400);
}

// Write back the cached layouts (struct copy)
Expand All @@ -275,7 +355,41 @@ private void RebuildFilteredList()
if (!string.IsNullOrEmpty(_searchText) &&
!msg.Message.Contains(_searchText, StringComparison.OrdinalIgnoreCase))
continue;
_filteredIndices.Add(i);
if (_collapse || msg.Count == 1)
{
_filteredIndices.Add(i);
}
else if (msg.Count > 1)
{
for (int t = 0; t < msg.Count; t++)
{
_filteredIndices.Add(i);
}
}
}
}

private static void GetToggleStyle(LogSeverity severity, out Color textColor, out Color bgColor)
{
switch (severity)
{
case LogSeverity.Warning:
textColor = Color.FromArgb(255, 230, 200, 80);
bgColor = Color.FromArgb(20, 230, 200, 80);//visualIndex % 2 == 0 ? Color.FromArgb(10, 230, 200, 80) : Color.Transparent;
break;
case LogSeverity.Error:
case LogSeverity.Exception:
textColor = Color.FromArgb(255, 230, 80, 80);
bgColor = Color.FromArgb(20, 230, 80, 80);//visualIndex % 2 == 0 ? Color.FromArgb(10, 230, 80, 80) : Color.Transparent;
break;
case LogSeverity.Success:
textColor = Color.FromArgb(255, 80, 200, 80);
bgColor = Color.Transparent;
break;
default:
textColor = EditorTheme.Ink400;
bgColor = Color.FromArgb(10, 255, 255, 255);
break;
}
}

Expand All @@ -286,23 +400,23 @@ private static void GetEntryStyle(LogSeverity severity, int visualIndex, out str
case LogSeverity.Warning:
icon = EditorIcons.TriangleExclamation;
textColor = Color.FromArgb(255, 230, 200, 80);
bgColor = visualIndex % 2 == 0 ? Color.FromArgb(10, 230, 200, 80) : Color.Transparent;
bgColor = Color.FromArgb(20, 230, 200, 80);//visualIndex % 2 == 0 ? Color.FromArgb(10, 230, 200, 80) : Color.Transparent;
break;
case LogSeverity.Error:
case LogSeverity.Exception:
icon = EditorIcons.CircleExclamation;
textColor = Color.FromArgb(255, 230, 80, 80);
bgColor = visualIndex % 2 == 0 ? Color.FromArgb(10, 230, 80, 80) : Color.Transparent;
bgColor = Color.FromArgb(20, 230, 80, 80);//visualIndex % 2 == 0 ? Color.FromArgb(10, 230, 80, 80) : Color.Transparent;
break;
case LogSeverity.Success:
icon = EditorIcons.CircleCheck;
textColor = Color.FromArgb(255, 80, 200, 80);
bgColor = Color.Transparent;
bgColor = Color.FromArgb(25, 80, 200, 80);
break;
default:
icon = EditorIcons.CircleInfo;
textColor = EditorTheme.Ink500;
bgColor = visualIndex % 2 == 0 ? Color.FromArgb(8, 255, 255, 255) : Color.Transparent;
textColor = EditorTheme.Ink400;
bgColor = visualIndex % 2 == 0 ? Color.FromArgb(10, 255, 255, 255) : Color.Transparent;
break;
}
}
Expand Down
Loading
Loading