Skip to content
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
499 changes: 499 additions & 0 deletions TLM/TLM/Resources/Options/Tristate-switch.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added TLM/TLM/Resources/Options/tristate-false.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added TLM/TLM/Resources/Options/tristate-null.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added TLM/TLM/Resources/Options/tristate-true.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 2 additions & 1 deletion TLM/TLM/State/OptionsTabs/KeybindsTab.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,14 @@ namespace TrafficManager.State {
using TrafficManager.State.Keybinds;
using TrafficManager.UI.Helpers;
using TrafficManager.UI;
using TrafficManager.Util.Extensions;

public static class KeybindsTab {
internal static void MakeSettings_Keybinds(ExtUITabstrip tabStrip) {
string keybindsTabText = Translation.Options.Get("Tab:Keybinds");
UIHelper panelHelper = tabStrip.AddTabPage(keybindsTabText, false);
UIHelperBase keyboardGroup = panelHelper.AddGroup(keybindsTabText);
((UIPanel)((UIHelper)keyboardGroup).self).gameObject.AddComponent<KeybindSettingsPage>();
keyboardGroup.AddComponent<KeybindSettingsPage>();
}
}
}
7 changes: 7 additions & 0 deletions TLM/TLM/TLM.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -181,12 +181,15 @@
<Compile Include="TrafficLight\Impl\ITrafficLightContainer.cs" />
<Compile Include="TrafficLight\Impl\TrafficLightSimulation.cs" />
<Compile Include="UI\AllowDespawn\AllowDespawnPanel.cs" />
<Compile Include="UI\Helpers\IValuePropagator.cs" />
<Compile Include="UI\Helpers\ActionButton.cs" />
<Compile Include="UI\Helpers\TriStateCheckboxOption.cs" />
<Compile Include="UI\Helpers\GlobalConfigObserver.cs" />
<Compile Include="UI\Helpers\DropDownOption.cs" />
<Compile Include="UI\Helpers\DLCRestrictedCheckboxOption.cs" />
<Compile Include="UI\Helpers\SliderOption.cs" />
<Compile Include="UI\Helpers\OptionButtonBase.cs" />
<Compile Include="UI\Helpers\UITriStateCheckbox.cs" />
<Compile Include="UI\Helpers\UrlButton.cs" />
<Compile Include="UI\Textures\RoadSignTheme.cs" />
<Compile Include="UI\MainMenu\RoutingDetectorButton.cs" />
Expand All @@ -198,6 +201,7 @@
<Compile Include="UI\WhatsNew\WhatsNewMarkup.cs" />
<Compile Include="Util\DetailLogger.cs" />
<Compile Include="Util\Extensions\ParkedVehicleExtensions.cs" />
<Compile Include="Util\Extensions\UIHelperExtensions.cs" />
<Compile Include="Util\Extensions\VersionExtension.cs" />
<Compile Include="Util\Iterators\GetNodeSegmentIdsEnumerable.cs" />
<Compile Include="Util\Iterators\GetNodeSegmentIdsEnumerator.cs" />
Expand Down Expand Up @@ -1239,6 +1243,9 @@
<EmbeddedResource Include="Resources\SignThemes\Fallback\Restrict-PedestrianCrossing.png" />
<EmbeddedResource Include="Resources\SignThemes\Fallback\Restrict-RightOnRed.png" />
<EmbeddedResource Include="Resources\SignThemes\Fallback\Restrict-UTurn.png" />
<EmbeddedResource Include="Resources\Options\tristate-false.png" />
<EmbeddedResource Include="Resources\Options\tristate-null.png" />
<EmbeddedResource Include="Resources\Options\tristate-true.png" />
<Content Include="Resources\SignThemes\Kmph_China_Generic\README.md" />
</ItemGroup>
<ItemGroup Condition="'$(Configuration)'=='Debug' Or '$(Configuration)'=='FullDebug' Or '$(Configuration)'=='PF2_Debug'">
Expand Down
51 changes: 22 additions & 29 deletions TLM/TLM/UI/Helpers/CheckboxOption.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,43 +9,47 @@ namespace TrafficManager.UI.Helpers {
using TrafficManager.Util;
using UnityEngine;

public class CheckboxOption : SerializableUIOptionBase<bool, UICheckBox, CheckboxOption> {
public class CheckboxOption :
SerializableUIOptionBase<bool, UICheckBox, CheckboxOption>, IValuePropagator {
private const int CHECKBOX_LABEL_MAX_WIDTH = 695;
private const int CHECKBOX_LABEL_MAX_WIDTH_INDENTED = 680;

private List<CheckboxOption> _propagatesTrueTo;
private List<CheckboxOption> _propagatesFalseTo;
private HashSet<IValuePropagator> _propagatesTrueTo = new();
private HashSet<IValuePropagator> _propagatesFalseTo = new();

public CheckboxOption(string fieldName, Options.PersistTo scope = Options.PersistTo.Savegame)
: base(fieldName, scope) { }

/* Data */
/// <summary>
/// If this checkbox is set <c>true</c>, it will propagate that to the <paramref name="target"/>.
/// Chainable.
/// </summary>
/// <param name="target">The checkox to propagate <c>true</c> value to.</param>
/// <param name="target">The checkbox to propagate <c>true</c> value to.</param>
/// <remarks>
/// If target is set <c>false</c>, it will propagate that back to this checkbox.
/// </remarks>
public CheckboxOption PropagateTrueTo([NotNull] CheckboxOption target) {
Log.Info($"CheckboxOption.PropagateTrueTo: `{FieldName}` will propagate to `{target.FieldName}`");

if (_propagatesTrueTo == null)
_propagatesTrueTo = new();
public CheckboxOption PropagateTrueTo([NotNull] IValuePropagator target) {
Log.Info($"CheckboxOption.PropagateTrueTo: `{FieldName}` will propagate to `{target}`");
this.AddPropagate(target,true);
target.AddPropagate(this, false);
return this;
}

if (!_propagatesTrueTo.Contains(target))
_propagatesTrueTo.Add(target);
private HashSet<IValuePropagator> GetTargetPropagates(bool value) =>
value ? _propagatesTrueTo : _propagatesFalseTo;

if (target._propagatesFalseTo == null)
target._propagatesFalseTo = new();
public void AddPropagate(IValuePropagator target, bool value) =>
GetTargetPropagates(value).Add(target);

if (!target._propagatesFalseTo.Contains(this))
target._propagatesFalseTo.Add(this);
public void Propagate(bool value) => Value = value;

return this;
private void PropagateAll(bool value) {
foreach (var target in GetTargetPropagates(value))
target.Propagate(value);
}

public override string ToString() => FieldName;

public override void Load(byte data) => Value = data != 0;

public override byte Save() => (byte)(Value ? 1 : 0);
Expand All @@ -67,14 +71,8 @@ public override bool Value {
return;

base.Value = value;

Log.Info($"CheckboxOption.Value: `{FieldName}` changed to {value}");

if (value && _propagatesTrueTo != null)
PropagateTo(_propagatesTrueTo, true);

if (!value && _propagatesFalseTo != null)
PropagateTo(_propagatesFalseTo, false);
PropagateAll(value);

if (Shortcuts.IsMainThread()) {
if (HasUI) {
Expand Down Expand Up @@ -105,11 +103,6 @@ public override CheckboxOption AddUI(UIHelperBase container) {
return this;
}

private void PropagateTo(IList<CheckboxOption> targets, bool value) {
foreach (var target in targets)
target.Value = value;
}

protected override void UpdateLabel() {
if (!HasUI) return;

Expand Down
3 changes: 2 additions & 1 deletion TLM/TLM/UI/Helpers/DLCRestrictedCheckboxOption.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ namespace TrafficManager.UI.Helpers;
using ColossalFramework.UI;
using ICities;
using TrafficManager.State;
using TrafficManager.Util.Extensions;
using UnityEngine;

public class DLCRestrictedCheckboxOption : CheckboxOption {
Expand All @@ -18,7 +19,7 @@ public DLCRestrictedCheckboxOption(string fieldName,
}

public override CheckboxOption AddUI(UIHelperBase container) {
UIPanel panel = ((UIPanel)((UIHelper)container).self).AddUIComponent<UIPanel>();
UIPanel panel = container.AddUIComponent<UIPanel>();
panel.autoLayout = false;
panel.size = new Vector2(720, 22);//default checkbox template size
panel.relativePosition = Vector3.zero;
Expand Down
11 changes: 11 additions & 0 deletions TLM/TLM/UI/Helpers/IValuePropagator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
namespace TrafficManager.UI.Helpers {
/// <summary>
/// we propagate <c>true</c> when depender* has been enabled.
/// we propagate <c>false</c> when dependee* has been disabled.
/// (depender depends on dependee)
/// </summary>
public interface IValuePropagator {
void Propagate(bool value);
void AddPropagate(IValuePropagator item, bool value);
}
}
2 changes: 0 additions & 2 deletions TLM/TLM/UI/Helpers/SerializableUIOptionBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ namespace TrafficManager.UI.Helpers {

public abstract class SerializableUIOptionBase<TVal, TUI, TComponent> : ILegacySerializableOption
where TUI : UIComponent
where TVal : IConvertible
{

/// <summary>Use as tooltip for readonly UI components.</summary>
Expand Down Expand Up @@ -53,7 +52,6 @@ public SerializableUIOptionBase(string fieldName, Options.PersistTo scope) {

_fieldName = fieldName;
_scope = scope;

if (scope.IsFlagSet(Options.PersistTo.Savegame)) {
_fieldInfo = typeof(Options).GetField(fieldName, BindingFlags.Static | BindingFlags.Public);

Expand Down
158 changes: 158 additions & 0 deletions TLM/TLM/UI/Helpers/TriStateCheckboxOption.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
namespace TrafficManager.UI.Helpers {
using ICities;
using ColossalFramework.UI;
using TrafficManager.State;
using CSUtil.Commons;
using UnityEngine;
using TrafficManager.Util.Extensions;
using JetBrains.Annotations;
using System.Collections.Generic;
using TrafficManager.Util;
using System;

public class TriStateCheckboxOption :
SerializableUIOptionBase<bool?, UITriStateCheckbox, TriStateCheckboxOption>, IValuePropagator {
private const int LABEL_MAX_WIDTH = 615;
private const int LABEL_MAX_WIDTH_INDENTED = 600;

public TriStateCheckboxOption(string fieldName, Options.PersistTo scope = Options.PersistTo.Savegame)
: base(fieldName, scope) { }

private HashSet<IValuePropagator> _propagatesTrueTo = new();
private HashSet<IValuePropagator> _propagatesFalseTo = new();

public TriStateCheckboxOption PropagateTrueTo([NotNull] IValuePropagator target) {
Log.Info($"TriStateCheckboxOption.PropagateTrueTo: `{FieldName}` will propagate to `{target}`");
this.AddPropagate(target, true);
target.AddPropagate(this, false);
return this;
}

private HashSet<IValuePropagator> GetTargetPropagates(bool value) =>
value ? _propagatesTrueTo : _propagatesFalseTo;

public void AddPropagate(IValuePropagator target, bool value) =>
GetTargetPropagates(value).Add(target);

public void Propagate(bool value) {
if (!value) {
// this tristate button depends on another option that has been disabled.
Value = null;
} else {
// Don't know to set to true or false because both are none-null.
// I don't think it makes sense for other options to depend on tristate checkbox anyway.
throw new NotImplementedException("other options cannot depend on tristate checkbox");
}
}

private void PropagateAll(bool value) {
foreach (var target in GetTargetPropagates(value))
target.Propagate(value);
}

public override string ToString() => FieldName;

public override void Load(byte data) =>
Value = TernaryBoolUtil.ToOptBool((TernaryBool)data);

public override byte Save() =>
(byte)TernaryBoolUtil.ToTernaryBool(Value);

public override bool? Value {
get => base.Value;
set {
if (value != base.Value) {
base.Value = value;
Log.Info($"TriStateCheckboxOption.Value: `{FieldName}` changed to {value}");
PropagateAll(value.HasValue);

if (Shortcuts.IsMainThread()) {
if (HasUI) {
_ui.Value = value;
}
} else {
SimulationManager.instance.m_ThreadingWrapper.QueueMainThread(() => {
if (HasUI) {
_ui.Value = value;
}
});
}
}
}
}

public override TriStateCheckboxOption AddUI(UIHelperBase container) {
_ui = container.AddUIComponent<UITriStateCheckbox>();
_ui.EventValueChanged += (_,val) => InvokeOnValueChanged(val);
if (Indent) ApplyIndent(_ui);
UpdateLabel();
ApplyTextWrap(_ui, Indent);

UpdateTooltip();
UpdateReadOnly();

return this;
}

protected override void UpdateLabel() {
if (!HasUI) return;

_ui.label.text = Translate(Label);
}

protected override void UpdateTooltip() {
if (!HasUI) return;

_ui.tooltip = IsInScope
? string.IsNullOrEmpty(_tooltip)
? string.Empty // avoid invalidating UI if already no tooltip
: Translate(_tooltip)
: Translate(INGAME_ONLY_SETTING);
}

protected override void UpdateReadOnly() {
if (!HasUI) return;

var readOnly = !IsInScope || _readOnly;

Log._Debug($"TriStateCheckboxOption.UpdateReadOnly() - `{FieldName}` is {(readOnly ? "read-only" : "writeable")}");

_ui.readOnly = readOnly;
_ui.opacity = readOnly ? 0.3f : 1f;
}

/* UI helper methods */

internal static void ApplyIndent(UIComponent component) {
UILabel label = component.Find<UILabel>("Label");

if (label != null) {
label.padding = new RectOffset(22, 0, 0, 0);
}

UISprite check = component.Find<UISprite>("Unchecked");

if (check != null) {
check.relativePosition += new Vector3(22.0f, 0);
}
}

internal static void ApplyTextWrap(UICheckBox checkBox, bool indented = false) {
UILabel label = checkBox.label;
bool requireTextWrap;
int maxWidth = indented ? LABEL_MAX_WIDTH_INDENTED : LABEL_MAX_WIDTH;
using (UIFontRenderer renderer = label.ObtainRenderer()) {
Vector2 size = renderer.MeasureString(label.text);
requireTextWrap = size.x > maxWidth;
}
label.autoSize = false;
label.wordWrap = true;
label.verticalAlignment = UIVerticalAlignment.Middle;
label.textAlignment = UIHorizontalAlignment.Left;
label.size = new Vector2(maxWidth, requireTextWrap ? 40 : 20);
if (requireTextWrap) {
checkBox.height = 42; // set new height + top/bottom 1px padding
}
}
}
}
Loading