using Annulus; using Azimuth; using Clave; using Glass.Blacklight; using System; using System.Collections.Generic; using Prism; namespace Glass.Testbed.TestCases.Text { public class FontsTestCase : TestCase { private readonly World world; public FontsTestCase(World world) { this.world = world; } private const string LoremIpsum = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do " + "eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud " + "exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat."; private const int LinesShown = 3; private const int GammaBarGap = 4; // Height comes from the scrollbar's own arrow art, not a guessed constant. private static readonly Scalar GammaBarHeight = UiPacksheetMaterials.HorizontalScrollBar.DecrementButton.PreferredSize(Constraints.Unbounded).Y; // Must match PrimitiveBatch.CoverageGammaFloor -- kept as a literal since Glass.Testbed doesn't // otherwise reference GPUBridge. private static readonly Scalar GammaMinimum = .01953125; private static readonly Scalar GammaMaximum = 5; // Leaves CoverageGamma unset so the sample follows its font's live gamma, like any other text using it. private static TextMaterialStyle SampleStyle(SpriteFontDefinition font) { return new TextMaterialStyle { Font = font, TintColor = Colors.Black, VerticalAlignment = Alignment.Vertical.Top, OverflowBehavior = TextOverflow.Ellipsis, }; } private static string GammaText(FontRenderer fontRenderer) { return fontRenderer.EffectiveCoverageGamma.ToString("F2"); } private static string CaptionTextFor(FontCase fontCase) => $"{fontCase.Name} {fontCase.Font.LineHeight}px"; private record struct FontCase(string Name, SpriteFontDefinition Font); protected override void Populate() { FontCase[][] cases = [ [new("Philosopher", Fonts.Philosopher18), new("Liberation Sans", Fonts.LiberationSans16)], [ new("Liberation Sans Bold", Fonts.LiberationSansBold16), new("Liberation Sans Italic", Fonts.LiberationSansItalic16), new("Liberation Sans Bold Italic", Fonts.LiberationSansBoldItalic16), new("Liberation Mono", Fonts.LiberationMono16), ], [new("Philosopher", Fonts.Philosopher32), new("Liberation Sans", Fonts.LiberationSans32)], [new("Liberation Sans Bold", Fonts.LiberationSansBold32)], [new("Philosopher", Fonts.Philosopher64), new("Liberation Sans", Fonts.LiberationSans64)], ]; var resets = new List(); // Fills the window below the case's chrome, so the tall layout scrolls instead of running off the bottom. var scroller = NewChild("fonts scroll view"); scroller.ScrollsHorizontally = false; scroller.ScrollViewMaterial = new ScrollViewMaterial { Background = Material.Invisible, Canvas = Material.Invisible, HorizontalScrollBar = UiPacksheetMaterials.HorizontalScrollBar, VerticalScrollBar = UiPacksheetMaterials.VerticalScrollBar, }; scroller.Bounds = ContentAggregator.NewRow(ContentRect.Height); Vector viewportSize = scroller.ContentViewportRect.Size; var content = new RectAggregator( Rect.FromPositionAndSize(Vector.Zero, new Vector(viewportSize.X, 0)), new Vector(Gutter, Gutter)); Button resetButton = new Label(Widgets.ButtonLabelStyle, "Reset gammas", Constraints.Unbounded) .WrapIn(UiPacksheetMaterials.Button, ButtonBehavior.Default); resetButton.DebugName = "reset gammas button"; resetButton.OnActivate += delegate (ButtonBase _, in KeyboardSnapshot __, KeyboardManager ___) { foreach (Action reset in resets) { reset(); } }; resetButton.Bounds = Rect.FromPositionAndSize( content.NewRow(resetButton.Bounds.Height).TopLeft, resetButton.Bounds.Size); scroller.CanvasChildren.Add(resetButton); FontCase sampleCase = cases[0][0]; Scalar captionHeight = Widgets.Caption(CaptionTextFor(sampleCase)).PreferredSize(Constraints.Unbounded).Y; foreach (FontCase[] fontRow in cases) { Rect widthProbe = Rect.FromPositionAndSize(Vector.Zero, viewportSize); Scalar boxWidth = widthProbe.NewCols(fontRow, margin: Gutter)[0].Rect.Width; Scalar tallest = 0; foreach (FontCase fontCase in fontRow) { var probe = new TextMaterial { Text = LoremIpsum, Style = SampleStyle(fontCase.Font) with { MaxLines = LinesShown }, }; Scalar boxHeight = probe .PreferredSize(Constraints.Loose(new Vector(boxWidth, Scalar.PositiveInfinity))).Y; tallest = Math.Max(boxHeight, tallest); } // Mirrors the cells' real caption/gamma-bar consumption below, so this can't silently drift from it. var rowHeightStack = new RectAggregator(Rect.Zero); rowHeightStack.NewRow(captionHeight); rowHeightStack.NewRow(GammaBarHeight, margin: CaptionMargin); rowHeightStack.NewRow(tallest, margin: GammaBarGap); Rect row = content.NewRow(rowHeightStack.Rect.Height); foreach ((Rect cellRect, FontCase fontCase) in row.NewCols(fontRow, margin: Gutter)) { Rect cell = cellRect; var caption = new Label(Widgets.CaptionStyle, CaptionTextFor(fontCase)); caption.Bounds = cell.NewRow(caption.Material, margin: CaptionMargin); scroller.CanvasChildren.Add(caption); FontRenderer fontRenderer = world.uiWorld.renderer.FontRendererFor(fontCase.Font); Rect scrollBarRow = cell.NewRow(GammaBarHeight, margin: GammaBarGap); var readout = new Label(Widgets.SmallCaptionStyle, GammaText(fontRenderer)); readout.Bounds = scrollBarRow.NewCol(readout.PreferredSize(Constraints.Unbounded).X, HorizontalJustification.Right, margin: GammaBarGap); scroller.CanvasChildren.Add(readout); ScrollBar scrollBar = Widgets.HorizontalScrollBar(); scrollBar.ContentSize = GammaMaximum - GammaMinimum + 1; scrollBar.ViewportSize = 1; scrollBar.StepAmount = 5.0 / 256; scrollBar.Bounds = scrollBarRow; scrollBar.ScrollTo(fontRenderer.EffectiveCoverageGamma - GammaMinimum); // Subscribed after the initial ScrollTo, or it would pin every font's override at startup. scrollBar.OnScrollOffsetChanged += offset => { fontRenderer.CoverageGammaOverride = GammaMinimum + offset; readout.Text = GammaText(fontRenderer); }; scroller.CanvasChildren.Add(scrollBar); resets.Add(() => { fontRenderer.CoverageGammaOverride = null; Scalar original = fontRenderer.EffectiveCoverageGamma; // ScrollTo raises the change event, which re-applies an override, so clear it again after. scrollBar.ScrollTo(original - GammaMinimum); fontRenderer.CoverageGammaOverride = null; readout.Text = GammaText(fontRenderer); }); scroller.CanvasChildren.Add(new Label(SampleStyle(fontCase.Font), LoremIpsum, Constraints.Tight(cell.Size)).WrapIn(UiPacksheetMaterials.GreenFlatPanel, cell.TopLeft)); } } scroller.ContentSize = content.Rect.Size; } } }