diff --git a/src/LittleSim/Scenes/BootScene.cs b/src/LittleSim/Scenes/BootScene.cs index dc52f14..1717b28 100644 --- a/src/LittleSim/Scenes/BootScene.cs +++ b/src/LittleSim/Scenes/BootScene.cs @@ -53,7 +53,7 @@ public sealed class BootScene : Scene HorizontalAlignment = HorizontalAlignment.Center, VerticalAlignment = VerticalAlignment.Center, }; - desktop.Root = Ui.Screen(_label); + desktop.Root = Ui.MenuScreen(_label); // Сборка атласов и дефов — без GPU, поэтому безопасно вне главного потока. _load = Task.Run(() => GameContent.Load()); diff --git a/src/LittleSim/Scenes/CreditsScene.cs b/src/LittleSim/Scenes/CreditsScene.cs index 254e6ec..b07f88e 100644 --- a/src/LittleSim/Scenes/CreditsScene.cs +++ b/src/LittleSim/Scenes/CreditsScene.cs @@ -17,15 +17,15 @@ public sealed class CreditsScene : Scene var input = this.UseInput(); var desktop = this.UseScaledUI(); // ставит MyraEnvironment.Game до создания виджетов - var column = Ui.Column(12); + var column = Ui.Card(10); column.Widgets.Add(Ui.Title(lang.Get("credits.title"))); - column.Widgets.Add(new Myra.Graphics2D.UI.Label { Height = 16 }); + column.Widgets.Add(Ui.AccentRule()); column.Widgets.Add(Ui.Subtitle(lang.Get("credits.author"))); column.Widgets.Add(Ui.Subtitle(lang.Get("credits.role"))); - column.Widgets.Add(new Myra.Graphics2D.UI.Label { Height = 16 }); + column.Widgets.Add(new Myra.Graphics2D.UI.Label { Height = 12 }); column.Widgets.Add(Ui.Button(lang.Get("credits.back"), Back)); - desktop.Root = Ui.Screen(column); + desktop.Root = Ui.MenuScreen(column); UpdateSystems.Add( new CallbackSystem(() => { diff --git a/src/LittleSim/Scenes/LoadGameScene.cs b/src/LittleSim/Scenes/LoadGameScene.cs index 6437a13..4a7108e 100644 --- a/src/LittleSim/Scenes/LoadGameScene.cs +++ b/src/LittleSim/Scenes/LoadGameScene.cs @@ -41,9 +41,9 @@ public sealed class LoadGameScene : Scene private void Rebuild() { var lang = _content.Languages; - var column = Ui.Column(8); + var column = Ui.Card(8); column.Widgets.Add(Ui.Title(lang.Get("load.title"))); - column.Widgets.Add(new Label { Height = 8 }); + column.Widgets.Add(Ui.AccentRule()); var saves = _store.List(); if (saves.Count == 0) @@ -98,7 +98,7 @@ public sealed class LoadGameScene : Scene column.Widgets.Add(new Label { Height = 8 }); column.Widgets.Add(Ui.Button(lang.Get("load.back"), Back)); - _desktop.Root = Ui.Screen(column); + _desktop.Root = Ui.MenuScreen(column); } private void Back() diff --git a/src/LittleSim/Scenes/MainMenuScene.cs b/src/LittleSim/Scenes/MainMenuScene.cs index 73b1e9d..66e1c34 100644 --- a/src/LittleSim/Scenes/MainMenuScene.cs +++ b/src/LittleSim/Scenes/MainMenuScene.cs @@ -19,19 +19,19 @@ public sealed class MainMenuScene : Scene var lang = content.Languages; var desktop = this.UseScaledUI(); // ставит MyraEnvironment.Game до создания виджетов - var column = Ui.Column(12); - column.Widgets.Add(Ui.Title(lang.Get("menu.title"))); - column.Widgets.Add(Ui.Subtitle(lang.Get("menu.subtitle"))); - column.Widgets.Add(new Myra.Graphics2D.UI.Label { Height = 16 }); - column.Widgets.Add(Ui.Button(lang.Get("menu.newworld"), () => Go(new NewWorldScene()))); - column.Widgets.Add(Ui.Button(lang.Get("menu.load"), () => Go(new LoadGameScene()))); - column.Widgets.Add(Ui.Button(lang.Get("menu.settings"), () => Go(new SettingsScene()))); - column.Widgets.Add(Ui.Button(lang.Get("menu.credits"), () => Go(new CreditsScene()))); - column.Widgets.Add( + var card = Ui.Card(8); + card.Widgets.Add(Ui.Title(lang.Get("menu.title"))); + card.Widgets.Add(Ui.Subtitle(lang.Get("menu.subtitle"))); + card.Widgets.Add(Ui.AccentRule()); + card.Widgets.Add(Ui.Button(lang.Get("menu.newworld"), () => Go(new NewWorldScene()))); + card.Widgets.Add(Ui.Button(lang.Get("menu.load"), () => Go(new LoadGameScene()))); + card.Widgets.Add(Ui.Button(lang.Get("menu.settings"), () => Go(new SettingsScene()))); + card.Widgets.Add(Ui.Button(lang.Get("menu.credits"), () => Go(new CreditsScene()))); + card.Widgets.Add( Ui.Button(lang.Get("menu.quit"), () => Context.Services.Get().Exit()) ); - desktop.Root = Ui.Screen(column); + desktop.Root = Ui.MenuScreen(card); } private void Go(Scene scene) diff --git a/src/LittleSim/Scenes/NewWorldScene.cs b/src/LittleSim/Scenes/NewWorldScene.cs index c9c3f96..2f88a18 100644 --- a/src/LittleSim/Scenes/NewWorldScene.cs +++ b/src/LittleSim/Scenes/NewWorldScene.cs @@ -38,9 +38,9 @@ public sealed class NewWorldScene : Scene var presets = content.Defs.All().OrderBy(p => p.Order).ToList(); _preset = presets[0]; - var column = Ui.Column(10); + var column = Ui.Card(10); column.Widgets.Add(Ui.Title(lang.Get("newworld.title"))); - column.Widgets.Add(new Label { Height = 8 }); + column.Widgets.Add(Ui.AccentRule()); _name = new TextBox { Text = lang.Get("newworld.defaultname"), Width = 280 }; column.Widgets.Add(LabeledRow(lang.Get("newworld.name"), _name)); @@ -106,7 +106,7 @@ public sealed class NewWorldScene : Scene buttons.Widgets.Add(Ui.Button(lang.Get("newworld.back"), Back, width: 150)); column.Widgets.Add(buttons); - desktop.Root = Ui.Screen(column); + desktop.Root = Ui.MenuScreen(column); Refresh(); UpdateSystems.Add( diff --git a/src/LittleSim/Scenes/SettingsScene.cs b/src/LittleSim/Scenes/SettingsScene.cs index f4986df..d26df42 100644 --- a/src/LittleSim/Scenes/SettingsScene.cs +++ b/src/LittleSim/Scenes/SettingsScene.cs @@ -47,7 +47,9 @@ public sealed class SettingsScene : Scene } private void Rebuild() => - _desktop.Root = Ui.Screen(SettingsPanel.Build(_settings, _content.Languages, Apply, Back)); + _desktop.Root = Ui.MenuScreen( + SettingsPanel.Build(_settings, _content.Languages, Apply, Back) + ); private void Apply() { diff --git a/src/LittleSim/Scenes/WorldLoadingScene.cs b/src/LittleSim/Scenes/WorldLoadingScene.cs index 44771f6..f655935 100644 --- a/src/LittleSim/Scenes/WorldLoadingScene.cs +++ b/src/LittleSim/Scenes/WorldLoadingScene.cs @@ -39,7 +39,7 @@ public sealed class WorldLoadingScene : Scene HorizontalAlignment = HorizontalAlignment.Center, VerticalAlignment = VerticalAlignment.Center, }; - desktop.Root = Ui.Screen(_label); + desktop.Root = Ui.MenuScreen(_label); UpdateSystems.Add(new CallbackSystem(Tick)); } diff --git a/src/LittleSim/UI/Menu.cs b/src/LittleSim/UI/Menu.cs index f1e3f10..0d4de85 100644 --- a/src/LittleSim/UI/Menu.cs +++ b/src/LittleSim/UI/Menu.cs @@ -1,13 +1,18 @@ using System; using Friflo.Engine.ECS.Systems; using Microsoft.Xna.Framework; +using Myra.Graphics2D; +using Myra.Graphics2D.Brushes; using Myra.Graphics2D.UI; namespace LittleSim.UI; /// /// Общие фабрики виджетов Myra для экранов меню — единый стиль и отсутствие дублирования -/// разметки между сценами. Все подписи приходят готовыми строками (локализованными вызывающим). +/// разметки между сценами. Тёмная тема с холодным акцентом: затемнённый фон-подложка на весь +/// экран (), карточки с рамкой (), кнопки с подсветкой +/// при наведении и нажатии (). Все подписи приходят готовыми строками +/// (локализованными вызывающим). /// internal static class Ui { @@ -17,7 +22,19 @@ internal static class Ui /// Приглушённый цвет вторичного текста. public static readonly Color Muted = new(150, 160, 175); - private const int ButtonWidth = 280; + /// Основной цвет текста на кнопках/карточках. + public static readonly Color Text = new(214, 222, 232); + + // Палитра тёмной темы (фон → карточка → кнопка и её состояния). + private static readonly Color Backdrop = new(12, 15, 21); + private static readonly Color CardBg = new(22, 28, 38, 235); + private static readonly Color CardBorder = new(70, 100, 134); + private static readonly Color ButtonBg = new(30, 39, 52); + private static readonly Color ButtonOver = new(46, 64, 88); + private static readonly Color ButtonPress = new(62, 88, 120); + private static readonly Color ButtonBorder = new(64, 88, 118); + + private const int ButtonWidth = 300; /// Крупный заголовок по центру. public static Label Title(string text) => @@ -37,7 +54,18 @@ internal static class Ui HorizontalAlignment = HorizontalAlignment.Center, }; - /// Кнопка фиксированной ширины с обработчиком клика. + /// Тонкая акцентная черта-разделитель по центру (под заголовком). + public static Panel AccentRule(int width = 130) => + new() + { + Width = width, + Height = 2, + HorizontalAlignment = HorizontalAlignment.Center, + Margin = new Thickness(0, 4, 0, 10), + Background = new SolidBrush(Accent), + }; + + /// Кнопка фиксированной ширины: тёмная заливка, акцентная рамка, подсветка hover/press. public static TextButton Button(string text, Action onClick, int width = ButtonWidth) { var button = new TextButton @@ -45,6 +73,13 @@ internal static class Ui Text = text, Width = width, HorizontalAlignment = HorizontalAlignment.Center, + Padding = new Thickness(12, 9), + TextColor = Text, + Background = new SolidBrush(ButtonBg), + OverBackground = new SolidBrush(ButtonOver), + PressedBackground = new SolidBrush(ButtonPress), + Border = new SolidBrush(ButtonBorder), + BorderThickness = new Thickness(1), }; button.Click += (_, _) => onClick(); return button; @@ -59,11 +94,25 @@ internal static class Ui VerticalAlignment = VerticalAlignment.Center, }; + /// Карточка по центру: вертикальная колонка с тёмным фоном, акцентной рамкой и отступами. + public static VerticalStackPanel Card(int spacing = 8) => + new() + { + Spacing = spacing, + Padding = new Thickness(30, 26), + HorizontalAlignment = HorizontalAlignment.Center, + VerticalAlignment = VerticalAlignment.Center, + Background = new SolidBrush(CardBg), + Border = new SolidBrush(CardBorder), + BorderThickness = new Thickness(1), + }; + /// Горизонтальный ряд. public static HorizontalStackPanel Row(int spacing = 8) => new() { Spacing = spacing, HorizontalAlignment = HorizontalAlignment.Center }; - /// Оборачивает содержимое в панель на весь экран (абсолютное позиционирование детей). + /// Оборачивает содержимое в панель на весь экран (абсолютное позиционирование детей). + /// Без фона — годится и для HUD поверх игрового мира. public static Panel Screen(params Widget[] children) { var panel = new Panel(); @@ -74,6 +123,30 @@ internal static class Ui return panel; } + + /// + /// Как , но с тёмной подложкой на весь экран под содержимым — для экранов меню, + /// чтобы фон был единообразно тёмным независимо от цвета очистки кадра. НЕ использовать для HUD над + /// игровым миром (подложка непрозрачна и скрыла бы мир). + /// + public static Panel MenuScreen(params Widget[] children) + { + var panel = new Panel(); + panel.Widgets.Add( + new Panel + { + HorizontalAlignment = HorizontalAlignment.Stretch, + VerticalAlignment = VerticalAlignment.Stretch, + Background = new SolidBrush(Backdrop), + } + ); + foreach (var child in children) + { + panel.Widgets.Add(child); + } + + return panel; + } } ///