/* Copyright (c) 2013 yvt This file is part of OpenSpades. OpenSpades is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. OpenSpades is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenSpades. If not, see . */ #include "DropDownList.as" namespace spades { class StartupScreenUI { private Renderer@ renderer; private AudioDevice@ audioDevice; Font@ font; StartupScreenHelper@ helper; spades::ui::UIManager@ manager; StartupScreenMainMenu@ mainMenu; bool shouldExit = false; StartupScreenUI(Renderer@ renderer, AudioDevice@ audioDevice, Font@ font, StartupScreenHelper@ helper) { @this.renderer = renderer; @this.audioDevice = audioDevice; @this.font = font; @this.helper = helper; SetupRenderer(); @manager = spades::ui::UIManager(renderer, audioDevice); @manager.RootElement.Font = font; @mainMenu = StartupScreenMainMenu(this); mainMenu.Bounds = manager.RootElement.Bounds; manager.RootElement.AddChild(mainMenu); } void SetupRenderer() { if(manager !is null) manager.KeyPanic(); } void MouseEvent(float x, float y) { manager.MouseEvent(x, y); } void WheelEvent(float x, float y) { manager.WheelEvent(x, y); } void KeyEvent(string key, bool down) { manager.KeyEvent(key, down); } void TextInputEvent(string text) { manager.TextInputEvent(text); } void TextEditingEvent(string text, int start, int len) { manager.TextEditingEvent(text, start, len); } bool AcceptsTextInput() { return manager.AcceptsTextInput; } AABB2 GetTextInputRect() { return manager.TextInputRect; } void RunFrame(float dt) { renderer.ColorNP = Vector4(0.f, 0.f, 0.f, 1.f); renderer.DrawImage(renderer.RegisterImage("Gfx/White.tga"), AABB2(0.f, 0.f, renderer.ScreenWidth, renderer.ScreenHeight)); // draw title logo Image@ img = renderer.RegisterImage("Gfx/Title/LogoSmall.png"); renderer.ColorNP = Vector4(1.f, 1.f, 1.f, 1.f); renderer.DrawImage(img, AABB2(10.f, 10.f, img.Width, img.Height)); manager.RunFrame(dt); manager.Render(); renderer.FrameDone(); renderer.Flip(); } void Closing() { shouldExit = true; } bool WantsToBeClosed() { return shouldExit; } } class StartupScreenMainMenu: spades::ui::UIElement { StartupScreenUI@ ui; StartupScreenHelper@ helper; spades::ui::ListView@ serverList; spades::ui::CheckBox@ bypassStartupWindowCheck; StartupScreenGraphicsTab@ graphicsTab; StartupScreenAudioTab@ audioTab; StartupScreenAdvancedTab@ advancedTab; private ConfigItem cl_showStartupWindow("cl_showStartupWindow", "-1"); private bool advancedTabVisible = false; StartupScreenMainMenu(StartupScreenUI@ ui) { super(ui.manager); @this.ui = ui; @this.helper = ui.helper; @this.Font = ui.font; float width = Manager.Renderer.ScreenWidth; float height = Manager.Renderer.ScreenHeight; { spades::ui::Button button(Manager); button.Caption = _Tr("StartupScreen", "Start"); button.Bounds = AABB2(width - 170.f, 20.f, 150.f, 30.f); button.Activated = EventHandler(this.OnStartPressed); AddChild(button); } { spades::ui::CheckBox button(Manager); button.Caption = _Tr("StartupScreen", "Skip this screen next time"); button.Bounds = AABB2(360.f, 62.f, width - 380.f, 20.f); AddChild(button); @bypassStartupWindowCheck = button; button.Activated = EventHandler(this.OnBypassStartupWindowCheckChanged); } AABB2 clientArea(10.f, 100.f, width - 20.f, height - 110.f); StartupScreenGraphicsTab graphicsTab(ui, clientArea.max - clientArea.min); StartupScreenAudioTab audioTab(ui, clientArea.max - clientArea.min); StartupScreenSystemInfoTab profileTab(ui, clientArea.max - clientArea.min); StartupScreenAdvancedTab advancedTab(ui, clientArea.max - clientArea.min); graphicsTab.Bounds = clientArea; audioTab.Bounds = clientArea; profileTab.Bounds = clientArea; advancedTab.Bounds = clientArea; AddChild(graphicsTab); AddChild(audioTab); AddChild(profileTab); AddChild(advancedTab); audioTab.Visible = false; profileTab.Visible = false; advancedTab.Visible = false; @this.graphicsTab = graphicsTab; @this.audioTab = audioTab; @this.advancedTab = advancedTab; { spades::ui::SimpleTabStrip tabStrip(Manager); AddChild(tabStrip); tabStrip.Bounds = AABB2(10.f, 70.f, width - 20.f, 24.f); tabStrip.AddItem(_Tr("StartupScreen", "Graphics"), graphicsTab); tabStrip.AddItem(_Tr("StartupScreen", "Audio"), audioTab); tabStrip.AddItem(_Tr("StartupScreen", "System Info"), profileTab); tabStrip.AddItem(_Tr("StartupScreen", "Advanced"), advancedTab); @tabStrip.Changed = EventHandler(this.OnTabChanged); } LoadConfig(); } private void OnTabChanged(spades::ui::UIElement@) { bool v = advancedTab.Visible; if(advancedTabVisible and not v) { LoadConfig(); } advancedTabVisible = v; } void LoadConfig() { switch(cl_showStartupWindow.IntValue) { case -1: bypassStartupWindowCheck.Toggled = false; break; case 0: bypassStartupWindowCheck.Toggled = true; break; default: bypassStartupWindowCheck.Toggled = false; break; } this.graphicsTab.LoadConfig(); this.audioTab.LoadConfig(); this.advancedTab.LoadConfig(); } private void OnBypassStartupWindowCheckChanged(spades::ui::UIElement@ sender) { cl_showStartupWindow.IntValue = (bypassStartupWindowCheck.Toggled ? 0 : 1); } private void OnQuitPressed(spades::ui::UIElement@ sender) { ui.shouldExit = true; } private void OnSetupPressed(spades::ui::UIElement@ sender) { PreferenceView al(this, PreferenceViewOptions()); al.Run(); } private void Start() { helper.Start(); ui.shouldExit = true; // we have to exit from the startup screen to start the game } private void OnStartPressed(spades::ui::UIElement@ sender) { Start(); } void HotKey(string key) { if(IsEnabled and key == "Enter") { Start(); } else if(IsEnabled and key == "Escape") { ui.shouldExit = true; } else { UIElement::HotKey(key); } } void Render() { UIElement::Render(); } } funcdef void HelpTextHandler(string text); class ChainedEventHandler { EventHandler@ h; EventHandler@ h2; ChainedEventHandler(EventHandler@ h, EventHandler@ h2) { @this.h = h; @this.h2 = h2; } void Handler(spades::ui::UIElement@ e) { h(e); h2(e); } } class HelpHandler { private HelpTextHandler@ handler; private spades::ui::TextViewer@ helpView; private string text; HelpHandler(spades::ui::TextViewer@ helpView, string text) { this.text = text; @this.helpView = helpView; } HelpHandler(HelpTextHandler@ handler, string text) { this.text = text; @this.handler = handler; } private void OnMouseHover(spades::ui::UIElement@ elm) { if(helpView !is null) { helpView.Text = text; } if(handler !is null) { handler(text); } } void Watch(spades::ui::UIElement@ elm) { @elm.MouseEntered = EventHandler(this.OnMouseHover); } void WatchDeep(spades::ui::UIElement@ elm) { if(elm.MouseEntered !is null) { ChainedEventHandler chain(elm.MouseEntered, EventHandler(this.OnMouseHover)); @elm.MouseEntered = EventHandler(chain.Handler); }else{ Watch(elm); } spades::ui::UIElementIterator it(elm); while(it.MoveNext()) { WatchDeep(it.Current); } } } mixin class LabelAddable { private void AddLabel(float x, float y, float h, string text) { spades::ui::Label label(Manager); Font@ font = ui.font; Vector2 siz = font.Measure(text); label.Text = text; label.Alignment = Vector2(0.f, 0.5f); label.Bounds = AABB2(x, y, siz.x, h); AddChild(label); } } class StartupScreenConfigViewModel: spades::ui::ListViewModel { spades::ui::UIElement@[] items; spades::ui::UIElement@[]@ items2; StartupScreenConfigViewModel() { } int NumRows { get { if(items2 !is null) return int(items2.length); return int(items.length); } } void Filter(string text) { if(text.length == 0) { @items2 = null; return; } spades::ui::UIElement@[] newItems; for(uint i = 0, count = items.length; i < count; i++) { StartupScreenConfigItemEditor@ editor = cast(items[i]); if(editor is null) continue; string label = editor.GetLabel(); if(StringContainsCaseInsensitive(label, text)) { newItems.insertLast(items[i]); } } @items2 = newItems; } spades::ui::UIElement@ CreateElement(int row) { if(items2 !is null) return items2[row]; return items[row]; } void RecycleElement(spades::ui::UIElement@ elem) { } } interface StartupScreenGenericConfig { string GetValue(); void SetValue(string); /** Returns an empty string when there's no problem. */ string CheckValueCapability(string); } class StartupScreenConfig: StartupScreenGenericConfig { private StartupScreenUI@ ui; private ConfigItem@ cfg; private string cfgName; StartupScreenConfig(StartupScreenUI@ ui, string cfg) { @this.ui = ui; @this.cfg = ConfigItem(cfg); cfgName = cfg; } string GetValue() { return cfg.StringValue; } void SetValue(string v) { cfg.StringValue = v; } string CheckValueCapability(string v) { return ui.helper.CheckConfigCapability(cfgName, v); } } class StartupScreenConfigSetter { StartupScreenGenericConfig@ c; string value; StartupScreenConfigSetter(StartupScreenGenericConfig@ c, string value) { @this.c = c; this.value = value; } void Set(spades::ui::UIElement@) { c.SetValue(this.value); } } interface StartupScreenConfigItemEditor { void LoadConfig(); StartupScreenGenericConfig@ GetConfig(); void SetHelpTextHandler(HelpTextHandler@); string GetLabel(); } class StartupScreenConfigSelectItemEditor: spades::ui::UIElement, LabelAddable, StartupScreenConfigItemEditor, StartupScreenConfigItemEditor { private StartupScreenUI@ ui; private string[]@ descs; private string[]@ values; private StartupScreenGenericConfig@ config; private spades::ui::RadioButton@[] buttons; private string label; StartupScreenConfigSelectItemEditor(StartupScreenUI@ ui, StartupScreenGenericConfig@ cfg, string values, string descs) { super(ui.manager); @this.ui = ui; @this.descs = descs.split("|"); @config = cfg; @this.values = values.split("|"); { string desc = this.descs[0]; int idx = desc.findFirst(":"); if(idx >= 0) { desc = desc.substr(0, idx); } AddLabel(0, 0, 24.f, desc); this.label = desc; } for(uint i = 0; i < this.values.length; i++) { spades::ui::RadioButton b(Manager); string desc = this.descs[i + 1]; int idx = desc.findFirst(":"); if(idx >= 0) { desc = desc.substr(0, idx); } b.Caption = desc; b.GroupName = "hoge"; StartupScreenConfigSetter setter(config, this.values[i]); @b.Activated = EventHandler(setter.Set); buttons.insertLast(b); this.AddChild(b); } } string GetLabel() { return label; } void LoadConfig() { string val = config.GetValue(); for(uint i = 0, count = values.length; i < count; i++) { buttons[i].Toggled = (values[i] == val); buttons[i].Enable = CheckValueCapability(values[i]).length == 0; } } string CheckValueCapability(string v) { return config.CheckValueCapability(v); } StartupScreenGenericConfig@ GetConfig() { return config; } void SetHelpTextHandler(HelpTextHandler@ handler) { for(uint i = 0, count = values.length; i < count; i++) { string desc = descs[i + 1]; int idx = desc.findFirst(":"); if(idx < 0) { desc = descs[0]; idx = desc.findFirst(":"); } desc = desc.substr(uint(idx + 1)); HelpHandler(handler, desc).Watch(buttons[i]); } } void set_Bounds(AABB2 v) { UIElement::set_Bounds(v); Vector2 size = this.Size; float h = 24.f; float x = size.x; for(uint i = buttons.length; i > 0; i--) { spades::ui::RadioButton@ b = buttons[i - 1]; float w = ui.font.Measure(b.Caption).x + 26.f; x -= w + 2.f; b.Bounds = AABB2(x, 0.f, w, h); } } } class StartupScreenConfigCheckItemEditor: spades::ui::UIElement, StartupScreenConfigItemEditor { private StartupScreenUI@ ui; private string desc; private string valueOff; private string valueOn; private StartupScreenGenericConfig@ config; private spades::ui::CheckBox@ button; private string label; StartupScreenConfigCheckItemEditor(StartupScreenUI@ ui, StartupScreenGenericConfig@ cfg, string valueOff, string valueOn, string label, string desc) { super(ui.manager); @this.ui = ui; @config = cfg; this.valueOff = valueOff; this.valueOn = valueOn; this.desc = desc; spades::ui::CheckBox b(Manager); b.Caption = label; this.label = label; @b.Activated = EventHandler(this.StateChanged); @button = b; this.AddChild(b); } string GetLabel() { return label; } void LoadConfig() { string val = config.GetValue(); button.Toggled = (val != valueOff); button.Enable = CheckValueCapability(valueOn).length == 0; } string CheckValueCapability(string v) { return config.CheckValueCapability(v); } StartupScreenGenericConfig@ GetConfig() { return config; } private void StateChanged(spades::ui::UIElement@) { config.SetValue(button.Toggled ? valueOn : valueOff); } void SetHelpTextHandler(HelpTextHandler@ handler) { HelpHandler(handler, desc).Watch(button); } void set_Bounds(AABB2 v) { UIElement::set_Bounds(v); Vector2 size = this.Size; float h = 24.f; button.Bounds = AABB2(0.f, 0.f, size.x, h); } } class StartupScreenConfigSliderItemEditor: spades::ui::UIElement, StartupScreenConfigItemEditor, LabelAddable { private StartupScreenUI@ ui; private string desc; private double stepSize; private StartupScreenGenericConfig@ config; private spades::ui::Slider@ slider; private spades::ui::Label@ valueLabel; private ConfigNumberFormatter@ formatter; private string label; StartupScreenConfigSliderItemEditor(StartupScreenUI@ ui, StartupScreenGenericConfig@ cfg, double minValue, double maxValue, double stepSize, string label, string desc, ConfigNumberFormatter@ formatter) { super(ui.manager); @this.ui = ui; @config = cfg; this.desc = desc; this.stepSize = stepSize; this.label = label; AddLabel(0, 0, 24.f, label); spades::ui::Slider b(Manager); b.MinValue = minValue; b.MaxValue = maxValue; // compute large change int steps = int((maxValue - minValue) / stepSize); steps = (steps + 9) / 10; b.LargeChange = float(steps) * stepSize; b.SmallChange = stepSize; @b.Changed = EventHandler(this.StateChanged); @slider = b; this.AddChild(b); spades::ui::Label l(Manager); @valueLabel = l; l.Alignment = Vector2(1.f, 0.5f); this.AddChild(l); @this.formatter = formatter; } private void UpdateLabel() { double v = slider.Value; string s = formatter.Format(v); valueLabel.Text = s; } string GetLabel() { return label; } void LoadConfig() { string val = config.GetValue(); double v = ParseDouble(val); // don't use `ScrollTo` here; this calls `Changed` so // out-of-range value will be clamped and saved back slider.Value = Clamp(v, slider.MinValue, slider.MaxValue); // FIXME: button.Enable = CheckValueCapability(valueOn).length == 0; UpdateLabel(); } string CheckValueCapability(string v) { return ""; //FIXME: config.CheckValueCapability(v); } StartupScreenGenericConfig@ GetConfig() { return config; } void DoRounding() { double v = double(slider.Value - slider.MinValue); v = floor((v / stepSize) + 0.5) * stepSize; v += double(slider.MinValue); slider.Value = v; } private void StateChanged(spades::ui::UIElement@) { DoRounding(); config.SetValue(ToString(slider.Value)); UpdateLabel(); } void SetHelpTextHandler(HelpTextHandler@ handler) { HelpHandler(handler, desc).WatchDeep(slider); } void set_Bounds(AABB2 v) { UIElement::set_Bounds(v); Vector2 size = this.Size; float h = 24.f; slider.Bounds = AABB2(100.f, 2.f, size.x - 180.f, h - 4.f); valueLabel.Bounds = AABB2(size.x - 80.f, 0.f, 80.f, h); } } class StartupScreenConfigFieldItemEditor: spades::ui::UIElement, StartupScreenConfigItemEditor, LabelAddable { private StartupScreenUI@ ui; private string desc; private double stepSize; private StartupScreenGenericConfig@ config; private spades::ui::Field@ field; private string label; StartupScreenConfigFieldItemEditor(StartupScreenUI@ ui, StartupScreenGenericConfig@ cfg, string label, string desc) { super(ui.manager); @this.ui = ui; @config = cfg; this.desc = desc; this.label = label; AddLabel(0, 0, 24.f, label); spades::ui::Field b(Manager); @b.Changed = EventHandler(this.StateChanged); @field = b; this.AddChild(b); } string GetLabel() { return label; } void LoadConfig() { string val = config.GetValue(); field.Text = val; } string CheckValueCapability(string v) { return config.CheckValueCapability(v); } StartupScreenGenericConfig@ GetConfig() { return config; } private void StateChanged(spades::ui::UIElement@) { config.SetValue(field.Text); } void SetHelpTextHandler(HelpTextHandler@ handler) { HelpHandler(handler, desc).WatchDeep(field); } void set_Bounds(AABB2 v) { UIElement::set_Bounds(v); Vector2 size = this.Size; float h = 24.f; field.Bounds = AABB2(240.f, 0.f, size.x - 240.f, h); } } // Maps multiple configs to {"0", "1", ...} or "custom" class StartupScreenComplexConfig: StartupScreenGenericConfig { StartupScreenConfigItemEditor@[] editors; StartupScreenComplexConfigPreset@[] presets; void AddEditor(StartupScreenConfigItemEditor@ editor) { Assert(presets.length == 0); editors.insertLast(editor); } void AddPreset(StartupScreenComplexConfigPreset@ preset) { Assert(preset.Values.length == editors.length); presets.insertLast(preset); } string GetValue() { string[] values; for(uint i = 0; i < editors.length; i++) { values.insertLast(editors[i].GetConfig().GetValue()); } for(uint i = 0; i < presets.length; i++) { string[]@ pval = presets[i].Values; uint j = 0; uint jc = pval.length; for(; j < jc; j++) { if(values[j] != pval[j]) { break; } } if(j == jc) { return ToString(int(i)); } } return "custom"; } void SetValue(string v) { if(v == "custom") { return; } uint pId = uint(ParseInt(v)); string[]@ pval = presets[pId].Values; for(uint i = 0; i < pval.length; i++) { editors[i].GetConfig().SetValue(pval[i]); } } string CheckValueCapability(string v) { if(v == "custom") { return ""; } uint pId = uint(ParseInt(v)); string[]@ pval = presets[pId].Values; string ret = ""; for(uint i = 0; i < pval.length; i++) { ret += editors[i].GetConfig().CheckValueCapability(pval[i]); } return ret; } // used for StartupScreenConfigSelectItemEditor's ctor param string GetValuesString() { string[] lst; for(uint i = 0; i < presets.length; i++) { lst.insertLast(ToString(int(i))); } lst.insertLast("custom"); return join(lst, "|"); } // used for StartupScreenConfigSelectItemEditor's ctor param string GetDescriptionsString() { string[] lst; for(uint i = 0; i < presets.length; i++) { lst.insertLast(presets[i].Name); } lst.insertLast(_Tr("StartupScreen", "Custom")); return join(lst, "|"); } } class StartupScreenComplexConfigPreset { string Name; string[] Values; StartupScreenComplexConfigPreset(string name, string values) { Name = name; Values = values.split("|"); } } class StartupScreenConfigComplexItemEditor: StartupScreenConfigSelectItemEditor { private string dlgTitle; StartupScreenConfigComplexItemEditor(StartupScreenUI@ ui, StartupScreenComplexConfig@ cfg, string label, string desc) { super(ui, cfg, cfg.GetValuesString(), label + ":" + desc + "|" + cfg.GetDescriptionsString()); dlgTitle = label; @buttons[buttons.length - 1].Activated = EventHandler(this.CustomClicked); } private void CustomClicked(spades::ui::UIElement@) { RunDialog(); } private void DialogDone(spades::ui::UIElement@) { LoadConfig(); } private void RunDialog() { StartupScreenComplexConfigDialog dlg(ui.manager, cast(config)); dlg.DialogDone = EventHandler(this.DialogDone); dlg.Title = dlgTitle; dlg.RunDialog(); } } class StartupScreenConfigView: spades::ui::ListViewBase { private StartupScreenConfigViewModel vmodel; StartupScreenConfigView(spades::ui::UIManager@ manager) { super(manager); this.RowHeight = 30.f; } void Finalize() { @this.Model = vmodel; } void AddRow(spades::ui::UIElement@ elm) { vmodel.items.insertLast(elm); } void Filter(string text) { vmodel.Filter(text); Reload(); } void SetHelpTextHandler(HelpTextHandler@ handler) { spades::ui::UIElement@[]@ elms = vmodel.items; for(uint i = 0; i < elms.length; i++) { StartupScreenConfigItemEditor@ item = cast(elms[i]); if(item !is null) { item.SetHelpTextHandler(handler); } } } void LoadConfig() { spades::ui::UIElement@[]@ elms = vmodel.items; for(uint i = 0; i < elms.length; i++) { StartupScreenConfigItemEditor@ item = cast(elms[i]); if(item !is null) { item.LoadConfig(); } } } } class StartupScreenComplexConfigDialog: spades::ui::UIElement { StartupScreenComplexConfig@ config; float ContentsTop = 50.f; float ContentsHeight; string Title; spades::ui::TextViewer@ helpView; StartupScreenConfigView@ configView; spades::ui::EventHandler@ DialogDone; private spades::ui::UIElement@ oldRoot; StartupScreenComplexConfigDialog(spades::ui::UIManager@ manager, StartupScreenComplexConfig@ config) { super(manager); @this.config = config; Vector2 size = manager.RootElement.Size; ContentsHeight = size.y - ContentsTop * 2.f; float mainWidth = size.x - 250.f; { spades::ui::TextViewer e(Manager); e.Bounds = AABB2(mainWidth, ContentsTop + 30.f, size.x - mainWidth - 10.f, ContentsHeight - 60.f); AddChild(e); @helpView = e; } { StartupScreenConfigView cfg(Manager); StartupScreenConfigItemEditor@[]@ editors = config.editors; for(uint i = 0; i < editors.length; i++) cfg.AddRow(cast(editors[i])); cfg.Finalize(); cfg.SetHelpTextHandler(HelpTextHandler(this.HandleHelpText)); cfg.Bounds = AABB2(10.f, ContentsTop + 30.f, mainWidth - 20.f, ContentsHeight - 60.f); AddChild(cfg); @configView = cfg; } { spades::ui::Button button(Manager); button.Caption = _Tr("StartupScreen", "Close"); button.Bounds = AABB2(size.x - 160.f, ContentsTop + ContentsHeight - 30.f, 150.f, 30.f); button.Activated = EventHandler(this.CloseActivated); AddChild(button); } } private void CloseActivated(spades::ui::UIElement@) { if(oldRoot !is null) { oldRoot.Enable = true; @oldRoot = null; } @this.Parent = null; DialogDone(this); } private void HandleHelpText(string s) { helpView.Text = s; } void RunDialog() { spades::ui::UIElement@ root = Manager.RootElement; spades::ui::UIElementIterator iterator(root); while(iterator.MoveNext()) { spades::ui::UIElement@ e = iterator.Current; if(e.Enable and e.Visible) { @oldRoot = e; e.Enable = false; break; } } this.Bounds = root.Bounds; root.AddChild(this); configView.LoadConfig(); } void Render() { Vector2 pos = ScreenPosition; Vector2 size = Size; Renderer@ r = Manager.Renderer; Image@ img = r.RegisterImage("Gfx/White.tga"); r.ColorNP = Vector4(0, 0, 0, 0.8f); r.DrawImage(img, AABB2(pos.x, pos.y, size.x, ContentsTop - 15.f)); r.DrawImage(img, AABB2(pos.x, ContentsTop + ContentsHeight + 15.f, size.x, ContentsTop - 15.f)); r.ColorNP = Vector4(0, 0, 0, 0.95f); r.DrawImage(img, AABB2(pos.x, ContentsTop - 15.f, size.x, ContentsHeight + 30.f)); r.ColorNP = Vector4(1, 1, 1, 0.08f); r.DrawImage(img, AABB2(pos.x, pos.y + ContentsTop - 15.f, size.x, 1.f)); r.DrawImage(img, AABB2(pos.x, pos.y + ContentsTop + ContentsHeight + 15.f, size.x, 1.f)); r.ColorNP = Vector4(1, 1, 1, 0.2f); r.DrawImage(img, AABB2(pos.x, pos.y + ContentsTop - 14.f, size.x, 1.f)); r.DrawImage(img, AABB2(pos.x, pos.y + ContentsTop + ContentsHeight + 14.f, size.x, 1.f)); Font@ font = Font; r.ColorNP = Vector4(0.8f, 0.8f, 0.8f, 1.f); r.DrawImage(img, AABB2(pos.x, pos.y + ContentsTop, size.x, 20.f)); font.Draw(Title, Vector2(pos.x + 10.f, pos.y + ContentsTop), 1.f, Vector4(0.f, 0.f, 0.f, 1.f)); UIElement::Render(); } } class StartupScreenGraphicsTab: spades::ui::UIElement, LabelAddable { StartupScreenUI@ ui; StartupScreenHelper@ helper; StartupScreenGraphicsDisplayResolutionEditor@ resEdit; spades::ui::CheckBox@ fullscreenCheck; spades::ui::RadioButton@ driverOpenGL; spades::ui::RadioButton@ driverSoftware; spades::ui::TextViewer@ helpView; StartupScreenConfigView@ configViewGL; StartupScreenConfigView@ configViewSoftware; private ConfigItem r_renderer("r_renderer"); private ConfigItem r_fullscreen("r_fullscreen"); StartupScreenGraphicsTab(StartupScreenUI@ ui, Vector2 size) { super(ui.manager); @this.ui = ui; @helper = ui.helper; float mainWidth = size.x - 250.f; { spades::ui::TextViewer e(Manager); e.Bounds = AABB2(mainWidth + 10.f, 0.f, size.x - mainWidth - 10.f, size.y); @e.Font = ui.font; e.Text = _Tr("StartupScreen", "Graphics Settings"); AddChild(e); @helpView = e; } AddLabel(0.f, 0.f, 24.f, _Tr("StartupScreen", "Resolution")); { StartupScreenGraphicsDisplayResolutionEditor e(ui); e.Bounds = AABB2(100.f, 0.f, 124.f, 24.f); AddChild(e); @resEdit = e; } { spades::ui::CheckBox e(Manager); e.Caption = _Tr("StartupScreen", "Fullscreen Mode"); e.Bounds = AABB2(230.f, 0.f, 200.f, 24.f); HelpHandler(helpView, _Tr("StartupScreen", "By running in fullscreen mode OpenSpades occupies the " "screen, making it easier for you to concentrate on playing the game.")).Watch(e); @e.Activated = EventHandler(this.OnFullscreenCheck); AddChild(e); @fullscreenCheck = e; } AddLabel(0.f, 30.f, 24.f, _Tr("StartupScreen", "Backend")); { spades::ui::RadioButton e(Manager); e.Caption = _Tr("StartupScreen", "OpenGL"); e.Bounds = AABB2(100.f, 30.f, 140.f, 24.f); e.GroupName = "driver"; HelpHandler(helpView, _Tr("StartupScreen", "OpenGL renderer uses your computer's graphics " "accelerator to generate the game screen.")).Watch(e); @e.Activated = EventHandler(this.OnDriverOpenGL); AddChild(e); @driverOpenGL = e; } { spades::ui::RadioButton e(Manager); e.Caption = _Tr("StartupScreen", "Software"); e.Bounds = AABB2(250.f, 30.f, 140.f, 24.f); e.GroupName = "driver"; HelpHandler(helpView, _Tr("StartupScreen", "Software renderer uses CPU to generate the game " "screen. Its quality and performance might be inferior to OpenGL " "renderer, but it works even with an unsupported GPU.")).Watch(e); @e.Activated = EventHandler(this.OnDriverSoftware); AddChild(e); @driverSoftware = e; } { StartupScreenConfigView cfg(Manager); cfg.AddRow(StartupScreenConfigSelectItemEditor(ui, StartupScreenGraphicsAntialiasConfig(ui), "0|2|4|fxaa", _Tr("StartupScreen", "Antialias:Enables a technique to improve the appearance of high-constrast edges.\n\n" "MSAA: Performs antialiasing by generating an intermediate high-resolution image. " "Looks best, but doesn't cope with some settings.\n\n" "FXAA: Performs antialiasing by smoothing artifacts out as a post-process.|" "Off|MSAA 2x|4x|FXAA"))); cfg.AddRow(StartupScreenConfigCheckItemEditor(ui, StartupScreenConfig(ui, "r_radiosity"), "0", "1", _Tr("StartupScreen", "Global Illumination"), _Tr("StartupScreen", "Enables a physically based simulation of light path for more realistic lighting."))); { StartupScreenComplexConfig cplx; cplx.AddEditor(StartupScreenConfigCheckItemEditor(ui, StartupScreenConfig(ui, "r_cameraBlur"), "0", "1", _Tr("StartupScreen", "Camera Blur"), _Tr("StartupScreen", "Blurs the screen when you turns quickly."))); cplx.AddEditor(StartupScreenConfigCheckItemEditor(ui, StartupScreenConfig(ui, "r_lens"), "0", "1", _Tr("StartupScreen", "Lens Effect"), _Tr("StartupScreen", "Simulates distortion caused by a real camera lens."))); cplx.AddEditor(StartupScreenConfigCheckItemEditor(ui, StartupScreenConfig(ui, "r_bloom"), "0", "1", _Tr("StartupScreen", "Lens Scattering Filter"), _Tr("StartupScreen", "Simulates light being scattered by dust on the camera lens."))); // r_lens is currently no-op cplx.AddEditor(StartupScreenConfigCheckItemEditor(ui, StartupScreenConfig(ui, "r_lensFlare"), "0", "1", _Tr("StartupScreen", "Lens Flare"), _Tr("StartupScreen", "The Sun causes lens flare."))); cplx.AddEditor(StartupScreenConfigCheckItemEditor(ui, StartupScreenConfig(ui, "r_lensFlareDynamic"), "0", "1", _Tr("StartupScreen", "Flares for Dynamic Lights"), _Tr("StartupScreen", "Enables lens flare for light sources other than the Sun."))); cplx.AddEditor(StartupScreenConfigCheckItemEditor(ui, StartupScreenConfig(ui, "r_colorCorrection"), "0", "1", _Tr("StartupScreen", "Color Correction"), _Tr("StartupScreen", "Applies cinematic color correction to make the image look better."))); cplx.AddEditor(StartupScreenConfigCheckItemEditor(ui, StartupScreenConfig(ui, "r_depthOfField"), "0", "1", _Tr("StartupScreen", "Depth of Field"), _Tr("StartupScreen", "Blurs out-of-focus objects."))); cplx.AddPreset(StartupScreenComplexConfigPreset(_Tr("StartupScreen", "Low"), "0|0|0|0|0|0|0")); cplx.AddPreset(StartupScreenComplexConfigPreset(_Tr("StartupScreen", "Medium"), "1|0|0|1|0|1|0")); cplx.AddPreset(StartupScreenComplexConfigPreset(_Tr("StartupScreen", "High"), "1|0|1|1|1|1|1")); cfg.AddRow(StartupScreenConfigComplexItemEditor(ui, cplx, _Tr("StartupScreen", "Post-process"), _Tr("StartupScreen", "Post-process modifies the image to make it look better and " "more realistic."))); } cfg.AddRow(StartupScreenConfigSelectItemEditor(ui, StartupScreenConfig(ui, "r_softParticles"), "0|1|2", _Tr("StartupScreen", "Particles|" "Low:Artifact occurs when a particle intersects other objects.|" "Medium:Particle intersects objects smoothly.|" "High:Particle intersects objects smoothly, and some objects casts " "their shadow to particles."))); { StartupScreenComplexConfig cplx; // r_mapSoftShadow is currently no-op cplx.AddEditor(StartupScreenConfigCheckItemEditor(ui, StartupScreenConfig(ui, "r_dlights"), "0", "1", _Tr("StartupScreen", "Dynamic Lights"), _Tr("StartupScreen", "Gives some objects an ability to emit light to give them " "an energy-emitting impression."))); cplx.AddEditor(StartupScreenConfigCheckItemEditor(ui, StartupScreenConfig(ui, "r_modelShadows"), "0", "1", _Tr("StartupScreen", "Shadows"), _Tr("StartupScreen", "Non-static object casts a shadow."))); cplx.AddEditor(StartupScreenConfigCheckItemEditor(ui, StartupScreenConfig(ui, "r_fogShadow"), "0", "1", _Tr("StartupScreen", "Volumetric Fog"), _Tr("StartupScreen", "Simulates shadow being casted to the fog particles using a " "super highly computationally demanding algorithm. "))); cplx.AddEditor(StartupScreenConfigCheckItemEditor(ui, StartupScreenConfig(ui, "r_physicalLighting"), "0", "1", _Tr("StartupScreen", "Physically Based Lighting"), _Tr("StartupScreen", "Uses more accurate approximation techniques to decide the brightness of objects."))); cplx.AddPreset(StartupScreenComplexConfigPreset(_Tr("StartupScreen", "Low"), "1|0|0|0")); cplx.AddPreset(StartupScreenComplexConfigPreset(_Tr("StartupScreen", "Medium"), "1|1|0|0")); cplx.AddPreset(StartupScreenComplexConfigPreset(_Tr("StartupScreen", "High"), "1|1|0|1")); cplx.AddPreset(StartupScreenComplexConfigPreset(_Tr("StartupScreen", "Ultra"), "1|1|1|1")); cfg.AddRow(StartupScreenConfigComplexItemEditor(ui, cplx, _Tr("StartupScreen", "Direct Lights"), _Tr("StartupScreen", "Controls how light encounting a material and atmosphere directly " "affects its appearance."))); } { StartupScreenComplexConfig cplx; cplx.AddEditor(StartupScreenConfigSelectItemEditor(ui, StartupScreenConfig(ui, "r_water"), "0|1|2", _Tr("StartupScreen", "Water Shader|" "None:Water is rendered in the same way that normal blocks are done.|" "Level 1:Refraction and the reflected Sun are simulated.|" "Level 2:Waving water is simulated as well as reflection and refraction."))); cplx.AddPreset(StartupScreenComplexConfigPreset(_Tr("StartupScreen", "Low"), "0")); cplx.AddPreset(StartupScreenComplexConfigPreset(_Tr("StartupScreen", "Medium"), "1")); cplx.AddPreset(StartupScreenComplexConfigPreset(_Tr("StartupScreen", "High"), "2")); cfg.AddRow(StartupScreenConfigComplexItemEditor(ui, cplx, _Tr("StartupScreen", "Shader Effects"), _Tr("StartupScreen", "Special effects."))); } cfg.Finalize(); cfg.SetHelpTextHandler(HelpTextHandler(this.HandleHelpText)); cfg.Bounds = AABB2(0.f, 60.f, mainWidth, size.y - 60.f); AddChild(cfg); @configViewGL = cfg; } { StartupScreenConfigView cfg(Manager); cfg.AddRow(StartupScreenConfigSelectItemEditor(ui, StartupScreenConfig(ui, "r_swUndersampling"), "0|1|2", _Tr("StartupScreen", "Fast Mode:Reduces the image resolution to make the rendering faster.|" "Off|2x|4x"))); cfg.Finalize(); cfg.SetHelpTextHandler(HelpTextHandler(this.HandleHelpText)); cfg.Bounds = AABB2(0.f, 60.f, mainWidth, size.y - 60.f); AddChild(cfg); @configViewSoftware = cfg; } } private void HandleHelpText(string text) { helpView.Text = text; } private void OnDriverOpenGL(spades::ui::UIElement@){ r_renderer.StringValue = "gl"; LoadConfig(); } private void OnDriverSoftware(spades::ui::UIElement@){ r_renderer.StringValue = "sw"; LoadConfig(); } private void OnFullscreenCheck(spades::ui::UIElement@) { r_fullscreen.IntValue = fullscreenCheck.Toggled ? 1 : 0; } void LoadConfig() { resEdit.LoadConfig(); if(r_renderer.StringValue == "sw") { driverSoftware.Check(); configViewGL.Visible = false; configViewSoftware.Visible = true; }else{ driverOpenGL.Check(); configViewGL.Visible = true; configViewSoftware.Visible = false; } fullscreenCheck.Toggled = r_fullscreen.IntValue != 0; driverOpenGL.Enable = ui.helper.CheckConfigCapability("r_renderer", "gl").length == 0; driverSoftware.Enable = ui.helper.CheckConfigCapability("r_renderer", "sw").length == 0; configViewGL.LoadConfig(); configViewSoftware.LoadConfig(); } } class StartupScreenGraphicsDisplayResolutionEditorDropdownButton: spades::ui::SimpleButton { StartupScreenGraphicsDisplayResolutionEditorDropdownButton(spades::ui::UIManager@ manager) { super(manager); } void Render() { SimpleButton::Render(); Renderer@ renderer = Manager.Renderer; Image@ image = renderer.RegisterImage("Gfx/UI/ScrollArrow.png"); AABB2 bnd = ScreenBounds; Vector2 p = (bnd.min + bnd.max) * 0.5f + Vector2(-8.f, 8.f); renderer.DrawImage(image, AABB2(p.x, p.y, 16.f, -16.f)); } } class StartupScreenGraphicsDisplayResolutionEditor: spades::ui::UIElement { spades::ui::Field@ widthField; spades::ui::Field@ heightField; spades::ui::Button@ dropdownButton; private ConfigItem r_videoWidth("r_videoWidth"); private ConfigItem r_videoHeight("r_videoHeight"); StartupScreenHelper@ helper; StartupScreenGraphicsDisplayResolutionEditor(StartupScreenUI@ ui) { super(ui.manager); @helper = ui.helper; { spades::ui::Field e(manager); AddChild(e); e.Bounds = AABB2(0, 0, 45.f, 24.f); e.DenyNonAscii = true; e.Changed = EventHandler(this.ValueEditHandler); @widthField = e; } { spades::ui::Field e(manager); AddChild(e); e.Bounds = AABB2(53, 0, 45.f, 24.f); e.DenyNonAscii = true; e.Changed = EventHandler(this.ValueEditHandler); @heightField = e; } { StartupScreenGraphicsDisplayResolutionEditorDropdownButton e(manager); AddChild(e); e.Bounds = AABB2(100, 0, 24.f, 24.f); @e.Activated = EventHandler(this.ShowDropdown); @dropdownButton = e; } } void LoadConfig() { widthField.Text = ToString(r_videoWidth.IntValue); heightField.Text = ToString(r_videoHeight.IntValue); } void SaveConfig() { int w = ParseInt(widthField.Text); int h = ParseInt(heightField.Text); if(w < 640 or h < 480 or w > 8192 or h > 8192) return; r_videoWidth.IntValue = w; r_videoHeight.IntValue = h; } private void ValueEditHandler(spades::ui::UIElement@) { SaveConfig(); } private void DropdownHandler(int index) { if(index >= 0) { widthField.Text = ToString(helper.GetVideoModeWidth(index)); heightField.Text = ToString(helper.GetVideoModeHeight(index)); SaveConfig(); } } private void ShowDropdown(spades::ui::UIElement@) { string[] items = {}; int cnt = helper.GetNumVideoModes(); for(int i = 0; i < cnt; i++) { int w = helper.GetVideoModeWidth(i); int h = helper.GetVideoModeHeight(i); string s = ToString(w) + "x" + ToString(h); items.insertLast(s); } spades::ui::ShowDropDownList(this, items, DropDownListHandler(this.DropdownHandler)); } void Render() { Font@ font = this.Font; font.Draw("x", Vector2(45.f, 0.f) + ScreenPosition, 1.f, Vector4(1.f, 1.f, 1.f, 1.f)); UIElement::Render(); } } class StartupScreenGraphicsAntialiasConfig: StartupScreenGenericConfig { private StartupScreenUI@ ui; private ConfigItem@ msaaConfig; private ConfigItem@ fxaaConfig; StartupScreenGraphicsAntialiasConfig(StartupScreenUI@ ui) { @this.ui = ui; @msaaConfig = ConfigItem("r_multisamples"); @fxaaConfig = ConfigItem("r_fxaa"); } string GetValue() { if(fxaaConfig.IntValue != 0) { return "fxaa"; }else{ int v = msaaConfig.IntValue; if(v < 2) return "0"; else return msaaConfig.StringValue; } } void SetValue(string v) { if(v == "fxaa") { msaaConfig.StringValue = "0"; fxaaConfig.StringValue = "1"; } else if (v == "0" || v == "1") { msaaConfig.StringValue = "0"; fxaaConfig.StringValue = "0"; } else { msaaConfig.StringValue = v; fxaaConfig.StringValue = "0"; } } string CheckValueCapability(string v) { if(v == "fxaa") { return ui.helper.CheckConfigCapability("r_multisamples", "0") + ui.helper.CheckConfigCapability("r_fxaa", "1"); } else if (v == "0" || v == "1") { return ui.helper.CheckConfigCapability("r_multisamples", "0") + ui.helper.CheckConfigCapability("r_fxaa", "0"); } else { return ui.helper.CheckConfigCapability("r_multisamples", v) + ui.helper.CheckConfigCapability("r_fxaa", "0"); } } } class StartupScreenAudioTab: spades::ui::UIElement, LabelAddable { StartupScreenUI@ ui; StartupScreenHelper@ helper; spades::ui::RadioButton@ driverOpenAL; spades::ui::RadioButton@ driverYSR; spades::ui::RadioButton@ driverNull; spades::ui::TextViewer@ helpView; StartupScreenConfigView@ configViewOpenAL; StartupScreenConfigView@ configViewYSR; private ConfigItem s_audioDriver("s_audioDriver"); private ConfigItem s_eax("s_eax"); StartupScreenAudioTab(StartupScreenUI@ ui, Vector2 size) { super(ui.manager); @this.ui = ui; @helper = ui.helper; float mainWidth = size.x - 250.f; { spades::ui::TextViewer e(Manager); e.Bounds = AABB2(mainWidth + 10.f, 0.f, size.x - mainWidth - 10.f, size.y); @e.Font = ui.font; e.Text = _Tr("StartupScreen", "Audio Settings"); AddChild(e); @helpView = e; } AddLabel(0.f, 0.f, 24.f, _Tr("StartupScreen", "Backend")); { spades::ui::RadioButton e(Manager); e.Caption = _Tr("StartupScreen", "OpenAL"); e.Bounds = AABB2(100.f, 0.f, 100.f, 24.f); e.GroupName = "driver"; HelpHandler(helpView, _Tr("StartupScreen", "Uses an OpenAL-capable sound card to process sound. " "In most cases where there isn't such a sound card, software emulation is " "used.")).Watch(e); @e.Activated = EventHandler(this.OnDriverOpenAL); AddChild(e); @driverOpenAL = e; } { spades::ui::RadioButton e(Manager); e.Caption = _Tr("StartupScreen", "YSR"); e.Bounds = AABB2(210.f, 0.f, 100.f, 24.f); e.GroupName = "driver"; HelpHandler(helpView, _Tr("StartupScreen", "YSR is an experimental 3D HDR sound engine optimized " "for OpenSpades. It features several enhanced features including " "automatic load control, dynamics compressor, HRTF-based " "3D audio, and high quality reverb.")).Watch(e); @e.Activated = EventHandler(this.OnDriverYSR); AddChild(e); @driverYSR = e; } { spades::ui::RadioButton e(Manager); e.Caption = _Tr("StartupScreen", "Null"); e.Bounds = AABB2(320.f, 0.f, 100.f, 24.f); e.GroupName = "driver"; HelpHandler(helpView, _Tr("StartupScreen", "Disables audio output.")).Watch(e); @e.Activated = EventHandler(this.OnDriverNull); AddChild(e); @driverNull = e; } { StartupScreenConfigView cfg(Manager); cfg.AddRow(StartupScreenConfigSliderItemEditor(ui, StartupScreenConfig(ui, "s_maxPolyphonics"), 16.0, 256.0, 8.0, _Tr("StartupScreen", "Polyphonics"), _Tr("StartupScreen", "Specifies how many sounds can be played simultaneously. " "Higher value needs more processing power, so setting this too high might " "cause an overload (especially with a software emulation)."), ConfigNumberFormatter(0, " poly"))); cfg.AddRow(StartupScreenConfigCheckItemEditor(ui, StartupScreenConfig(ui, "s_eax"), "0", "1", _Tr("StartupScreen", "EAX"), _Tr("StartupScreen", "Enables extended features provided by the OpenAL driver to create " "more ambience."))); cfg.Finalize(); cfg.SetHelpTextHandler(HelpTextHandler(this.HandleHelpText)); cfg.Bounds = AABB2(0.f, 30.f, mainWidth, size.y - 30.f); AddChild(cfg); @configViewOpenAL = cfg; } { StartupScreenConfigView cfg(Manager); cfg.AddRow(StartupScreenConfigSliderItemEditor(ui, StartupScreenConfig(ui, "s_maxPolyphonics"), 16.0, 256.0, 8.0, _Tr("StartupScreen", "Polyphonics"), _Tr("StartupScreen", "Specifies how many sounds can be played simultaneously. " "No matter what value is set, YSR might reduce the number of sounds " "when an overload is detected."), ConfigNumberFormatter(0, " poly"))); cfg.Finalize(); cfg.SetHelpTextHandler(HelpTextHandler(this.HandleHelpText)); cfg.Bounds = AABB2(0.f, 30.f, mainWidth, size.y - 30.f); AddChild(cfg); @configViewYSR = cfg; } } private void HandleHelpText(string text) { helpView.Text = text; } private void OnDriverOpenAL(spades::ui::UIElement@){ s_audioDriver.StringValue = "openal"; LoadConfig(); } private void OnDriverYSR(spades::ui::UIElement@){ s_audioDriver.StringValue = "ysr"; LoadConfig(); } private void OnDriverNull(spades::ui::UIElement@){ s_audioDriver.StringValue = "null"; LoadConfig(); } void LoadConfig() { if(s_audioDriver.StringValue == "ysr") { driverYSR.Check(); configViewOpenAL.Visible = false; configViewYSR.Visible = true; }else if(s_audioDriver.StringValue == "openal"){ driverOpenAL.Check(); configViewOpenAL.Visible = true; configViewYSR.Visible = false; }else if(s_audioDriver.StringValue == "null"){ driverNull.Check(); configViewOpenAL.Visible = false; configViewYSR.Visible = false; } driverOpenAL.Enable = ui.helper.CheckConfigCapability("s_audioDriver", "openal").length == 0; driverYSR.Enable = ui.helper.CheckConfigCapability("s_audioDriver", "ysr").length == 0; driverNull.Enable = ui.helper.CheckConfigCapability("s_audioDriver", "null").length == 0; configViewOpenAL.LoadConfig(); configViewYSR.LoadConfig(); } } class StartupScreenSystemInfoTab: spades::ui::UIElement, LabelAddable { StartupScreenUI@ ui; StartupScreenHelper@ helper; spades::ui::TextViewer@ helpView; StartupScreenSystemInfoTab(StartupScreenUI@ ui, Vector2 size) { super(ui.manager); @this.ui = ui; @helper = ui.helper; { spades::ui::TextViewer e(Manager); e.Bounds = AABB2(0.f, 0.f, size.x, size.y - 30.f); @e.Font = ui.font; AddChild(e); for(int i = 0, count = helper.GetNumReportLines(); i < count; i++) { string text = helper.GetReportLineText(i); Vector4 col = helper.GetReportLineColor(i); e.AddLine(text, true, col); } @helpView = e; } { spades::ui::Button button(Manager); button.Caption = _Tr("StartupScreen", "Copy to Clipboard"); button.Bounds = AABB2(size.x - 180.f, size.y - 30.f, 180.f, 30.f); button.Activated = EventHandler(this.OnCopyReport); AddChild(button); } } private void OnCopyReport(spades::ui::UIElement@){ Manager.Copy(helper.GetReport()); } } class StartupScreenAdvancedTab: spades::ui::UIElement, LabelAddable { StartupScreenUI@ ui; StartupScreenHelper@ helper; spades::ui::Field@ filter; spades::ui::TextViewer@ helpView; StartupScreenConfigView@ configView; StartupScreenAdvancedTab(StartupScreenUI@ ui, Vector2 size) { super(ui.manager); @this.ui = ui; @helper = ui.helper; float mainWidth = size.x - 250.f; { spades::ui::TextViewer e(Manager); e.Bounds = AABB2(mainWidth + 10.f, 0.f, size.x - mainWidth - 10.f, size.y); @e.Font = ui.font; e.Text = _Tr("StartupScreen", "Advanced Settings"); e.Visible = false; AddChild(e); @helpView = e; } { spades::ui::Field e(Manager); e.Placeholder = _Tr("StartupScreen", "Filter"); e.Bounds = AABB2(0.f, 0.f, size.x, 24.f); @e.Changed = EventHandler(this.OnFilterChanged); AddChild(e); @filter = e; } { StartupScreenConfigView cfg(Manager); string[]@ names = GetAllConfigNames(); for(uint i = 0, count = names.length; i < count; i++) { cfg.AddRow(StartupScreenConfigFieldItemEditor(ui, StartupScreenConfig(ui, names[i]), names[i], "")); } cfg.Finalize(); cfg.SetHelpTextHandler(HelpTextHandler(this.HandleHelpText)); cfg.Bounds = AABB2(0.f, 30.f, size.x, size.y - 30.f); AddChild(cfg); @configView = cfg; } } private void HandleHelpText(string text) { helpView.Text = text; } private void OnFilterChanged(spades::ui::UIElement@){ configView.Filter(filter.Text); } void LoadConfig() { configView.LoadConfig(); } } StartupScreenUI@ CreateStartupScreenUI(Renderer@ renderer, AudioDevice@ audioDevice, Font@ font, StartupScreenHelper@ helper) { return StartupScreenUI(renderer, audioDevice, font, helper); } }