feat(chat): stop repeating the same minute on every line
The comparison value comes from the message data, not from a variable carried between rows. In 1.5.6 the loop walked every message and skipped invisible ones with a dummy, so what it remembered was the last *visible* stamp. The virtualised list only iterates the visible window, so the row above that window was never drawn at all -- a carried variable would hold whatever was on screen before the last scroll, and the first stamp after every jump would be wrong. No predecessor means draw. Scrolling into the middle of a log would otherwise swallow the only stamp on screen. Both draw paths now pass an index; the linear one was a foreach and had none. The setting is on by default and existed with translations in twenty-three languages -- Catalan and Italian had kept the English string, so those two are done now. It needs no fingerprint entry: the column stays reserved when the stamp is suppressed, so hiding one changes no row's height.
This commit is contained in:
Generated
+2
-2
@@ -527,10 +527,10 @@
|
|||||||
<value>Finestra emergent</value>
|
<value>Finestra emergent</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Options_HideSameTimestamps_Name">
|
<data name="Options_HideSameTimestamps_Name">
|
||||||
<value>Hide timestamps when redundant</value>
|
<value>Amaga les marques de temps redundants</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Options_HideSameTimestamps_Description">
|
<data name="Options_HideSameTimestamps_Description">
|
||||||
<value>Hide timestamps when previous messages have the same timestamp.</value>
|
<value>Amaga la marca de temps quan el missatge anterior ja en té la mateixa.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Options_ShowPopOutTitleBar_Name">
|
<data name="Options_ShowPopOutTitleBar_Name">
|
||||||
<value>Show title bar for popped-out tabs</value>
|
<value>Show title bar for popped-out tabs</value>
|
||||||
|
|||||||
Generated
+2
-2
@@ -527,10 +527,10 @@
|
|||||||
<value>Pop out</value>
|
<value>Pop out</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Options_HideSameTimestamps_Name">
|
<data name="Options_HideSameTimestamps_Name">
|
||||||
<value>Hide timestamps when redundant</value>
|
<value>Nascondi gli orari ridondanti</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Options_HideSameTimestamps_Description">
|
<data name="Options_HideSameTimestamps_Description">
|
||||||
<value>Hide timestamps when previous messages have the same timestamp.</value>
|
<value>Nasconde l'orario quando il messaggio precedente ha già lo stesso.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Options_ShowPopOutTitleBar_Name">
|
<data name="Options_ShowPopOutTitleBar_Name">
|
||||||
<value>Show title bar for popped-out tabs</value>
|
<value>Show title bar for popped-out tabs</value>
|
||||||
|
|||||||
@@ -34,8 +34,8 @@ internal sealed class MessageList
|
|||||||
// Bound once. A method group off an instance method captures `this` and is
|
// Bound once. A method group off an instance method captures `this` and is
|
||||||
// not cached by Roslyn, so `compact ? DrawCompactRow : DrawCardRow` would
|
// not cached by Roslyn, so `compact ? DrawCompactRow : DrawCardRow` would
|
||||||
// allocate a delegate on every frame of every window.
|
// allocate a delegate on every frame of every window.
|
||||||
private readonly Action<Message> _drawCompactRow;
|
private readonly Action<Message, string?> _drawCompactRow;
|
||||||
private readonly Action<Message> _drawCardRow;
|
private readonly Action<Message, string?> _drawCardRow;
|
||||||
|
|
||||||
// Reused across frames: at MessageManager.MessageDisplayLimit a fresh array
|
// Reused across frames: at MessageManager.MessageDisplayLimit a fresh array
|
||||||
// per frame is 40 KB of garbage, and A2 put the default density on this
|
// per frame is 40 KB of garbage, and A2 put the default density on this
|
||||||
@@ -261,15 +261,20 @@ internal sealed class MessageList
|
|||||||
|
|
||||||
// Draws the stamp into its column and leaves the cursor at the text column,
|
// Draws the stamp into its column and leaves the cursor at the text column,
|
||||||
// whether or not anything was drawn.
|
// whether or not anything was drawn.
|
||||||
private void DrawTimestampCell(Message message)
|
private void DrawTimestampCell(Message message, string? previousStamp)
|
||||||
{
|
{
|
||||||
var origin = ImGui.GetCursorPos();
|
var origin = ImGui.GetCursorPos();
|
||||||
|
|
||||||
if (_stampVisible)
|
var stamp = FormatTimestamp(message.Date);
|
||||||
|
var draw =
|
||||||
|
_stampVisible
|
||||||
|
&& RepeatedTimestamp.ShouldDraw(Plugin.Config.HideSameTimestamps, stamp, previousStamp);
|
||||||
|
|
||||||
|
if (draw)
|
||||||
{
|
{
|
||||||
ImGui.SetCursorPosY(origin.Y + _metaDrop);
|
ImGui.SetCursorPosY(origin.Y + _metaDrop);
|
||||||
using (MetaFace().Push())
|
using (MetaFace().Push())
|
||||||
ImGui.TextUnformatted(FormatTimestamp(message.Date));
|
ImGui.TextUnformatted(stamp);
|
||||||
|
|
||||||
ImGui.SameLine(0f, 0f);
|
ImGui.SameLine(0f, 0f);
|
||||||
}
|
}
|
||||||
@@ -328,7 +333,7 @@ internal sealed class MessageList
|
|||||||
_scrollToBottomRequested = true;
|
_scrollToBottomRequested = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DrawCompactRow(Message message)
|
private void DrawCompactRow(Message message, string? previousStamp)
|
||||||
{
|
{
|
||||||
// B2-1/B2-2: render the sender through DrawChunks (the name-aware path
|
// B2-1/B2-2: render the sender through DrawChunks (the name-aware path
|
||||||
// that applies WorldSuffixMode/NameFormMode via ForDisplay), not as a
|
// that applies WorldSuffixMode/NameFormMode via ForDisplay), not as a
|
||||||
@@ -336,7 +341,7 @@ internal sealed class MessageList
|
|||||||
// channel brackets/colon as ChunkSource.None wrappers (MessageManager
|
// channel brackets/colon as ChunkSource.None wrappers (MessageManager
|
||||||
// .cs:300-314), so the separator is rendered by the chunks. 1.5.6 parity
|
// .cs:300-314), so the separator is rendered by the chunks. 1.5.6 parity
|
||||||
// (ChatLogWindow.cs:1965: DrawChunks(message.Sender) + SameLine).
|
// (ChatLogWindow.cs:1965: DrawChunks(message.Sender) + SameLine).
|
||||||
DrawTimestampCell(message);
|
DrawTimestampCell(message, previousStamp);
|
||||||
|
|
||||||
if (message.Sender.Count == 0)
|
if (message.Sender.Count == 0)
|
||||||
{
|
{
|
||||||
@@ -365,7 +370,7 @@ internal sealed class MessageList
|
|||||||
private void DrawRows(
|
private void DrawRows(
|
||||||
Tab tab,
|
Tab tab,
|
||||||
IReadOnlyList<Message> messages,
|
IReadOnlyList<Message> messages,
|
||||||
Action<Message> drawRow,
|
Action<Message, string?> drawRow,
|
||||||
bool frozen
|
bool frozen
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
@@ -438,7 +443,9 @@ internal sealed class MessageList
|
|||||||
// the text instead of needing a draw-channel detour.
|
// the text instead of needing a draw-channel detour.
|
||||||
DrawRowSurface(msg, heights[i]);
|
DrawRowSurface(msg, heights[i]);
|
||||||
|
|
||||||
drawRow(msg);
|
// From the data, not from a variable carried between rows: this loop
|
||||||
|
// starts at FirstVisible, so the row above the window was never drawn.
|
||||||
|
drawRow(msg, i > 0 ? FormatTimestamp(messages[i - 1].Date) : null);
|
||||||
if (frozen)
|
if (frozen)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
@@ -531,7 +538,7 @@ internal sealed class MessageList
|
|||||||
private void DrawLinearAndMeasure(
|
private void DrawLinearAndMeasure(
|
||||||
Guid tabId,
|
Guid tabId,
|
||||||
IReadOnlyList<Message> messages,
|
IReadOnlyList<Message> messages,
|
||||||
Action<Message> drawRow
|
Action<Message, string?> drawRow
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
// No row has a cached height on this frame, so the surface cannot be
|
// No row has a cached height on this frame, so the surface cannot be
|
||||||
@@ -540,12 +547,13 @@ internal sealed class MessageList
|
|||||||
// frame after every resize.
|
// frame after every resize.
|
||||||
using var surfaces = StyleEngine.RowSurfaceScope.Push();
|
using var surfaces = StyleEngine.RowSurfaceScope.Push();
|
||||||
|
|
||||||
foreach (var msg in messages)
|
for (var i = 0; i < messages.Count; i++)
|
||||||
{
|
{
|
||||||
|
var msg = messages[i];
|
||||||
var before = ImGui.GetCursorPosY();
|
var before = ImGui.GetCursorPosY();
|
||||||
var top = ImGui.GetCursorScreenPos();
|
var top = ImGui.GetCursorScreenPos();
|
||||||
|
|
||||||
drawRow(msg);
|
drawRow(msg, i > 0 ? FormatTimestamp(messages[i - 1].Date) : null);
|
||||||
|
|
||||||
var after = ImGui.GetCursorPosY();
|
var after = ImGui.GetCursorPosY();
|
||||||
var height = after - before;
|
var height = after - before;
|
||||||
@@ -556,7 +564,7 @@ internal sealed class MessageList
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DrawCardRow(Message message)
|
private void DrawCardRow(Message message, string? previousStamp)
|
||||||
{
|
{
|
||||||
// B2-1/B2-2: sender via DrawChunks (name-aware path), on its own line
|
// B2-1/B2-2: sender via DrawChunks (name-aware path), on its own line
|
||||||
// with content below — 1.5.6 card parity (ChatLogWindow.cs:1913, no
|
// with content below — 1.5.6 card parity (ChatLogWindow.cs:1913, no
|
||||||
@@ -568,7 +576,7 @@ internal sealed class MessageList
|
|||||||
// densities; only a message with a sender gets the two-line treatment.
|
// densities; only a message with a sender gets the two-line treatment.
|
||||||
if (message.Sender.Count == 0)
|
if (message.Sender.Count == 0)
|
||||||
{
|
{
|
||||||
DrawTimestampCell(message);
|
DrawTimestampCell(message, previousStamp);
|
||||||
using (ItalicFace().Push())
|
using (ItalicFace().Push())
|
||||||
_chunkRenderer.DrawChunks(
|
_chunkRenderer.DrawChunks(
|
||||||
message.Content,
|
message.Content,
|
||||||
@@ -580,7 +588,7 @@ internal sealed class MessageList
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
DrawTimestampCell(message);
|
DrawTimestampCell(message, previousStamp);
|
||||||
using (SenderFace().Push())
|
using (SenderFace().Push())
|
||||||
_chunkRenderer.DrawChunks(message.Sender, wrap: true, handler: _handler, lineWidth: 0f);
|
_chunkRenderer.DrawChunks(message.Sender, wrap: true, handler: _handler, lineWidth: 0f);
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
namespace HellionChat.Ui.Components;
|
||||||
|
|
||||||
|
// TEST-MIRROR: Ui/RepeatedTimestampTests.cs
|
||||||
|
//
|
||||||
|
// Whether a row draws its timestamp, given the one above it.
|
||||||
|
//
|
||||||
|
// The comparison value has to come from the message data, not from a drawing
|
||||||
|
// state carried between rows. In 1.5.6 the loop walked every message and skipped
|
||||||
|
// the invisible ones with a dummy, so the "last stamp" it remembered was the last
|
||||||
|
// visible one. The virtualised list only iterates the visible window, so a row at
|
||||||
|
// the top of that window has a predecessor in the data that was never drawn --
|
||||||
|
// and a state variable would hold whatever was on screen before the scroll.
|
||||||
|
//
|
||||||
|
// No predecessor means draw. Scrolling into the middle of a log would otherwise
|
||||||
|
// swallow the only stamp on screen.
|
||||||
|
internal static class RepeatedTimestamp
|
||||||
|
{
|
||||||
|
internal static bool ShouldDraw(bool enabled, string current, string? previous) =>
|
||||||
|
!enabled || previous is null || !string.Equals(current, previous, StringComparison.Ordinal);
|
||||||
|
}
|
||||||
@@ -39,6 +39,13 @@ internal sealed class ChatTab
|
|||||||
() => Plugin.Config.Use24HourClock,
|
() => Plugin.Config.Use24HourClock,
|
||||||
v => Plugin.Config.Use24HourClock = v
|
v => Plugin.Config.Use24HourClock = v
|
||||||
);
|
);
|
||||||
|
_w.ToggleRow(
|
||||||
|
ImGui.GetID("chat.display.samestamps"u8),
|
||||||
|
Language.Options_HideSameTimestamps_Name,
|
||||||
|
Language.Options_HideSameTimestamps_Description,
|
||||||
|
() => Plugin.Config.HideSameTimestamps,
|
||||||
|
v => Plugin.Config.HideSameTimestamps = v
|
||||||
|
);
|
||||||
|
|
||||||
// Descriptions move out of the help markers and onto the row. They
|
// Descriptions move out of the help markers and onto the row. They
|
||||||
// were written to be read; a (?) the user has to hover is where an
|
// were written to be read; a (?) the user has to hover is where an
|
||||||
|
|||||||
Reference in New Issue
Block a user