Editor/UI/ArchGuideWindow.cs
using System;
using System.Linq;
using Editor;
namespace Sunless.Architecture;
// Where a tool's how-to is read. Modeless and re-pointed rather than one window per tool, so pressing HOW TO on
// a second tool moves the one window rather than stacking another over the scene view.
public sealed class ArchGuideWindow : Window {
const int Reading = 560;
const int Gutter = 28;
static ArchGuideWindow standing;
public static void Open( ArchGuide guide ) {
if ( guide is null ) {
return;
}
if ( standing.IsValid() ) {
standing.Read( guide );
standing.Focus();
return;
}
standing = new ArchGuideWindow( guide );
standing.Show();
}
Layout page;
ArchGuideWindow( ArchGuide guide ) {
DeleteOnClose = true;
Size = new Vector2( Reading + Gutter * 2 + 24, 720 );
MinimumSize = new Vector2( 420, 360 );
SetWindowIcon( "menu_book" );
var scroll = new ScrollArea( this );
scroll.Canvas = new Widget( scroll ) { Layout = Layout.Column() };
scroll.Canvas.Layout.Margin = Gutter;
scroll.Canvas.Layout.Spacing = 0;
page = scroll.Canvas.Layout;
Canvas = new Widget( this ) { Layout = Layout.Column() };
Canvas.Layout.Add( scroll, 1 );
Read( guide );
}
void Read( ArchGuide guide ) {
WindowTitle = $"How to — {guide.Title}";
page.Clear( true );
page.Add( new ArchGuideHeading( guide.Title, 15f, 700, 34f ) );
page.Add( Paragraph( guide.Lead, 0.82f, 11f ) );
foreach ( var chapter in guide.Chapters ) {
Chapter( chapter );
}
page.AddStretchCell();
}
void Chapter( ArchGuideChapter chapter ) {
page.Add( new ArchGuideRule() );
page.Add( new ArchGuideHeading( chapter.Heading, 11f, 700, 30f ) );
if ( ArchIcons.Picture( chapter.Picture ) is { } drawn ) {
page.Add( new ArchGuidePicture( drawn, Reading ) );
}
if ( chapter.Plates.Length > 0 ) {
Plates( chapter );
}
foreach ( var line in chapter.Body ) {
page.Add( Paragraph( line, 0.72f, 10.5f ) );
}
for ( var index = 0; index < chapter.Steps.Length; index++ ) {
page.Add( new ArchGuideStep( index + 1, chapter.Steps[index], Reading ) );
}
}
void Plates( ArchGuideChapter chapter ) {
var row = page.AddRow();
row.Spacing = 8;
row.Margin = new Sandbox.UI.Margin( 0, 4, 0, 8 );
foreach ( var plate in chapter.Plates ) {
row.Add( new ArchGuidePlateView( plate ) );
}
row.AddStretchCell();
}
static Widget Paragraph( string text, float alpha, float size ) {
var label = new Label( text ) { WordWrap = true, FixedWidth = Reading };
label.MinimumHeight = MathF.Ceiling( text.Length / 82f ) * 17f + 10f;
label.SetStyles( $"font-size: {size}px; color: rgba(255,255,255,{alpha}); padding-bottom: 6px;" );
return label;
}
}
sealed class ArchGuideHeading : Widget {
readonly string text;
readonly float size;
readonly int weight;
public ArchGuideHeading( string text, float size, int weight, float height ) {
this.text = text;
this.size = size;
this.weight = weight;
FixedHeight = height;
}
protected override void OnPaint() {
Paint.TextAntialiasing = true;
Paint.SetDefaultFont( size, weight );
Paint.SetPen( Theme.Text );
Paint.DrawText( LocalRect, text, TextFlag.LeftBottom );
}
}
sealed class ArchGuideRule : Widget {
public ArchGuideRule() {
FixedHeight = 14f;
}
protected override void OnPaint() {
Paint.SetPen( Theme.Text.WithAlpha( 0.1f ) );
Paint.DrawLine( new Vector2( 0f, 8f ), new Vector2( Width, 8f ) );
}
}
// Drawn at the reading width and letterboxed to the file's own aspect, so a plate drawn at any size lands
// looking deliberate rather than stretched.
sealed class ArchGuidePicture : Widget {
readonly string path;
public ArchGuidePicture( string path, float width ) {
this.path = path;
var image = Pixmap.FromFile( path );
var aspect = image is not null && image.Width > 0 ? (float)image.Height / image.Width : 0.5f;
FixedWidth = width;
FixedHeight = MathF.Round( width * aspect ) + 10f;
}
protected override void OnPaint() {
Paint.Draw( new Rect( 0f, 0f, Width, Height - 10f ), path, 1f, 4f );
}
}
// The tool's OWN icon art, captioned. A chapter can show the very tiles it is talking about without anyone
// drawing anything new, and a slug with no PNG behind it falls back to its glyph exactly as the tile does.
sealed class ArchGuidePlateView : Widget {
readonly string icon;
readonly string caption;
public ArchGuidePlateView( ArchGuidePlate plate ) {
icon = ArchIcons.Get( plate.Slug, plate.Glyph );
caption = plate.Caption;
FixedWidth = 92f;
FixedHeight = 86f;
}
protected override void OnPaint() {
Paint.Antialiasing = true;
Paint.TextAntialiasing = true;
Paint.SetBrushAndPen( Theme.Text.WithAlpha( 0.04f ), Theme.Text.WithAlpha( 0.1f ) );
Paint.DrawRect( LocalRect.Shrink( 0f, 0f, 1f, 1f ), 4 );
Paint.SetPen( Theme.TextControl.WithAlpha( 0.9f ) );
Paint.DrawIcon( new Rect( 0f, 8f, Width, 46f ), icon, 40f, TextFlag.Center );
Paint.SetDefaultFont( 7.5f, 600 );
Paint.SetPen( Theme.Text.WithAlpha( 0.65f ) );
Paint.DrawText( new Rect( 4f, 56f, Width - 8f, 20f ), caption, TextFlag.Center );
}
}
sealed class ArchGuideStep : Widget {
readonly int number;
readonly string text;
public ArchGuideStep( int number, string text, float width ) {
this.number = number;
this.text = text;
FixedWidth = width;
FixedHeight = MathF.Ceiling( text.Length / 74f ) * 17f + 12f;
}
protected override void OnPaint() {
Paint.Antialiasing = true;
Paint.TextAntialiasing = true;
var badge = new Rect( 0f, 3f, 16f, 16f );
Paint.SetBrushAndPen( Theme.Blue.WithAlpha( 0.85f ) );
Paint.DrawRect( badge, 3 );
Paint.SetPen( Theme.TabBackground );
Paint.SetDefaultFont( 6.5f, 700 );
Paint.DrawText( badge, number.ToString(), TextFlag.Center );
Paint.SetDefaultFont( 10.5f, 400 );
Paint.SetPen( Theme.Text.WithAlpha( 0.8f ) );
Paint.DrawText( new Rect( 26f, 0f, Width - 26f, Height ), text, TextFlag.LeftTop | TextFlag.WordWrap );
}
}