diff --git a/Game.UI/BasePanel.cs b/Game.UI/BasePanel.cs index 31b53a2c..c13b5584 100644 --- a/Game.UI/BasePanel.cs +++ b/Game.UI/BasePanel.cs @@ -10,31 +10,6 @@ namespace Game.UI; -public class OptionsTestPage : PropertyPage -{ - public OptionsTestPage(Panel? parent, string? name) : base(parent, name) {} -} - -public class OptionsDialog : PropertyDialog -{ - readonly ModInfo ModInfo = Singleton(); - public OptionsDialog(Panel? parent) : base(parent, "OptionsDialog") { - SetDeleteSelfOnClose(true); - SetBounds(0, 0, 512, 406); - SetSizeable(false); - - SetTitle("#GameUI_Options", true); - // TODO - - AddPage(new OptionsTestPage(this, null), "#GameUI_Video"); - AddPage(new OptionsTestPage(this, null), "#GameUI_Video"); - AddPage(new OptionsTestPage(this, null), "#GameUI_Video"); - - SetApplyButtonVisible(true); - GetPropertySheet().SetTabWidth(84); - } -} - public class GameMenuItem : MenuItem { public GameMenuItem(Menu panel, string name, string text) : base(panel, name, text) { @@ -90,7 +65,7 @@ public enum BackgroundState public class GameMenu(Panel parent, string name) : Menu(parent, name) { protected override void LayoutMenuBorder() { } - public virtual int AddMenuItem(ReadOnlySpan itemName, ReadOnlySpan itemText, ReadOnlySpan command, Panel? target, KeyValues? userData = null) { + public override int AddMenuItem(ReadOnlySpan itemName, ReadOnlySpan itemText, ReadOnlySpan command, Panel? target, KeyValues? userData = null) { MenuItem item = new GameMenuItem(this, new string(itemName), new string(itemText)); item.AddActionSignalTarget(target); item.SetCommand(command); @@ -98,7 +73,7 @@ public virtual int AddMenuItem(ReadOnlySpan itemName, ReadOnlySpan i item.SetUserData(userData); return base.AddMenuItem(item); } - public virtual int AddMenuItem(ReadOnlySpan itemName, ReadOnlySpan itemText, KeyValues command, Panel? target, KeyValues? userData = null) { + public override int AddMenuItem(ReadOnlySpan itemName, ReadOnlySpan itemText, KeyValues command, Panel? target, KeyValues? userData = null) { MenuItem item = new GameMenuItem(this, new string(itemName), new string(itemText)); item.AddActionSignalTarget(target); item.SetCommand(command); diff --git a/Game.UI/CvarNegateCheckButton.cs b/Game.UI/CvarNegateCheckButton.cs new file mode 100644 index 00000000..44998fba --- /dev/null +++ b/Game.UI/CvarNegateCheckButton.cs @@ -0,0 +1,99 @@ +using Source.Common.Commands; +using Source.Common.Formats.Keyvalues; +using Source.Common.GUI; +using Source.GUI.Controls; + +namespace Game.UI; + +public class CvarNegateCheckButton : CheckButton +{ + string? CvarName; + bool StartState; + + public CvarNegateCheckButton(Panel parent, ReadOnlySpan name, ReadOnlySpan text, ReadOnlySpan cvarName) : base(parent, name, text) { + CvarName = cvarName.Length > 0 ? new(cvarName) : null; + Reset(); + AddActionSignalTarget(this); + } + + public override void Paint() { + if (CvarName == null) { + base.Paint(); + return; + } + + ConVarRef var = new(CvarName); + if (!var.IsValid()) + return; + + float value = var.GetFloat(); + + if (value < 0) { + if (!StartState) { + SetSelected(true); + StartState = true; + } + } + else { + if (StartState) { + SetSelected(false); + StartState = false; + } + } + + base.Paint(); + } + + public void Reset() { + ConVarRef var = new(CvarName); + if (!var.IsValid()) + return; + + float value = var.GetFloat(); + + if (value < 0) + StartState = true; + else + StartState = false; + + SetSelected(StartState); + } + + public bool HasBeenModified() => IsSelected() != StartState; + + public override void SetSelected(bool state) { + base.SetSelected(state); + } + + public void ApplyChanges() { + if (CvarName == null || CvarName.Length == 0) + return; + + ConVarRef var = new(CvarName); + float value = var.GetFloat(); + + value = (float)MathF.Abs(value); + if (value < 0.00001) + value = 0.022f; + + StartState = IsSelected(); + value = -value; + + float ans = StartState ? value : -value; + var.SetValue(ans); + } + + public void OnButtonChecked() { + if (HasBeenModified()) + PostActionSignal(new KeyValues("ControlModified")); // todo static + } + + public override void OnMessage(KeyValues message, IPanel? from) { + if (message.Name.Equals("CheckButtonChecked", StringComparison.OrdinalIgnoreCase)) { + OnButtonChecked(); + return; + } + + base.OnMessage(message, from); + } +} diff --git a/Game.UI/CvarSlider.cs b/Game.UI/CvarSlider.cs new file mode 100644 index 00000000..fc3bdea9 --- /dev/null +++ b/Game.UI/CvarSlider.cs @@ -0,0 +1,229 @@ +using Source; +using Source.Common.Commands; +using Source.Common.Formats.Keyvalues; +using Source.Common.GUI; +using Source.GUI.Controls; + +namespace Game.UI; + +public class CvarSlider : Slider +{ + [PanelAnimationVar("use_convar_minmax", "0", "bool")] bool UseCvarMinMax; + bool AllowOutOfRange; + bool ModifiedOnce; + float StartValue; + int iStartValue; + float LastSliderValue; + float CurrentValue; + char[]? CvarName = new char[64]; + bool CreatedInCode; + float MinValue; + float MaxValue; + public CvarSlider(Panel panel, ReadOnlySpan name) : base(panel, name) { + SetupSlider(0, 1, "", false); + CreatedInCode = false; + AddActionSignalTarget(this); + } + + public CvarSlider(Panel parent, ReadOnlySpan name, ReadOnlySpan text, float minValue, float maxValue, ReadOnlySpan cvarName, bool allowOutOfRange) : base(parent, name) { + AddActionSignalTarget(this); + SetupSlider(minValue, maxValue, cvarName, allowOutOfRange); + CreatedInCode = true; + } + + public void SetupSlider(float minValue, float maxValue, ReadOnlySpan cvarName, bool allowOutOfRange) { + ConVarRef var = new(cvarName, true); + + if (var.IsValid()) { + float cvarMin; + if (var.GetMin(out double CVarMin)) { + cvarMin = (float)CVarMin; + minValue = UseCvarMinMax ? cvarMin : Math.Max(minValue, cvarMin); + } + + float cvarMax; + if (var.GetMax(out double CVarMax)) { + cvarMax = (float)CVarMax; + maxValue = UseCvarMinMax ? cvarMax : Math.Min(maxValue, cvarMax); + } + } + + MinValue = minValue; + MaxValue = maxValue; + + SetRange((int)(100.0f * MinValue), (int)(100.0f * MaxValue)); + + Span min = stackalloc char[32]; + Span max = stackalloc char[32]; + + minValue.TryFormat(min, out int minLen, "F2"); + maxValue.TryFormat(max, out int maxLen, "F2"); + + SetTickCaptions(min, max); + + cvarName.CopyTo(CvarName); + + ModifiedOnce = false; + AllowOutOfRange = allowOutOfRange; + + Reset(); + } + + public void SetTickColor(Color color) => TickColor = color; + + public override void ApplySettings(KeyValues resourceData) { + base.ApplySettings(resourceData); + + if (!CreatedInCode) { + float minValue = resourceData.GetFloat("minvalue", 0); + float maxValue = resourceData.GetFloat("maxvalue", 1); + ReadOnlySpan cvarName = resourceData.GetString("cvar_name", ""); + bool allowOutOfRange = resourceData.GetInt("allow_out_of_range", 0) != 0; + SetupSlider(minValue, maxValue, cvarName, allowOutOfRange); + + // HACK: If our parent is a property page, we want the dialog containing it + if (GetParent() is PropertyPage && GetParent()!.GetParent() != null) + GetParent()!.GetParent()!.AddActionSignalTarget(this); + else + GetParent()!.AddActionSignalTarget(this); + } + } + + public override void GetSettings(KeyValues outResourceData) { + base.GetSettings(outResourceData); + + if (!CreatedInCode) { + outResourceData.SetFloat("minvalue", MinValue); + outResourceData.SetFloat("maxvalue", MaxValue); + outResourceData.SetString("cvar_name", CvarName!); + outResourceData.SetInt("allow_out_of_range", AllowOutOfRange ? 1 : 0); + } + } + + public void SetCVarName(ReadOnlySpan cvarName) { + cvarName.CopyTo(CvarName); + ModifiedOnce = false; + Reset(); + } + + public void SetMinMaxValues(float minValue, float maxValue, bool setTickDisplay) { + SetRange((int)(100.0f * minValue), (int)(100.0f * maxValue)); + + if (setTickDisplay) { + Span min = stackalloc char[32]; + Span max = stackalloc char[32]; + + minValue.TryFormat(min, out int minLen, "F2"); + maxValue.TryFormat(max, out int maxLen, "F2"); + + SetTickCaptions(min, max); + } + + Reset(); + } + + public override void Paint() { + ConVarRef var = new(CvarName!, true); + if (!var.IsValid()) + return; + + float curValue = var.GetFloat(); + if (curValue != StartValue) { + int value = (int)(curValue * 100.0f); + StartValue = curValue; + CurrentValue = curValue; + + SetValue(value); + iStartValue = GetValue(); + } + base.Paint(); + } + + public void ApplyChanges() { + if (ModifiedOnce) { + iStartValue = GetValue(); + if (AllowOutOfRange) + StartValue = CurrentValue; + else + StartValue = iStartValue / 100.0f; + + ConVarRef var = new(CvarName!, true); + if (!var.IsValid()) + return; + var.SetValue(StartValue); + } + } + + public float GetSliderValue() { + if (AllowOutOfRange) + return CurrentValue; + else + return GetValue() / 100.0f; + } + + public void SetSliderValue(float value) { + int val = (int)(value * 100.0f); + SetValue(val, false); + + LastSliderValue = value; + + if (CurrentValue != value) { + CurrentValue = value; + ModifiedOnce = true; + } + } + + public void Reset() { + ConVarRef var = new(CvarName!, true); + + if (!var.IsValid()) { + CurrentValue = StartValue = 0.0f; + SetValue(0, false); + iStartValue = GetValue(); + LastSliderValue = iStartValue; + return; + } + + StartValue = var.GetFloat(); + CurrentValue = StartValue; + + int value = (int)(StartValue * 100.0f); + SetValue(value, false); + + iStartValue = GetValue(); + LastSliderValue = iStartValue; + } + + public bool HasBeenModified() { + if (GetValue() != iStartValue) + ModifiedOnce = true; + return ModifiedOnce; + } + + public void OnSliderMoved() { + if (HasBeenModified()) { + if (LastSliderValue != GetValue()) { + LastSliderValue = GetValue(); + CurrentValue = GetValue() / 100.0f; + } + + PostActionSignal(new KeyValues("ControlModified")); + } + } + + // todo complete + + public override void OnMessage(KeyValues message, IPanel? from) { + switch (message.Name) { + case "SliderMoved": + OnSliderMoved(); + break; + case "SliderDragEnd": + // OnSliderDragEnd(); + break; + default: + base.OnMessage(message, from); + break; + } + } +} diff --git a/Game.UI/CvarToggleCheckButton.cs b/Game.UI/CvarToggleCheckButton.cs new file mode 100644 index 00000000..83ab73a3 --- /dev/null +++ b/Game.UI/CvarToggleCheckButton.cs @@ -0,0 +1,93 @@ +using Source.Common.Commands; +using Source.Common.Formats.Keyvalues; +using Source.GUI.Controls; + +namespace Game.UI; + +public class CvarToggleCheckButton : CheckButton +{ + string? CvarName; + bool StartValue; + + public CvarToggleCheckButton(Panel parent, ReadOnlySpan name, ReadOnlySpan text, ReadOnlySpan cvarName) : base(parent, name, text) { + CvarName = cvarName.Length > 0 ? new(cvarName) : null; + if (CvarName != null) + Reset(); + AddActionSignalTarget(this); + } + + public override void Paint() { + if (CvarName == null || CvarName.Length == 0) { + base.Paint(); + return; + } + + ConVarRef var = new(CvarName); + if (!var.IsValid()) + return; + + bool value = var.GetBool(); + + if (value != StartValue) { + SetSelected(value); + StartValue = value; + } + + base.Paint(); + } + + public void ApplyChanges() { + if (CvarName == null || CvarName.Length == 0) + return; + + StartValue = IsSelected(); + ConVarRef var = new(CvarName); + if (!var.IsValid()) + return; + var.SetValue(StartValue); + } + + public void Reset() { + if (CvarName == null || CvarName.Length == 0) + return; + + ConVarRef var = new(CvarName); + if (!var.IsValid()) + return; + + StartValue = var.GetBool(); + SetSelected(StartValue); + } + + public bool HasBeenModified() => IsSelected() != StartValue; + + public void OnButtonChecked() { + if (HasBeenModified()) + PostActionSignal(new KeyValues("ControlModified")); // todo static + } + + public override void ApplySettings(KeyValues resourceData) { + base.ApplySettings(resourceData); + + ReadOnlySpan cvarName = resourceData.GetString("cvar_name", ""); + ReadOnlySpan cvarValue = resourceData.GetString("cvar_value", ""); + + if (cvarName.Equals("", StringComparison.Ordinal)) + return; + + CvarName = cvarName.Length > 0 ? new(cvarName) : null; + + if (cvarValue.Equals("1", StringComparison.Ordinal)) + StartValue = true; + else + StartValue = false; + + ConVarRef var = new(CvarName); + if (var.IsValid()) { + if (var.GetBool()) + SetSelected(true); + else + SetSelected(false); + } + } +} diff --git a/Game.UI/GameUI.cs b/Game.UI/GameUI.cs index 3e1883b8..1d37f722 100644 --- a/Game.UI/GameUI.cs +++ b/Game.UI/GameUI.cs @@ -51,7 +51,7 @@ public void OnGameUIActivated() { } public void OnGameUIHidden() { - if (engine.GetMaxClients() <= 1) + if (engine.GetMaxClients() <= 1) engine.ClientCmd_Unrestricted("unpause"); staticPanel.OnGameUIHidden(); @@ -122,7 +122,7 @@ public void RunFrame() { public void SetMainMenuOverride(IPanel panel) { //BasePanel? basePanel = BasePanel(); //if (basePanel != null) - //basePanel.SetMainMenuOverride(panel); // todo + // basePanel.SetMainMenuOverride(panel); // todo } public bool SetShowProgressText(bool show) { @@ -220,6 +220,9 @@ public bool HasSavedThisMenuSession() { } public bool IsInBackgroundLevel() { + ReadOnlySpan levelName = engine.GetLevelName(); + if (!levelName.IsEmpty && levelName[0] != '\0' && engine.IsLevelMainMenuBackground()) + return true; return false; } diff --git a/Game.UI/LoadingDialog.cs b/Game.UI/LoadingDialog.cs index 95d249d7..c962a51b 100644 --- a/Game.UI/LoadingDialog.cs +++ b/Game.UI/LoadingDialog.cs @@ -1,6 +1,7 @@ using Source; using Source.Common; using Source.Common.Client; +using Source.Common.Formats.Keyvalues; using Source.Common.GameUI; using Source.Common.GUI; using Source.GUI.Controls; @@ -32,8 +33,7 @@ public class LoadingDialog : Frame [PanelAnimationVar("0")] int AdditionalIndentY; public override void PerformLayout() { - if (ConsoleStyle) - { + if (ConsoleStyle) { Surface.GetScreenSize(out int screenWide, out int screenTall); GetSize(out int wide, out int tall); float x, y; @@ -114,8 +114,7 @@ void Init() { SetSizeable(false); SetMoveable(false); - if (ConsoleStyle) - { + if (ConsoleStyle) { Center = false; Progress.SetVisible(false); Progress2.SetVisible(false); @@ -124,7 +123,7 @@ void Init() { TimeRemainingLabel.SetVisible(false); SetMinimumSize(0, 0); - // SetTitleBarVisible(false); + SetTitleBarVisible(false); ProgressFraction = 0; } @@ -250,9 +249,32 @@ internal bool SetProgressPoint(float progress) { return nOldDrawnSegments != nNewDrawSegments; } - internal void SetSecondaryProgress(float progress) - { + internal void SetSecondaryProgress(float progress) { + if (!ConsoleStyle) + return; + + if (!ShowingSecondaryProgress && progress > 0.99f) + return; + + if (!ShowingSecondaryProgress) { + LoadControlSettings("resource/LoadingDialogDualProgress.res"); + ShowingSecondaryProgress = true; + Progress2.SetVisible(true); + SecondaryProgressStartTime = System.GetFrameTime(); + } + + if (progress > SecondaryProgress) { + Progress2.SetProgress(progress); + SecondaryProgress = progress; + LastSecondaryProgressUpdateTime = System.GetFrameTime(); + } + if (progress < SecondaryProgress) { + Progress2.SetProgress(progress); + SecondaryProgress = progress; + LastSecondaryProgressUpdateTime = System.GetFrameTime(); + SecondaryProgressStartTime = System.GetFrameTime(); + } } internal void SetStatusText(ReadOnlySpan statusText) { @@ -272,20 +294,24 @@ internal bool SetShowProgressText(bool show) { return bret; } - public override void OnThink() - { + public override void OnThink() { base.OnThink(); - // if (!ConsoleStyle && ShowingSecondaryProgress) - // { + if (!ConsoleStyle && ShowingSecondaryProgress) { + Span unicode = stackalloc char[512]; + if (SecondaryProgress >= 1.0f) + TimeRemainingLabel.SetText("complete"); + else if (ProgressBar.ConstructTimeRemainingString(unicode, (float)SecondaryProgressStartTime, (float)System.GetFrameTime(), SecondaryProgress, (float)LastSecondaryProgressUpdateTime, true)) + TimeRemainingLabel.SetText(unicode); + else + TimeRemainingLabel.SetText(""); + } - // } + SetAlpha(255); } - public override void PaintBackground() - { - if (!ConsoleStyle) - { + public override void PaintBackground() { + if (!ConsoleStyle) { base.PaintBackground(); return; } @@ -295,8 +321,7 @@ public override void PaintBackground() int x = (panelWide - barWide) / 2; int y = panelTall - barTall; - if (LoadingBackground != null) - { + if (LoadingBackground != null) { IScheme? scheme = SchemeManager.GetScheme("ClientScheme"); Color color = GetSchemeColor("TanDarker", new(255, 255, 255, 255), scheme); diff --git a/Game.UI/OptionsDialog.cs b/Game.UI/OptionsDialog.cs new file mode 100644 index 00000000..d5863e31 --- /dev/null +++ b/Game.UI/OptionsDialog.cs @@ -0,0 +1,35 @@ +using Source.Common; +using Source.Common.Formats.Keyvalues; +using Source.GUI.Controls; + +namespace Game.UI; +public class OptionsDialog : PropertyDialog +{ + readonly ModInfo ModInfo = Singleton(); + public OptionsDialog(Panel? parent) : base(parent, "OptionsDialog") { + SetDeleteSelfOnClose(true); + SetBounds(0, 0, 512, 406); + SetSizeable(false); + + SetTitle("#GameUI_Options", true); + // TODO + + AddPage(new OptionsSubMouse(this, null), "#GameUI_Mouse"); + + SetApplyButtonVisible(true); + GetPropertySheet().SetTabWidth(84); + } + + public override void Activate() { + base.Activate(); + EnableApplyButton(false); + } + + public void OnGameUIHidden() { + for (int i = 0; i < GetChildCount(); i++) { + Panel child = GetChild(i); + if (child != null && child.IsVisible()) + PostMessage(child, new KeyValues("GameUIHidden")); // todo static kv + } + } +} \ No newline at end of file diff --git a/Game.UI/OptionsSubMouse.cs b/Game.UI/OptionsSubMouse.cs new file mode 100644 index 00000000..d86067bb --- /dev/null +++ b/Game.UI/OptionsSubMouse.cs @@ -0,0 +1,117 @@ +using Source.Common.Commands; +using Source.Common.Formats.Keyvalues; +using Source.GUI.Controls; + +namespace Game.UI; + +public class OptionsSubMouse : PropertyPage +{ + CvarNegateCheckButton ReverseMouseCheckBox; + CvarToggleCheckButton MouseFilterCheckBox; + CvarToggleCheckButton MouseRawCheckBox; + CheckButton MouseAccelerationCheckBox; + CvarToggleCheckButton JoystickCheckBox; + CvarToggleCheckButton JoystickSouthpawCheckBox; + CvarToggleCheckButton QuickInfoCheckBox; + CvarToggleCheckButton ReverseJoystickCheckBox; + CvarSlider MouseSensitivitySlider; + TextEntry MouseSensitivityLabel; + CvarSlider MouseAccelExponentSlider; + TextEntry MouseAccelExponentLabel; + CvarSlider JoyYawSensitivitySlider; + Label JoyYawSensitivityPreLabel; + CvarSlider JoyPitchSensitivitySlider; + Label JoyPitchSensitivityPreLabel; + + public OptionsSubMouse(Panel? parent, string? name) : base(parent, name) { + ReverseMouseCheckBox = new(this, "ReverseMouse", "#GameUI_ReverseMouse", "m_pitch"); + MouseFilterCheckBox = new(this, "MouseFilter", "#GameUI_MouseFilter", "m_filter"); + MouseRawCheckBox = new(this, "MouseRaw", "#GameUI_MouseRaw", "m_rawinput"); + MouseAccelerationCheckBox = new(this, "MouseAccelerationCheckbox", "#GameUI_MouseCustomAccel"); + JoystickCheckBox = new(this, "Joystick", "#GameUI_Joystick", "joystick"); + JoystickSouthpawCheckBox = new(this, "JoystickSouthpaw", "#GameUI_JoystickSouthpaw", "joy_movement_stick"); + ReverseJoystickCheckBox = new(this, "ReverseJoystick", "#GameUI_ReverseJoystick", "joy_inverty"); + QuickInfoCheckBox = new(this, "HudQuickInfo", "#GameUI_HudQuickInfo", "hud_quickinfo"); + MouseSensitivitySlider = new(this, "Slider", "#GameUI_MouseSensitivity", 0.1f, 6.0f, "sensitivity", true); + MouseSensitivityLabel = new(this, "SensitivityLabel"); + MouseSensitivityLabel.AddActionSignalTarget(this); + MouseAccelExponentSlider = new(this, "MouseAccelerationSlider", "#GameUI_MouseAcceleration", 1.0f, 1.4f, "m_customaccel_exponent", true); + MouseAccelExponentLabel = new(this, "MouseAccelerationLabel"); + MouseAccelExponentLabel.AddActionSignalTarget(this); + JoyYawSensitivitySlider = new(this, "JoystickYawSlider", "#GameUI_JoystickYawSensitivity", -0.5f, -7.0f, "joy_yawsensitivity", true); + JoyYawSensitivityPreLabel = new(this, "JoystickYawSensitivityPreLabel", "#GameUI_JoystickLookSpeedYaw"); + JoyPitchSensitivitySlider = new(this, "JoystickPitchSlider", "#GameUI_JoystickPitchSensitivity", 0.5f, 7.0f, "joy_pitchsensitivity", true); + JoyPitchSensitivityPreLabel = new(this, "JoystickPitchSensitivityPreLabel", "#GameUI_JoystickLookSpeedPitch"); + + LoadControlSettings("resource/OptionsSubMouse.res"); + } + + public override void OnResetData() { + ReverseMouseCheckBox.Reset(); + MouseFilterCheckBox.Reset(); + MouseRawCheckBox.Reset(); + JoystickCheckBox.Reset(); + JoystickSouthpawCheckBox.Reset(); + ReverseJoystickCheckBox.Reset(); + QuickInfoCheckBox.Reset(); + MouseSensitivitySlider.Reset(); + MouseAccelExponentSlider.Reset(); + JoyYawSensitivitySlider.Reset(); + JoyPitchSensitivitySlider.Reset(); + + ConVarRef var = new("m_customaccel"); + if (var.IsValid()) + MouseAccelerationCheckBox.SetSelected(var.GetBool()); + } + + public override void OnApplyChanges() { + ReverseMouseCheckBox.ApplyChanges(); + MouseFilterCheckBox.ApplyChanges(); + MouseRawCheckBox.ApplyChanges(); + JoystickCheckBox.ApplyChanges(); + JoystickSouthpawCheckBox.ApplyChanges(); + ReverseJoystickCheckBox.ApplyChanges(); + QuickInfoCheckBox.ApplyChanges(); + MouseSensitivitySlider.ApplyChanges(); + MouseAccelExponentSlider.ApplyChanges(); + JoyYawSensitivitySlider.ApplyChanges(); + JoyPitchSensitivitySlider.ApplyChanges(); + + // engine.ClientCmd_Unrestricted("jpyadvancedupdate"); + + ConVarRef var = new("m_customaccel"); + if (var.IsValid()) + var.SetValue(MouseAccelerationCheckBox.IsSelected() ? 3 : 0); + } + + public void OnControlModified(Panel panel) { + PostActionSignal(new KeyValues("ApplyButtonEnable")); + + if (panel == MouseSensitivitySlider && MouseAccelExponentSlider.HasBeenModified()) + UpdateSensitivityLabel(); + else if (panel == MouseAccelExponentSlider && MouseAccelExponentSlider.HasBeenModified()) + UpdateAccelerationLabel(); + // else if (panel == JoystickCheckBox) + // UpdateJoystickPanels(); + else if (panel == MouseAccelerationCheckBox) { + MouseAccelExponentSlider.SetEnabled(MouseAccelerationCheckBox.IsSelected()); + MouseAccelExponentLabel.SetEnabled(MouseAccelerationCheckBox.IsSelected()); + } + } + + private void UpdateSensitivityLabel() { + Span buf = stackalloc char[64]; + string formatted = $" {MouseSensitivitySlider.GetSliderValue():F2}"; + formatted.AsSpan().CopyTo(buf); + MouseSensitivityLabel.SetText(buf); + } + + public void UpdateAccelerationLabel() { + Span buf = stackalloc char[64]; + string formatted = $" {MouseAccelExponentSlider.GetSliderValue():F2}"; + formatted.AsSpan().CopyTo(buf); + MouseAccelExponentLabel.SetText(buf); + } + + //todo +} \ No newline at end of file diff --git a/Source.Common/Commands/ConVar.cs b/Source.Common/Commands/ConVar.cs index 395737eb..f3a8f8e3 100644 --- a/Source.Common/Commands/ConVar.cs +++ b/Source.Common/Commands/ConVar.cs @@ -61,6 +61,8 @@ public void Init(ReadOnlySpan name, bool ignoreMissing = false) { [MethodImpl(MethodImplOptions.AggressiveInlining)] public readonly void SetValue(double value) => ConVar.SetValue(value); [MethodImpl(MethodImplOptions.AggressiveInlining)] public readonly void SetValue(bool value) => ConVar.SetValue(value ? 1 : 0); [MethodImpl(MethodImplOptions.AggressiveInlining)] public readonly ReadOnlySpan GetDefault() => ConVar.GetDefault(); + [MethodImpl(MethodImplOptions.AggressiveInlining)] public readonly bool GetMin(out double min) => ConVar.GetMin(out min); + [MethodImpl(MethodImplOptions.AggressiveInlining)] public readonly bool GetMax(out double max) => ConVar.GetMax(out max); } public class ConVar : ConCommandBase, IConVar @@ -353,11 +355,11 @@ private static void PrintFlags(ConCommandBase var) { Dbg.ConMsg("\n"); } - private bool GetMin(out double min) { + public bool GetMin(out double min) { min = this.minVal; return this.hasMin; } - private bool GetMax(out double max) { + public bool GetMax(out double max) { max = this.maxVal; return this.hasMax; } diff --git a/Source.Common/GUI/IImage.cs b/Source.Common/GUI/IImage.cs index fbc712b0..81f1cae7 100644 --- a/Source.Common/GUI/IImage.cs +++ b/Source.Common/GUI/IImage.cs @@ -1,10 +1,19 @@ namespace Source.Common.GUI; -public interface IImage { +public interface IImage +{ void Paint(); void SetPos(int x, int y); void GetContentSize(out int wide, out int tall); void GetSize(out int wide, out int tall); void SetSize(int wide, int tall); void SetColor(Color color); +} + +public enum IImageRotation +{ + Unrotated = 0, + Clockwise_90, + Anticlockwise_90, + Flipped } \ No newline at end of file diff --git a/Source.GUI.Controls/BuildGroup.cs b/Source.GUI.Controls/BuildGroup.cs index 065e74c0..dd71e855 100644 --- a/Source.GUI.Controls/BuildGroup.cs +++ b/Source.GUI.Controls/BuildGroup.cs @@ -83,7 +83,7 @@ internal void LoadControlSettings(ReadOnlySpan controlResourceName, ReadOn if (conditions != null && conditions.GetFirstSubKey() != null) { if (usingPrecachedSourceKeys) { - dat = dat.MakeCopy(); + dat = dat!.MakeCopy(); deleteKeys = true; } diff --git a/Source.GUI.Controls/Button.cs b/Source.GUI.Controls/Button.cs index 433138cd..c04ee234 100644 --- a/Source.GUI.Controls/Button.cs +++ b/Source.GUI.Controls/Button.cs @@ -40,12 +40,20 @@ public class Button : Label string? DepressedSoundName; string? ReleasedSoundName; - readonly public ISystem System = Singleton(); - public override void OnMessage(KeyValues message, IPanel? from) { switch (message.Name) { - case "Hotkey": DoClick(); return; - default: base.OnMessage(message, from); return; + case "PressButton": + DoClick(); + return; + case "Hotkey": + DoClick(); + return; + case "SetState": + OnSetState(message.GetInt("state", 0)); + return; + default: + base.OnMessage(message, from); + return; } } public void Init() { diff --git a/Source.GUI.Controls/CheckButton.cs b/Source.GUI.Controls/CheckButton.cs index 0629795c..a649a3fd 100644 --- a/Source.GUI.Controls/CheckButton.cs +++ b/Source.GUI.Controls/CheckButton.cs @@ -53,15 +53,15 @@ public class CheckButton : ToggleButton Color DisabledBgColor; Color HighlightFgColor; - public CheckButton(Panel parent, string name, string text) : base(parent, name, text) { + public CheckButton(Panel parent, ReadOnlySpan name, ReadOnlySpan text) : base(parent, name, text) { SetContentAlignment(Alignment.West); CheckButtonCheckable = true; UseSmallCheckImage = false; CheckBoxImage = new(this); - // SetTextImageIndex(1); - // SetImageAtIndex(0, CheckBoxImage, CHECK_INSET); + SetTextImageIndex(1); + SetImageAtIndex(0, CheckBoxImage, CHECK_INSET); SelectedFgColor = new(196, 181, 80, 255); SelectedBgColor = new(130, 130, 130, 255); @@ -97,7 +97,7 @@ public override void ApplySchemeSettings(IScheme scheme) { CheckBoxImage.SetFont(scheme.GetFont(UseSmallCheckImage ? "MarlettSmall" : "Marlett", IsProportional())); CheckBoxImage.ResizeImageToContent(); - // SetImageAtIndex(0, CheckBoxImage, CHECK_INSET); + SetImageAtIndex(0, CheckBoxImage, CHECK_INSET); SetPaintBackgroundEnabled(false); } @@ -106,7 +106,7 @@ public override void ApplySchemeSettings(IScheme scheme) { public override void SetSelected(bool state) { if (CheckButtonCheckable) { - KeyValues msg = new("CheckButtonCheckec", "sate", state ? 1 : 0); + KeyValues msg = new("CheckButtonChecked", "state", state ? 1 : 0); PostActionSignal(msg); base.SetSelected(state); @@ -137,4 +137,13 @@ public void SetHighlightColor(Color color) { InvalidateLayout(false); } } + + public override void OnMessage(KeyValues message, IPanel? from) { + if (message.Name == "CheckButtonChecked") { + OnCheckButtonChecked((Panel)from!); + return; + } + + base.OnMessage(message, from); + } } diff --git a/Source.GUI.Controls/ComboBox.cs b/Source.GUI.Controls/ComboBox.cs index 7d38b372..9b0d4b56 100644 --- a/Source.GUI.Controls/ComboBox.cs +++ b/Source.GUI.Controls/ComboBox.cs @@ -8,13 +8,27 @@ class ComboBoxButton : Button { Color DisabledBgColor; - public ComboBoxButton(Panel parent, string name, string text) : base(parent, name, text) { + public ComboBoxButton(Panel parent, ReadOnlySpan name, ReadOnlySpan text) : base(parent, name, text) { SetButtonActivationType(ActivationType.OnPressed); } public override void ApplySchemeSettings(IScheme scheme) { base.ApplySchemeSettings(scheme); - // todo + + SetFont(scheme.GetFont("Marlett", IsProportional())); + SetContentAlignment(Alignment.West); + +#if OSX + SetTextInset(-3, 0); +#else + SetTextInset(3, 0); +#endif + SetDefaultBorder(scheme.GetBorder("ScrollBarButtonBorder")); + + SetDefaultColor(GetSchemeColor("ComboBoxButton.ArrowColor", scheme), GetSchemeColor("ComboBoxButton.BgColor", scheme)); + SetArmedColor(GetSchemeColor("ComboBoxButton.ArmedArrowColor", scheme), GetSchemeColor("ComboBoxButton.BgColor", scheme)); + SetDepressedColor(GetSchemeColor("ComboBoxButton.ArmedArrowColor", scheme), GetSchemeColor("ComboBoxButton.BgColor", scheme)); + DisabledBgColor = GetSchemeColor("ComboBoxButton.DisabledBgColor", scheme); } public override IBorder GetBorder(/*bool depressed, bool armed, bool selected, bool keyfocus*/) { @@ -24,20 +38,45 @@ public override IBorder GetBorder(/*bool depressed, bool armed, bool selected, b public override void OnCursorExited() { CallParentFunction(new KeyValues("CursorExited")); // todo: static kv } + + public override Color GetButtonFgColor() { + if (IsEnabled()) + return base.GetButtonFgColor(); + return DisabledBgColor; + } } public class ComboBox : TextEntry { + public static Panel Create_ComboBox() => new ComboBox(null, null, 5, true); + Menu DropDown; ComboBoxButton Button; bool PreventTextChangeMessage; bool Highlight; MenuDirection Direction; int OpenOffsetY; - char[] BorderOverride; + char[] BorderOverride = new char[64]; public ComboBox(Panel parent, string name, int numLines, bool allowEdit) : base(parent, name) { + SetEditable(allowEdit); + // SetHorizontalScrolling(false); + DropDown = new Menu(this, null); + DropDown.AddActionSignalTarget(this); + DropDown.SetTypeAheadMode(MenuTypeAheadMode.TYPE_AHEAD_MODE); + + Button = new ComboBoxButton(this, "Button", "u"); + Button.SetCommand("ButtonClicked"); + Button.AddActionSignalTarget(this); + + // SetNumberOfEditLines(numLines); + + Highlight = false; + Direction = MenuDirection.DOWN; + OpenOffsetY = 0; + PreventTextChangeMessage = false; + BorderOverride[0] = '\0'; } ~ComboBox() { @@ -92,7 +131,28 @@ public void SetMenu(Menu menu) { } public override void PerformLayout() { + GetPaintSize(out int wide, out int tall); + base.PerformLayout(); + + IFont buttonFont = Button.GetFont()!; + + int fontTall = Surface.GetFontTall(buttonFont); + int buttonSize = Math.Min(tall, fontTall); + int buttonY = (tall - 1 - buttonSize) / 2; + Button.GetContentSize(out int buttonWide, out int buttonTall); + buttonWide = Math.Max(buttonSize, buttonWide); + + Button.SetBounds(wide - buttonWide, buttonY, buttonWide, buttonSize); + + if (IsEditable()) + SetCursor(CursorCode.IBeam); + else + SetCursor(CursorCode.Arrow); + + Button.SetEnabled(IsEnabled()); + + DoMenuLayout(); } public void DoMenuLayout() { @@ -112,11 +172,20 @@ public void SortItems() { } public bool IsDropdownVisible() => DropDown.IsVisible(); public override void ApplySchemeSettings(IScheme scheme) { - + base.ApplySchemeSettings(scheme); + SetBorder(scheme.GetBorder(BorderOverride.Length > 0 ? BorderOverride : "ComboBoxBorder")); } public override void ApplySettings(KeyValues resourceData) { + base.ApplySettings(resourceData); + ReadOnlySpan border = resourceData.GetString("border_override", ""); + if (border.Length > 0) + border.CopyTo(BorderOverride); + + KeyValues KVButton = resourceData.FindKey("Button")!; + if (KVButton != null && Button != null) + Button.ApplySettings(KVButton); } public void SetDropdownButtonVisible(bool state) => @@ -197,9 +266,9 @@ public void OnMenuItemSelected() { public override void OnSizeChanged(int newWide, int newTall) { base.OnSizeChanged(newWide, newTall); - PerformLayout(); - Button.GetSize(out int bwide, out _); - SetDrawWidth(newWide - bwide); + //PerformLayout(); + //Button.GetSize(out int bwide, out _); + //SetDrawWidth(newWide - bwide); } public override void OnSetFocus() { @@ -253,4 +322,23 @@ public override void SetUseFallbackFont(bool state, IFont Fallback) { base.SetUseFallbackFont(state, Fallback); DropDown.SetUseFallbackFont(state, Fallback); } + + public override void OnMessage(KeyValues message, IPanel? from) { + switch (message.Name) { + case "MenuItemSelected": + OnMenuItemSelected(); + break; + case "SetText": + OnSetText(message.GetString("text", "")); + break; + case "MenuClosed": + OnMenuClose(); + break; + case "ActiveItem": + ActivateItem(message.GetInt("itemID", -1)); + break; + } + + base.OnMessage(message, from); + } } diff --git a/Source.GUI.Controls/ConsoleDialog.cs b/Source.GUI.Controls/ConsoleDialog.cs index 7772a3b5..ada38e0f 100644 --- a/Source.GUI.Controls/ConsoleDialog.cs +++ b/Source.GUI.Controls/ConsoleDialog.cs @@ -239,19 +239,19 @@ private void AddToHistory(ReadOnlySpan commandText, ReadOnlySpan ext } HistoryItem item; - for (int i = 0; i < CommandHistory.Count; i++) { + for (int i = CommandHistory.Count - 1; i >= 0; i--) { item = CommandHistory[i]; if (item == null) continue; - if (MemoryExtensions.Equals(command, item.GetText().AsSpan(), StringComparison.OrdinalIgnoreCase)) + if (!MemoryExtensions.Equals(command, item.GetText().AsSpan(), StringComparison.OrdinalIgnoreCase)) continue; if (!extra.IsEmpty || item.GetExtra() != null) { if (extra.IsEmpty || item.GetExtra() == null) continue; - if (MemoryExtensions.Equals(extra, item.GetExtra().AsSpan(), StringComparison.OrdinalIgnoreCase)) + if (!MemoryExtensions.Equals(extra, item.GetExtra().AsSpan(), StringComparison.OrdinalIgnoreCase)) continue; } @@ -274,7 +274,7 @@ private void ClearCompletionList() { Span command = stackalloc char[256]; strcpy(command, text); - ConCommand cmd = Cvar.FindCommand(command)!; + ConCommand? cmd = Cvar.FindCommand(command); if (cmd == null) return null; @@ -664,6 +664,9 @@ public override void OnMessage(KeyValues message, IPanel? from) { case "CommandSubmitted": OnCommandSubmitted(message.GetString("command")); return; + case "Activate": + Activate(); + return; } base.OnMessage(message, from); diff --git a/Source.GUI.Controls/EditablePanel.cs b/Source.GUI.Controls/EditablePanel.cs index 9a738707..9ac07700 100644 --- a/Source.GUI.Controls/EditablePanel.cs +++ b/Source.GUI.Controls/EditablePanel.cs @@ -45,7 +45,7 @@ public override void ApplySettings(KeyValues resourceData) { BuildGroup.ApplySettings(resourceData); } - public void ActivateBuildMode() { + public virtual void ActivateBuildMode() { BuildGroup.SetEnabled(true); } diff --git a/Source.GUI.Controls/Frame.cs b/Source.GUI.Controls/Frame.cs index 963fc7e4..586419fc 100644 --- a/Source.GUI.Controls/Frame.cs +++ b/Source.GUI.Controls/Frame.cs @@ -537,7 +537,7 @@ public Frame(Panel? parent, string? name, bool showTaskbarIcon = true, bool popu Span str = [(char)0x30, '\0']; MinimizeToSysTrayButton = new FrameButton(this, "frame_mintosystray", str); MinimizeToSysTrayButton.SetCommand("MinimizeToSysTray"); - // SetMinimizeToSysTrayButtonVisible(false); + SetMinimizeToSysTrayButtonVisible(false); CloseButton = new FrameButton(this, "frame_close", "r"); CloseButton.AddActionSignalTarget(this); @@ -589,6 +589,7 @@ private void SetupResizeCursors() { public void SetMenuButtonResponsive(bool state) => MenuButton?.SetResponsive(state); public void SetMinimizeButtonVisible(bool state) => MinimizeButton?.SetVisible(state); public void SetMaximizeButtonVisible(bool state) => MaximizeButton?.SetVisible(state); + public void SetMinimizeToSysTrayButtonVisible(bool state) => MinimizeToSysTrayButton?.SetVisible(state); public void SetCloseButtonVisible(bool state) => CloseButton?.SetVisible(state); public override void ApplySchemeSettings(IScheme scheme) { @@ -831,7 +832,7 @@ public override void ApplySettings(KeyValues resourceData) { } } - private void SetTitleBarVisible(bool state) { + public void SetTitleBarVisible(bool state) { DrawTitleBar = state; SetMenuButtonVisible(state); SetMinimizeButtonVisible(state); diff --git a/Source.GUI.Controls/ImagePanel.cs b/Source.GUI.Controls/ImagePanel.cs index edce173e..ed703889 100644 --- a/Source.GUI.Controls/ImagePanel.cs +++ b/Source.GUI.Controls/ImagePanel.cs @@ -1,8 +1,101 @@ -namespace Source.GUI.Controls; +using Source.Common.Formats.Keyvalues; +using Source.Common.GUI; + +namespace Source.GUI.Controls; public class ImagePanel : Panel { - public ImagePanel(Panel? parent, ReadOnlySpan name) : base(parent, name) { - + IImage? Image; + char[]? ImageName; + char[]? FillColorName; + char[]? DrawColorName; + bool PositionImage; + bool CenterImage; + bool ScaleImage; + bool TileImage; + bool TileHorizontally; + bool TileVertically; + float ScaleAmount; + Color FillColor; + Color DrawColor; + IImageRotation Rotation; + public ImagePanel(Panel? parent, ReadOnlySpan name) : base(parent, name) { + Image = null; + ImageName = null; + FillColorName = null; + DrawColorName = null; + PositionImage = false; + CenterImage = false; + ScaleImage = false; + TileImage = false; + TileHorizontally = false; + TileVertically = false; + ScaleAmount = 0.0f; + FillColor = new(0, 0, 0, 0); + DrawColor = new(255, 255, 255, 255); + Rotation = IImageRotation.Unrotated; + + SetImage(Image); + } + + public void SetImage(IImage? image) { + Image = image; + Repaint(); + } + + public void SetImage(ReadOnlySpan imageName) { + if (!imageName.IsEmpty && MemoryExtensions.Equals(imageName, ImageName, StringComparison.Ordinal)) + return; + + int len = imageName.Length; + ImageName = new char[len]; + imageName.CopyTo(ImageName); + InvalidateLayout(false, true); + } + + public IImage? GetImage() => Image; + public Color GetDrawColor() => DrawColor; + public void SetDrawColor(Color drawColor) => DrawColor = drawColor; + + public override void PaintBackground() { + + } + + public override void GetSettings(KeyValues outResourceData) { + base.GetSettings(outResourceData); + // + } + + public override void ApplySettings(KeyValues resourceData) { + base.ApplySettings(resourceData); + // + } + + public override void ApplySchemeSettings(IScheme scheme) { + base.ApplySchemeSettings(scheme); + // + } + + public void SetShouldScaleImage(bool scale) => ScaleImage = scale; + public bool GetShouldScaleImage() => ScaleImage; + + public void SetScaleAmount(float scale) => ScaleAmount = scale; + public float GetScaleAmount() => ScaleAmount; + + public void SetFillColor(Color color) => FillColor = color; + public Color GetFillColor() => FillColor; + + public char[]? GetImageName() => ImageName; + + public bool EvictImage() { + return false; // todo + } + + public int GetNumFrames() { + return 0; // todo + } + + public void SetFrame(int frame) { + } } diff --git a/Source.GUI.Controls/Label.cs b/Source.GUI.Controls/Label.cs index 8c4cf645..fe2bc5b1 100644 --- a/Source.GUI.Controls/Label.cs +++ b/Source.GUI.Controls/Label.cs @@ -29,7 +29,7 @@ public Label(Panel? parent, ReadOnlySpan panelName, ReadOnlySpan tex TextImageIndex = AddImage(TextImage, 0); } - public nint AddImage(TextImage textImage, int offset) { + public nint AddImage(TextImage? textImage, int offset) { nint newImage = Images.Count; Images.Add(new() { Image = textImage, @@ -547,7 +547,55 @@ private void ComputeAlignment(out int tx0, out int ty0, out int tx1, out int ty1 ty1 = ty0 + tTall; } - internal TextImage GetTextImage() { + internal TextImage? GetTextImage() { return TextImage; } + + public void ResetToSimpleTextImage() { + ClearImages(); + TextImageIndex = AddImage(TextImage!, 0); + } + + public void SetImageAtIndex(int index, IImage? image, int offset) { + EnsureImageCapacity(index); + + if(Images[index].Image != image || Images[index].Offset != offset) { + var info = Images[index]; + info.Image = image; + info.Offset = (short)offset; + Images[index] = info; + InvalidateLayout(); + } + } + + public int GetImageCount() => Images.Count; + + public nint SetTextImageIndex(int newIndex) { + if (newIndex == TextImageIndex) + return TextImageIndex; + + EnsureImageCapacity(newIndex); + + nint oldIndex = TextImageIndex; + + var info = Images[newIndex]; + if (TextImageIndex != 0) info.Image = null; + if (newIndex > -1) info.Image = TextImage; + Images[newIndex] = info; + + TextImageIndex = newIndex; + return oldIndex; + } + + public void EnsureImageCapacity(int maxIndex) { + while (Images.Count <= maxIndex) + AddImage(null, 0); + } + + public void SetImageBounds(nint index, int xPos, int width) { + var info = Images[(int)index]; + info.XPos = (short)xPos; + info.Width = (short)width; + Images[(int)index] = info; + } } diff --git a/Source.GUI.Controls/Menu.cs b/Source.GUI.Controls/Menu.cs index 9206144e..161f22bb 100644 --- a/Source.GUI.Controls/Menu.cs +++ b/Source.GUI.Controls/Menu.cs @@ -1464,6 +1464,12 @@ public override void OnMessage(KeyValues message, IPanel? from) { switch (message.Name) { + case "MenuItemSelected": + OnMenuItemSelected((Panel)message.GetPtr("panel")!); + break; + case "ScrollBarSliderMoved": + OnSliderMoved(); + break; case "CursorEnteredMenuItem": OnCursorEnteredMenuItem((MenuItem)message.GetPtr("Panel")!); break; diff --git a/Source.GUI.Controls/MenuButton.cs b/Source.GUI.Controls/MenuButton.cs index 5e74bb8f..46960e51 100644 --- a/Source.GUI.Controls/MenuButton.cs +++ b/Source.GUI.Controls/MenuButton.cs @@ -12,8 +12,8 @@ public class MenuButton : Button MenuDirection Direction; int OpenOffsetY; bool DropMenuButtonStyle; - TextImage DropMenuImage; - int ImageIndex; + TextImage? DropMenuImage; + nint ImageIndex; public MenuButton(Panel parent, string name, string text) : base(parent, name, text) { Menu = null; @@ -21,9 +21,9 @@ public MenuButton(Panel parent, string name, string text) : base(parent, name, t DropMenuImage = null; ImageIndex = -1; OpenOffsetY = 0; - DropMenuButtonStyle = false;// true when DropMenuImage can work + DropMenuButtonStyle = true; - // SetDropMenuButtonStyle(false); + SetDropMenuButtonStyle(false); SetUseCaptureMouse(true); SetButtonActivationType(ActivationType.OnPressed); } @@ -46,7 +46,7 @@ public void HideMenu() { return; Menu.SetVisible(false); - base.ForceDepressed(false); + ForceDepressed(false); Repaint(); OnHideMenu(Menu); } @@ -62,9 +62,10 @@ public override void OnKillFocus(Panel? newPanel) { base.OnKillFocus(newPanel); } + static readonly KeyValues KV_MenuClosed = new("MenuClosed"); public void OnMenuClose() { HideMenu(); - PostActionSignal(new KeyValues("MenuClosed")); // static kv + PostActionSignal(KV_MenuClosed); } public void SetOpenOffsetY(int offset) => OpenOffsetY = offset; @@ -76,7 +77,7 @@ public override void DoClick() { Input.GetCursorPos(out int mx, out int my); ScreenToLocal(ref mx, ref my); - DropMenuImage.GetContentSize(out int contentW, out int contentH); + DropMenuImage.GetContentSize(out int contentW, out _); int drawX = GetWide() - contentW - 2; if (mx > drawX && OnCheckMenuItemCount() == 0) { base.DoClick(); @@ -99,7 +100,7 @@ public override void DoClick() { // Menu.PositionRelativeToPanel(this, Direction, OpenOffsetY); MoveToFront(); OnShowMenu(Menu); - base.ForceDepressed(true); + ForceDepressed(true); Menu.SetVisible(true); Menu.RequestFocus(); } @@ -134,20 +135,22 @@ public void SetDropMenuButtonStyle(bool state) { return; if (state) { - // DropMenuImage = new TextImage("u"); - // IScheme? scheme = GetScheme(); - // DropMenuImage.SetFont(scheme!.GetFont("Marlett", IsProportional())); - // ImageIndex = AddImage(DropMenuImage); + DropMenuImage = new TextImage("u"); + IScheme? scheme = GetScheme(); + DropMenuImage.SetFont(scheme!.GetFont("Marlett", IsProportional())); + ImageIndex = AddImage(DropMenuImage, 0); } else { - // + ResetToSimpleTextImage(); + DropMenuImage = null; + ImageIndex = -1; } } public override void ApplySchemeSettings(IScheme scheme) { base.ApplySchemeSettings(scheme); - // if (DropMenuImage != null) - // SetImageAtIndex(1, DropMenuImage, 0); + if (DropMenuImage != null) + SetImageAtIndex(1, DropMenuImage, 0); } public override void PerformLayout() { @@ -164,9 +167,9 @@ public override void PerformLayout() { GetSize(out int w, out int h); DropMenuImage.ResizeImageToContent(); - DropMenuImage.GetContentSize(out int contentW, out int contentH); + DropMenuImage.GetContentSize(out int contentW, out _); - // SetImageBounds(ImageIndex, w - contentW - 2, contentW); + SetImageBounds(ImageIndex, w - contentW - 2, contentW); } public bool IsDropMenuButtonStyle() => DropMenuButtonStyle; @@ -177,7 +180,7 @@ public override void Paint() { if (!IsDropMenuButtonStyle()) return; - DropMenuImage.GetContentSize(out int contentW, out int contentH); + DropMenuImage!.GetContentSize(out int contentW, out int contentH); DropMenuImage.SetColor(IsEnabled() ? GetButtonFgColor() : GetDisabledFgColor1()); int drawX = GetWide() - contentW - 2; @@ -192,7 +195,7 @@ public override void OnCursorMoved(int x, int y) { if (!IsDropMenuButtonStyle()) return; - DropMenuImage.GetContentSize(out int contentW, out int contentH); + DropMenuImage!.GetContentSize(out int contentW, out int contentH); int drawX = GetWide() - contentW - 2; if (x <= drawX || OnCheckMenuItemCount() != 0) { SetButtonActivationType(ActivationType.OnPressedAndReleased); @@ -207,4 +210,13 @@ public Menu GetMenu() { Assert(Menu != null); return Menu; } + + public override void OnMessage(KeyValues message, IPanel? from) { + if (message.Name == "MenuClosed") { + OnMenuClose(); + return; + } + + base.OnMessage(message, from); + } } diff --git a/Source.GUI.Controls/MenuItem.cs b/Source.GUI.Controls/MenuItem.cs index db9ef36f..cbe5b44b 100644 --- a/Source.GUI.Controls/MenuItem.cs +++ b/Source.GUI.Controls/MenuItem.cs @@ -75,9 +75,9 @@ public MenuItem(Menu parent, ReadOnlySpan panelName, ReadOnlySpan te CascadeMenu.AddActionSignalTarget(this); } else if (Checkable) { - // SetTextImageIndex(1); + SetTextImageIndex(1); Check = new MenuItemCheckImage(this); - // SetImageAtIndex(0, Check, 6); + SetImageAtIndex(0, Check, 6); SetChecked(false); } @@ -223,7 +223,7 @@ public void GetCheckImageSize(out int wide, out int tall) { return CascadeMenu; } - public IBorder? GetBorder(bool depressed, bool armed, bool selected, bool keyfocus) { + public override IBorder? GetBorder(bool depressed, bool armed, bool selected, bool keyfocus) { return null; } @@ -244,7 +244,7 @@ public void SetChecked(bool state) { Checked = state; } - public bool CanBeDefaultButton() { + public override bool CanBeDefaultButton() { return false; } @@ -320,6 +320,15 @@ public override void GetContentSize(out int wide, out int tall) { public override void OnMessage(KeyValues message, IPanel? from) { switch (message.Name) { + case "MenuClosed": + OnKillFocus(newPanel: null); + break; + case "ArmItem": + ArmItem(); + break; + case "DisarmItem": + DisarmItem(); + break; case "KeyModeSet": OnKeyModeSet(); break; diff --git a/Source.GUI.Controls/ProgressBar.cs b/Source.GUI.Controls/ProgressBar.cs index 474e324b..2265ebc8 100644 --- a/Source.GUI.Controls/ProgressBar.cs +++ b/Source.GUI.Controls/ProgressBar.cs @@ -60,6 +60,45 @@ public override void ApplySchemeSettings(IScheme scheme) { SetBorder(scheme.GetBorder("ButtonDepressedBorder")); } + public static bool ConstructTimeRemainingString(Span output, float startTime, float currentTime, float currentProgress, float lastProgressUpdateTime, bool addRemainingSuffix) { + Assert(lastProgressUpdateTime <= currentTime); + + output[0] = '\0'; + + float timeElapsed = lastProgressUpdateTime - startTime; + float totalTime = timeElapsed / currentProgress; + + int secondsRemaining = (int)(totalTime - timeElapsed); + if (lastProgressUpdateTime < currentTime) { + float progressRate = currentProgress / timeElapsed; + float extrapolatedProgress = progressRate * (currentTime - lastProgressUpdateTime); + float extrapolatedTotalTimee = (currentTime - startTime) / extrapolatedProgress; + secondsRemaining = (int)(extrapolatedTotalTimee - timeElapsed); + } + + if (secondsRemaining == 0 && (totalTime - timeElapsed) > 0) + secondsRemaining = 1; + + int minutesRemaining = 0; + while (secondsRemaining >= 60) { + minutesRemaining++; + secondsRemaining -= 60; + } + + char[] minutesBuf = new char[16]; + char[] secondsBuf = new char[16]; + minutesRemaining.TryFormat(minutesBuf, out int minutesLen); + secondsRemaining.TryFormat(secondsBuf, out int secondsLen); + + if (minutesRemaining > 0) { + Span unicodeMinutes = stackalloc char[16]; + Span unicodeSeconds = stackalloc char[16]; + + } + + return false; // todo finish + } + public int GetBarInset() => BarInset; public void SetBarInset(int pixels) => BarInset = pixels; public int GetMargin() => BarMargin; diff --git a/Source.GUI.Controls/PropertyDialog.cs b/Source.GUI.Controls/PropertyDialog.cs index 11a0d013..b1ab06ec 100644 --- a/Source.GUI.Controls/PropertyDialog.cs +++ b/Source.GUI.Controls/PropertyDialog.cs @@ -1,4 +1,5 @@ using Source.Common.Formats.Keyvalues; +using Source.Common.GUI; namespace Source.GUI.Controls; @@ -113,13 +114,13 @@ public bool OnOK(bool applyOnly) { return true; } - public void ActivateBuildMode() { - // EditablePanel panel = (EditablePanel)GetActivePage(); + public override void ActivateBuildMode() { + EditablePanel panel = (EditablePanel)GetActivePage(); - // if (panel == null) - // return; + if (panel == null) + return; - // panel.ActivateBuildMode(); + panel.ActivateBuildMode(); } public void SetOKButtonText(ReadOnlySpan text) { @@ -164,4 +165,11 @@ public void EnableApplyButton(bool state) { public override void RequestFocus(int direction) { PropertySheet!.RequestFocus(direction); } + + public override void OnMessage(KeyValues message, IPanel? from) { + if (message.Name == "ApplyButtonEnable") { + OnApplyButtonEnable(message.GetBool("state", false)); + return; + } + } } diff --git a/Source.GUI.Controls/PropertyPage.cs b/Source.GUI.Controls/PropertyPage.cs index 8234a175..77d68aae 100644 --- a/Source.GUI.Controls/PropertyPage.cs +++ b/Source.GUI.Controls/PropertyPage.cs @@ -13,10 +13,10 @@ public PropertyPage(Panel? parent, string? name) : base(parent, name) { } - public void OnResetData() { } - public void OnApplyChanges() { } - public void OnPageShow() { } - public void OnPageHide() { } + public virtual void OnResetData() { } + public virtual void OnApplyChanges() { } + public virtual void OnPageShow() { } + public virtual void OnPageHide() { } public void OnPageTabActivated(Panel pageTab) { PageTab = pageTab; @@ -45,6 +45,12 @@ public override void SetVisible(bool state) { public override void OnMessage(KeyValues message, IPanel? from) { switch (message.Name) { + case "ApplyChanges": + OnApplyChanges(); + break; + case "ResetData": + OnResetData(); + break; case "PageShow": OnPageShow(); break; diff --git a/Source.GUI.Controls/PropertySheet.cs b/Source.GUI.Controls/PropertySheet.cs index ca1334d8..71bfee3e 100644 --- a/Source.GUI.Controls/PropertySheet.cs +++ b/Source.GUI.Controls/PropertySheet.cs @@ -819,9 +819,9 @@ public override void OnKeyCodePressed(ButtonCode code) { bool alt = Input.IsKeyDown(ButtonCode.KeyLAlt) || Input.IsKeyDown(ButtonCode.KeyRAlt); if (ctrl && shift && alt && code == ButtonCode.KeyB) { - // EditablePanel? panel = (EditablePanel)GetActivePage()!; - // if (panel != null) - // panel.ActivateBuildMode(); + EditablePanel? panel = (EditablePanel)GetActivePage()!; + if (panel != null) + panel.ActivateBuildMode(); } if (IsKBNavigationEnabled()) { @@ -858,12 +858,32 @@ public override void OnCommand(ReadOnlySpan command) { } public override void OnMessage(KeyValues message, IPanel? from) { - if (message.Name == "TabPressed") { - OnTabPressed((Panel)from!); - return; + switch (message.Name) { + case "TabPressed": + OnTabPressed((Panel)from!); + return; + case "TextChanged": + OnTextChanged((Panel)from!, message.GetString("text", "").ToString()); + return; + case "OpenContextMenu": + OnOpenContextMenu(message); + return; + case "ApplyButtonEnable": + OnApplyButtonEnable(); + return; + case "DefaultButtonSet": + OnDefaultButtonSet((Panel)message.GetPtr("button")!); + return; + case "CurrentDefaultButtonSet": + OnCurrentDefaultButtonSet((Panel)message.GetPtr("button")!); + return; + case "FindDefaultButton": + OnFindDefaultButton(); + return; + default: + base.OnMessage(message, from); + return; } - - base.OnMessage(message, from); } public void OnApplyButtonEnable() { diff --git a/Source.GUI.Controls/RadioButton.cs b/Source.GUI.Controls/RadioButton.cs index 984b6d86..9f978090 100644 --- a/Source.GUI.Controls/RadioButton.cs +++ b/Source.GUI.Controls/RadioButton.cs @@ -14,7 +14,6 @@ public class RadioButton : ToggleButton { // RadioImage RadioBoxImage; int OldTabPosition; - Color SelectedFgColor; int SubTabPosition; public RadioButton(Panel parent, string name, string text) : base(parent, name, text) { @@ -187,4 +186,13 @@ public override void OnKeyCodeTyped(ButtonCode code) { return bestRadio; } + + public override void OnMessage(KeyValues message, IPanel? from) { + if (message.Name == "RadioButtonChecked") { + OnRadioButtonChecked(message.GetInt("tabposition", -1)); + return; + } + + base.OnMessage(message, from); + } } diff --git a/Source.GUI.Controls/Slider.cs b/Source.GUI.Controls/Slider.cs new file mode 100644 index 00000000..1bd40887 --- /dev/null +++ b/Source.GUI.Controls/Slider.cs @@ -0,0 +1,535 @@ +using Source.Common.Formats.Keyvalues; +using Source.Common.GUI; +using Source.Common.Input; + +namespace Source.GUI.Controls; + +public class Slider : Panel +{ + bool Dragging; + int NobPosMin; + int NobPosMax; + int NobDragStartPosX; + int NobDragStartPosY; + int DragStartPosX; + int DragStartPosY; + int RangeMin; + int RangeMax; + int SubRangeMin; + int SubRangeMax; + int Value; + int ButtonOffset; + IBorder? SliderBorder; + IBorder? InsetBorder; + float NobSize; + TextImage? LeftCaption; + TextImage? RightCaption; + public Color TickColor; + Color TrackColor; + Color DisabledTextColor1; + Color DisabledTextColor2; + int NumTicks; + bool _IsDragOnRepositionNob; + bool UseSubRange; + bool Inverted; + public Slider(Panel? parent, ReadOnlySpan name) : base(parent, name) { + _IsDragOnRepositionNob = false; + Dragging = false; + Value = 0; + RangeMin = 0; + RangeMax = 0; + ButtonOffset = 0; + SliderBorder = null; + InsetBorder = null; + NumTicks = 10; + LeftCaption = null; + RightCaption = null; + + SubRangeMin = 0; + SubRangeMax = 0; + UseSubRange = false; + Inverted = false; + + SetThumbWidth(10); + RecomputeNobPosFromValue(); + AddActionSignalTarget(this); + // SetBlockDragChaining(true); + } + + public void SetSliderThumbSubRange(bool enable, int min = 0, int max = 100) { + UseSubRange = enable; + SubRangeMin = min; + SubRangeMax = max; + } + + public override void OnSizeChanged(int newWide, int newTall) { + base.OnSizeChanged(newWide, newTall); + RecomputeNobPosFromValue(); + } + + public void SetValue(int value, bool triggerChangeMessage = true) { + int oldValue = Value; + + if (RangeMin < RangeMax) { + if (value < RangeMin) + value = RangeMin; + if (value > RangeMax) + value = RangeMax; + } + else { + if (value < RangeMax) + value = RangeMax; + if (value > RangeMin) + value = RangeMin; + } + + Value = value; + RecomputeNobPosFromValue(); + + if (Value != oldValue && triggerChangeMessage) + SendSliderMovedMessage(); + } + + public int GetValue() => Value; + + public override void PerformLayout() { + base.PerformLayout(); + RecomputeNobPosFromValue(); + + if (LeftCaption != null) + LeftCaption.ResizeImageToContent(); + + if (RightCaption != null) + RightCaption.ResizeImageToContent(); + } + + public void RecomputeNobPosFromValue() { + GetTrackRect(out int x, out int y, out int wide, out int tall); + + float usevalue = Value; + int userange = RangeMin; + if (UseSubRange) { + userange = SubRangeMin; + usevalue = Math.Clamp(Value, SubRangeMin, SubRangeMax); + } + + float fwide = wide; + float frange = UseSubRange ? (SubRangeMax - SubRangeMin) : (RangeMax - RangeMin); + float fvalue = usevalue - userange; + float fper = (frange != 0.0f) ? (fvalue / frange) : 0.0f; + + if (Inverted) + fper = 1.0f - fper; + + float freepixels = fwide - NobSize; + float leftpixel = x; + float firstpixel = leftpixel + freepixels * fper + 0.5f; + + NobPosMin = (int)firstpixel; + NobPosMax = (int)(firstpixel + NobSize); + + int rightEdge = x + wide; + if (NobPosMax > rightEdge) { + NobPosMin = rightEdge - (int)NobSize; + NobPosMax = rightEdge; + } + + Repaint(); + } + + public void RecomputeValueFromNobPos() { + int value = EstimateValueAtPos(NobPosMin); + SetValue(value); + } + + public int EstimateValueAtPos(int localMouseX) { + GetTrackRect(out int x, out int y, out int wide, out int tall); + + int useRangeMin = UseSubRange ? SubRangeMin : RangeMin; + int useRangeMax = UseSubRange ? SubRangeMax : RangeMax; + + float fwide = wide; + float fnob = localMouseX - x; + float freepixels = fwide - NobSize; + float fvalue = freepixels != 0.0f ? fnob / freepixels : 0.0f; + + return (int)(useRangeMin + fvalue * (useRangeMax - useRangeMin)); + } + public void SetInverted(bool state) => Inverted = state; + + private void SendSliderMovedMessage() { + KeyValues msg = new("SliderMoved", "position", Value); + msg.SetPtr("panel", this); + PostActionSignal(msg); + } + + private void SendSliderDragStartMessage() { + KeyValues msg = new("SliderDragStart", "position", Value); + msg.SetPtr("panel", this); + PostActionSignal(msg); + } + + private void SendSliderDragEndMessage() { + KeyValues msg = new("SliderDragEnd", "position", Value); + msg.SetPtr("panel", this); + PostActionSignal(msg); + } + + public override void ApplySchemeSettings(IScheme scheme) { + base.ApplySchemeSettings(scheme); + + SetFgColor(GetSchemeColor("Slider.NobColor", scheme)); + + TickColor = scheme.GetColor("Slider.TextColor", GetFgColor()); + TrackColor = scheme.GetColor("Slider.TrackColor", GetFgColor()); + + DisabledTextColor1 = scheme.GetColor("Slider.DisabledTextColor1", GetFgColor()); + DisabledTextColor2 = scheme.GetColor("Slider.DisabledTextColor2", GetFgColor()); + + SliderBorder = scheme.GetBorder("ButtonBorder"); + InsetBorder = scheme.GetBorder("ButtonDepressedBorder"); + + if (LeftCaption != null) + LeftCaption.SetFont(scheme.GetFont("DefaultVerySmall", IsProportional())); + + if (RightCaption != null) + RightCaption.SetFont(scheme.GetFont("DefaultVerySmall", IsProportional())); + } + + public override void GetSettings(KeyValues outResourceData) { + base.GetSettings(outResourceData); + + Span buf = stackalloc char[256]; + + if (LeftCaption != null) { + LeftCaption.GetText(buf);//GetUnlocalizedText + outResourceData.SetString("leftText", buf); + } + + if (RightCaption != null) { + RightCaption.GetText(buf);//GetUnlocalizedText + outResourceData.SetString("rightText", buf); + } + } + + public override void ApplySettings(KeyValues resourceData) { + base.ApplySettings(resourceData); + + ReadOnlySpan left = resourceData.GetString("leftText", null); + ReadOnlySpan right = resourceData.GetString("rightText", null); + + int thumbWidth = resourceData.GetInt("thumbwidth", 0); + if (thumbWidth != 0) + SetThumbWidth(thumbWidth); + + SetTickCaptions(left, right); + + int numTicks = resourceData.GetInt("numTicks", -1); + if (numTicks >= 0) + SetNumTicks(numTicks); + + KeyValues rangeMinKV = resourceData.FindKey("rangeMin", false)!; + KeyValues rangeMaxKV = resourceData.FindKey("rangeMax", false)!; + + bool doClamp = false; + if (rangeMinKV != null) { + RangeMin = resourceData.GetInt("rangeMin"); + doClamp = true; + } + + if (rangeMaxKV != null) { + RangeMax = resourceData.GetInt("rangeMax"); + doClamp = true; + } + + if (doClamp) + ClampRange(); + } + + public void GetTrackRect(out int x, out int y, out int w, out int h) { + GetPaintSize(out int wide, out _); + x = 0; + y = 8; + w = wide - (int)NobSize; + h = 4; + } + + public override void Paint() { + DrawTicks(); + DrawTickLabels(); + DrawNob(); + } + + private void DrawTicks() { + GetTrackRect(out int x, out int y, out int wide, out int tall); + + float fwide = wide; + float freepixels = fwide - NobSize; + float leftpixel = NobSize / 2.0f; + float pixelspertick = freepixels / NumTicks; + + y += (int)NobSize; + int tickHeight = 5; + + if (IsEnabled()) { + Surface.DrawSetColor(TickColor); + for (int i = 0; i <= NumTicks; i++) { + int xpos = (int)(leftpixel + i * pixelspertick); + Surface.DrawFilledRect(xpos, y, xpos + 1, y + tickHeight); + } + } else { + Surface.DrawSetColor(DisabledTextColor1); + for (int i = 0; i <= NumTicks; i++) { + int xpos = (int)(leftpixel + i * pixelspertick); + Surface.DrawFilledRect(xpos + 1, y + 1, xpos + 2, y + tickHeight + 1); + } + + Surface.DrawSetColor(DisabledTextColor2); + for (int i = 0; i <= NumTicks; i++) { + int xpos = (int)(leftpixel + i * pixelspertick); + Surface.DrawFilledRect(xpos, y, xpos + 1, y + tickHeight); + } + } + } + + private void DrawTickLabels() { + GetTrackRect(out int x, out int y, out int wide, out int tall); + + y += (int)NobSize + 4; + + if (IsEnabled()) + Surface.DrawSetColor(TickColor); + else + Surface.DrawSetColor(DisabledTextColor1); + + if (LeftCaption != null) { + LeftCaption.SetPos(0, y); + if (IsEnabled()) + LeftCaption.SetColor(TickColor); + else + LeftCaption.SetColor(DisabledTextColor1); + LeftCaption.Paint(); + } + + if (RightCaption != null) { + RightCaption.GetSize(out int rwide, out int rtall); + RightCaption.SetPos(wide - rwide, y); + if (IsEnabled()) + RightCaption.SetColor(TickColor); + else + RightCaption.SetColor(DisabledTextColor1); + RightCaption.Paint(); + } + } + + private void DrawNob() { + GetTrackRect(out int x, out int y, out int wide, out int tall); + Color col = GetFgColor(); + + Surface.DrawSetColor(col); + int nobHeight = 16; + + Surface.DrawFilledRect(NobPosMin, y + tall / 2 - nobHeight / 2, NobPosMax, y + tall / 2 + nobHeight / 2); + + if (SliderBorder != null) + SliderBorder.Paint(NobPosMin, y + tall / 2 - nobHeight / 2, NobPosMax - NobPosMin, nobHeight); + } + + public void SetTickCaptions(ReadOnlySpan left, ReadOnlySpan right) { + if (left.Length > 0) + if (LeftCaption != null) + LeftCaption.SetText(left); + else + LeftCaption = new TextImage(left); + + if (right.Length > 0) + if (RightCaption != null) + RightCaption.SetText(right); + else + RightCaption = new TextImage(right); + + InvalidateLayout(); + } + + public override void PaintBackground() { + base.PaintBackground(); + + GetTrackRect(out int x, out int y, out int wide, out int tall); + + Surface.DrawSetColor(TrackColor); + Surface.DrawFilledRect(x, y, x + wide, y + tall); + if (InsetBorder != null) + InsetBorder.Paint(x, y, x + wide, y + tall); + } + + public void SetRange(int min, int max) { + RangeMin = min; + RangeMax = max; + ClampRange(); + } + + public void ClampRange() { + if (RangeMin < RangeMax) { + if (Value < RangeMin) + SetValue(RangeMin, false); + else if (Value > RangeMax) + SetValue(RangeMax, false); + } else { + if (Value < RangeMax) + SetValue(RangeMax, false); + else if (Value > RangeMin) + SetValue(RangeMin, false); + } + } + + public void GetRange(out int min, out int max) { + min = RangeMin; + max = RangeMax; + } + + public override void OnCursorMoved(int x, int y) { + if (!Dragging) + return; + + Input.GetCursorPos(out x, out y); + ScreenToLocal(ref x, ref y); + + GetTrackRect(out int tx, out int ty, out int wide, out int tall); + + NobPosMin = NobDragStartPosX + (x - DragStartPosX); + NobPosMax = NobDragStartPosY + (x - DragStartPosX); + + int rightEdge = tx + wide; + int unclamped = NobPosMin; + + if (NobPosMax > rightEdge) { + NobPosMin = rightEdge - (NobPosMax - NobPosMin); + NobPosMax = rightEdge; + } + + if (NobPosMin < tx) { + int offset = tx - NobPosMin; + NobPosMax = NobPosMax - offset; + NobPosMin = 0; + } + + int value = EstimateValueAtPos(unclamped); + SetValue(value, false); + + Repaint(); + SendSliderMovedMessage(); + } + + public void SetDragOnRepositionNob(bool state) => _IsDragOnRepositionNob = state; + + public bool IsDragOnRepositionNob() => _IsDragOnRepositionNob; + + public bool IsDragged() => Dragging; + + public override void OnMousePressed(ButtonCode code) { + if (!IsEnabled()) + return; + + Input.GetCursorPos(out int x, out int y); + ScreenToLocal(ref x, ref y); + RequestFocus(); + + bool startDragging = false, PostDragStartSignal = false; + + if (x >= NobPosMin && x <= NobPosMax) { + startDragging = true; + PostDragStartSignal = true; + } + else { + GetRange(out int min, out int max); + if (UseSubRange) { + min = SubRangeMin; + max = SubRangeMax; + } + + GetTrackRect(out int tx, out int ty, out int wide, out int tall); + if (wide > 0) { + float frange = max - min; + float clickFrac = Math.Clamp((float)(x - tx) / (wide - 1), 0.0f, 1.0f); + float value = min + clickFrac * frange; + startDragging = IsDragOnRepositionNob(); + + if (startDragging) { + Dragging = true; + SendSliderDragStartMessage(); + } + + SetValue((int)(value + 0.5f)); + } + } + + if (startDragging) { + Dragging = true; + Input.SetMouseCapture(this); + NobDragStartPosX = NobPosMin; + NobDragStartPosY = NobPosMax; + DragStartPosX = x; + DragStartPosY = y; + + if (PostDragStartSignal) + SendSliderDragStartMessage(); + } + } + + public override void OnMouseDoublePressed(ButtonCode code) => OnMousePressed(code); + + public override void OnKeyCodeTyped(ButtonCode code) { + switch(code) { + case ButtonCode.KeyLeft: + case ButtonCode.KeyDown: + SetValue(Value - 1); + break; + case ButtonCode.KeyRight: + case ButtonCode.KeyUp: + SetValue(Value + 1); + break; + case ButtonCode.KeyPageDown: + GetRange(out int min, out int max); + float range = max - min; + float pertick = range / NumTicks; + SetValue((int)(Value - pertick)); + break; + case ButtonCode.KeyPageUp: + GetRange(out min, out max); + range = max - min; + pertick = range / NumTicks; + SetValue((int)(Value + pertick)); + break; + case ButtonCode.KeyHome: + GetRange(out min, out _); + SetValue(min); + break; + case ButtonCode.KeyEnd: + GetRange(out _, out max); + SetValue(max); + break; + default: + base.OnKeyCodeTyped(code); + break; + } + } + + public override void OnMouseReleased(ButtonCode code) { + if (Dragging) { + Dragging = false; + Input.SetMouseCapture(null); + } + + if (IsEnabled()) + SendSliderDragEndMessage(); + } + + public void GetNobPos(out int min, out int max) { + min = NobPosMin; + max = NobPosMax; + } + + public void SetButtonOffset(int offset) => ButtonOffset = offset; + public void SetThumbWidth(int width) => NobSize = width; + public void SetNumTicks(int numTicks) => NumTicks = numTicks; +} diff --git a/Source.GUI.Controls/TextEntry.cs b/Source.GUI.Controls/TextEntry.cs index 8b3aa672..f7c03608 100644 --- a/Source.GUI.Controls/TextEntry.cs +++ b/Source.GUI.Controls/TextEntry.cs @@ -8,6 +8,7 @@ namespace Source.GUI.Controls; public class TextEntry : Panel { + public static Panel Create_TextEntry() => new TextEntry(null, null); public TextEntry(Panel? parent, string? name) : base(parent, name) { SetTriplePressAllowed(true); @@ -22,6 +23,8 @@ public TextEntry(Panel? parent, string? name) : base(parent, name) { ShouldSelectAllOnFirstFocus = false; ShouldSelectAllOnFocusAlways = false; + GotoTextEnd(); + SetAllowKeyBindingChainToParent(true); } public override void OnKeyFocusTicked() { @@ -234,7 +237,26 @@ public override void OnSizeChanged(int newWide, int newTall) { } private void ScrollLeftForResize() { + if (Multiline) + return; + + if (!HorizScrollingAllowed) + return; + + while (CurrentStartIndex > 0) { + CurrentStartIndex--; + int val = CurrentStartIndex; + if (IsCursorOffRightSideOfWindow(CursorPos)) { + CurrentStartIndex++; + break; + } + + if (val != CurrentStartIndex) + break; + } + + LayoutVerticalScrollBarSlider(); } public int GetDrawWidth() => DrawWidth; @@ -447,7 +469,7 @@ int DrawChar(char ch, IFont? font, int index, int x, int y) { List TextStream = []; List UndoTextStream = []; - List LineBreaks = [BUFFER_SIZE]; + List LineBreaks = []; int CursorPos; bool CursorIsAtEnd; @@ -461,7 +483,7 @@ int DrawChar(char ch, IFont? font, int index, int x, int y) { long CursorNextBlinkTime; int CursorBlinkRate; int PixelsIndent; - int CharsCount; + int CharCount; int MaxCharCount; bool DataChanged; bool Multiline; @@ -610,6 +632,23 @@ public void SetCatchEnterKey(bool state) { CatchEnterKey = state; } + public void SetVerticalScrollbar(bool state) { + VerticalScrollbar = state; + + if (VerticalScrollbar) { + if (VertScrollBar == null) { + VertScrollBar = new(this, "ScrollBar", true); + VertScrollBar.AddActionSignalTarget(this); + } + + VertScrollBar.SetVisible(true); + } + else if (VertScrollBar != null) + VertScrollBar.SetVisible(false); + + InvalidateLayout(); + } + public override void OnKeyCodePressed(ButtonCode code) { if (code == ButtonCode.KeyEnter) { if (!CatchEnterKey) { @@ -952,12 +991,12 @@ private void ScrollRight() LayoutVerticalScrollBarSlider(); } - private void Undo() { + private void Undo() { // FIXME: why does this only work once? CursorPos = UndoCursorPos; // I have a bad feeling about this... - UndoTextStream.CopyTo(TextStream.AsSpan()); - TextStream.RemoveRange(UndoTextStream.Count, TextStream.Count - UndoTextStream.Count); + TextStream.Clear(); + TextStream.AddRange(UndoTextStream); InvalidateLayout(); Repaint(); @@ -1109,7 +1148,16 @@ private bool IsCursorOffLeftSideOfWindow(int CursorPos) { } private void CalcBreakIndex() { + // if (CursorPos == TextStream.Count) { + // RecalculateBreaksIndex = LineBreaks.Count - 2; + // return; + // } + + // RecalculateBreaksIndex = 0; + // while (CursorPos > LineBreaks[RecalculateBreaksIndex]) // todo: fix out of range + // ++RecalculateBreaksIndex; + // --RecalculateBreaksIndex; } private void LayoutVerticalScrollBarSlider() { @@ -1117,11 +1165,95 @@ private void LayoutVerticalScrollBarSlider() { } private void RecalculateLineBreaks() { + if (!Multiline || HideText) + return; + + if (TextStream.Count == 0) + return; + + IFont font = Font!; + + int wide = GetWide() - 2; + + if (VertScrollBar != null) + wide -= VertScrollBar.GetWide(); + int charWidth; + int x = DRAW_OFFSET_X, y = DRAW_OFFSET_Y; + int wordStartIndex = 0; + int wordLength = 0; + bool hasWord = false; + bool justStartedNewLine = true; + bool wordStartedOnNewLine = true; + + int startChar = 0; + if (RecalculateBreaksIndex <= 0) { + LineBreaks.Clear(); + } + else { + for (int i2 = RecalculateBreaksIndex + 1; i2 < LineBreaks.Count; ++i2) { + LineBreaks.RemoveAt(i2); + i2--; + startChar = LineBreaks[RecalculateBreaksIndex]; + } + } + + if (TextStream[startChar] == '\r' || TextStream[startChar] == '\n') + startChar++; + + int i; + for (i = startChar; i < TextStream.Count; ++i) { + char ch = TextStream[i]; + + if (!char.IsWhiteSpace(ch)) { + if (hasWord) { + + } + else { + wordStartIndex = i; + hasWord = true; + wordStartedOnNewLine = justStartedNewLine; + wordLength = 0; + } + } + else + hasWord = false; + + charWidth = getCharWidth(font, ch); + if (char.IsControl(ch)) + justStartedNewLine = false; + + if ((x + charWidth) > wide || ch == '\r' || ch == '\n') { + AddAnotherLine(ref x, ref y); + + justStartedNewLine = true; + hasWord = false; + + if (ch == '\r' || ch == '\n') + LineBreaks.Add(i); + else if (wordStartedOnNewLine) + LineBreaks.Add(wordStartIndex); + else { + LineBreaks.Add(wordStartIndex); + i = wordStartIndex; + } + + wordLength = 0; + } + + x += charWidth; + wordLength += charWidth; + } + + CharCount = i - 1; + LineBreaks.Add(BUFFER_SIZE); + LayoutVerticalScrollBarSlider(); } private void SaveUndoState() { - + UndoCursorPos = CursorPos; + UndoTextStream.Clear(); + UndoTextStream.AddRange(TextStream); } private void Backspace() { @@ -1782,7 +1914,7 @@ public int GetText(Span outBuffer) { int i, c; for (i = 0, c = Math.Min(outBuffer.Length, TextStream.Count); i < c; i++) { char ch = TextStream[i]; - if (c == '\0') + if (ch == '\0') break; outBuffer[i] = ch; } @@ -1796,7 +1928,7 @@ public void SetText(ReadOnlySpan text) { if (text.Length > 0 && text[0] == '#') { ReadOnlySpan localized = Localize.Find(text); if (!localized.IsEmpty) { - SetText(text); + SetText(localized); return; } } diff --git a/Source.GUI.Controls/ToggleButton.cs b/Source.GUI.Controls/ToggleButton.cs index 29c87b35..c6bf927b 100644 --- a/Source.GUI.Controls/ToggleButton.cs +++ b/Source.GUI.Controls/ToggleButton.cs @@ -8,7 +8,7 @@ public class ToggleButton : Button { Color SelectedColor; - public ToggleButton(Panel parent, string name, string text) : base(parent, name, text) { + public ToggleButton(Panel parent, ReadOnlySpan name, ReadOnlySpan text) : base(parent, name, text) { SetButtonActivationType(ActivationType.OnPressed); } @@ -49,7 +49,7 @@ public override void ApplySchemeSettings(IScheme scheme) { } public override void OnKeyCodePressed(ButtonCode code) { - if (code == ButtonCode.KeyEnter) + if (code != ButtonCode.KeyEnter) base.OnKeyCodePressed(code); } } diff --git a/Source.GUI/ImageBorder.cs b/Source.GUI/ImageBorder.cs index b855f897..15162bae 100644 --- a/Source.GUI/ImageBorder.cs +++ b/Source.GUI/ImageBorder.cs @@ -7,7 +7,6 @@ namespace Source.GUI; public class ImageBorder : Border { - string? Name; PaintBackgroundType BackgroundType; TextureID TextureID; bool Tiled;