spectreconsole / spectre.console

A .NET library that makes it easier to create beautiful console applications.
https://spectreconsole.net
MIT License
9.17k stars 472 forks source link

Panel in layout adds newline in more complex layout split #1390

Open nanocortex opened 9 months ago

nanocortex commented 9 months ago

Information

Describe the bug There's some issue with adding newline or setting the wrong height on panel. It occurs mostly when there are 3 rows in layout and height of AnsiConsole is a specific number e.g. 17, 20, 23, 28, 31. Changing console height to other "correct" number seems to resolve the problem, so I would assume it's correlated to some measurement calculation?

To Reproduce Run this code and set height of terminal to specific number e.g. provided above or try to change height by one unit at a time, until issue occurs.

using Spectre.Console;

var layout = new Layout();
var col1 = new Layout();
var col1Row1 = new Layout();
var col1Row2 = new Layout();
var col1Row3 = new Layout();

col1.SplitRows(col1Row1, col1Row2, col1Row3);

var col2 = new Layout();

layout.SplitColumns(col1, col2);

var panel = new Panel("Hello, World!") { Expand = true };

List<Layout> layouts = [col1Row1, col1Row2, col1Row3, col2];
foreach (var l in layouts)
{
    l.Update(panel);
}

AnsiConsole.Clear();
AnsiConsole.Write(layout);
Console.ReadKey();

Expected behavior Display correct panels, without newline.

Screenshots Should be:

image

but is:

image

Please upvote :+1: this issue if you are interested in it.

BlazeFace commented 5 months ago

@patrickfreilinger It looks like this issue is related to how the Ratio is calculated. Essentially the calculation in this loop will have a remainder very close to one (for example .99999) but the DivMod calculation will not include it in the final size.

internal static class Ratio
{
    public static List<int> Resolve(int total, IEnumerable<IRatioResolvable> edges)
    ...
      if (!invalidate)
                  {
                      var remainder = 0f;
                      foreach (var flexibleEdge in flexibleEdges)
                      {
                          var (div, mod) = DivMod((portion * flexibleEdge.Edge.Ratio) + remainder, 1);
                          remainder = mod;
                          sizes[flexibleEdge.Index] = div;
                      }
                  }

For example in the case of a height of 17, the 3 sections are of height [5,6,5] adding up to 16 while the right side is of length 17.

A possible fix is to add a check into the DivMod calculation to check if the remainder is within a delta of one.

static (int Div, float Mod) DivMod(float x, float y)
        {
            var (div, mod) = ((int)(x / y), x % y);

            // If remainder is withing .0001 of 1 then we round up
            if (!(mod > 0.9999))
            {
                return (div, mod);
            }

            div++;
            mod = 0;
            return (div, mod);
        }

After this change the sizes are [5, 6, 6] and there is no longer an issue with the 2 halves being of different heights.