diff --git a/.gitignore b/.gitignore
index 0afd316..e3bdbdc 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,6 +3,7 @@
##
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
.idea
+qodana.yaml
# User-specific files
*.rsuser
*.suo
@@ -13,6 +14,8 @@
MareSynchronos/.DS_Store
*.zip
UmbraServer_extracted/
+NuGet.config
+Directory.Build.props
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
diff --git a/MareAPI b/MareAPI
index deb911c..f75f16f 160000
--- a/MareAPI
+++ b/MareAPI
@@ -1 +1 @@
-Subproject commit deb911cb0a0e0abb2bfe4df9c243e09a70db4000
+Subproject commit f75f16fb13637ba3e2b7cfc4c79de252f4d4eea6
diff --git a/MareSynchronos/MareSynchronos.csproj b/MareSynchronos/MareSynchronos.csproj
index 0b7986d..5c1f6c2 100644
--- a/MareSynchronos/MareSynchronos.csproj
+++ b/MareSynchronos/MareSynchronos.csproj
@@ -50,6 +50,8 @@
build$([System.DateTime]::UtcNow.ToString("yyyy-MM-ddTHH:mm:ss:fffZ"))
enable
+ true
+ $(NoWarn);NU1900
diff --git a/MareSynchronos/Services/AutoDetect/SyncshellDiscoveryService.cs b/MareSynchronos/Services/AutoDetect/SyncshellDiscoveryService.cs
index 67394b7..89d4392 100644
--- a/MareSynchronos/Services/AutoDetect/SyncshellDiscoveryService.cs
+++ b/MareSynchronos/Services/AutoDetect/SyncshellDiscoveryService.cs
@@ -73,6 +73,13 @@ public sealed class SyncshellDiscoveryService : IHostedService, IMediatorSubscri
}
public async Task SetVisibilityAsync(string gid, bool visible, CancellationToken ct)
+ {
+ return await SetVisibilityAsync(gid, visible, null, null, null, null, null, ct).ConfigureAwait(false);
+ }
+
+ public async Task SetVisibilityAsync(string gid, bool visible, int? displayDurationHours,
+ int[]? activeWeekdays, TimeSpan? timeStartLocal, TimeSpan? timeEndLocal, string? timeZone,
+ CancellationToken ct)
{
try
{
@@ -80,6 +87,11 @@ public sealed class SyncshellDiscoveryService : IHostedService, IMediatorSubscri
{
GID = gid,
AutoDetectVisible = visible,
+ DisplayDurationHours = displayDurationHours,
+ ActiveWeekdays = activeWeekdays,
+ TimeStartLocal = timeStartLocal.HasValue ? new DateTime(timeStartLocal.Value.Ticks).ToString("HH:mm") : null,
+ TimeEndLocal = timeEndLocal.HasValue ? new DateTime(timeEndLocal.Value.Ticks).ToString("HH:mm") : null,
+ TimeZone = timeZone,
};
var success = await _apiController.SyncshellDiscoverySetVisibility(request).ConfigureAwait(false);
if (!success) return false;
diff --git a/MareSynchronos/UI/Components/DrawGroupPair.cs b/MareSynchronos/UI/Components/DrawGroupPair.cs
index ec15593..bbf1bb7 100644
--- a/MareSynchronos/UI/Components/DrawGroupPair.cs
+++ b/MareSynchronos/UI/Components/DrawGroupPair.cs
@@ -79,6 +79,8 @@ public class DrawGroupPair : DrawPairBase
width += _uiSharedService.GetIconButtonSize(FontAwesomeIcon.Plus).X + spacing;
}
+ width += spacing * 1.2f;
+
return width;
}
@@ -215,6 +217,7 @@ public class DrawGroupPair : DrawPairBase
var pauseIcon = _fullInfoDto.GroupUserPermissions.IsPaused() ? FontAwesomeIcon.Play : FontAwesomeIcon.Pause;
var pauseButtonWidth = _uiSharedService.GetIconButtonSize(pauseIcon).X;
var barButtonWidth = _uiSharedService.GetIconButtonSize(FontAwesomeIcon.Bars).X;
+ var rightEdgeGap = spacing * 1.2f;
float totalWidth = 0f;
void Accumulate(bool condition, float width)
@@ -242,7 +245,7 @@ public class DrawGroupPair : DrawPairBase
float cardPaddingX = UiSharedService.GetCardContentPaddingX();
float rightMargin = cardPaddingX + 6f * ImGuiHelpers.GlobalScale;
float baseX = MathF.Max(ImGui.GetCursorPosX(),
- ImGui.GetWindowContentRegionMin().X + UiSharedService.GetWindowContentRegionWidth() - rightMargin - totalWidth);
+ ImGui.GetWindowContentRegionMin().X + UiSharedService.GetWindowContentRegionWidth() - rightMargin - rightEdgeGap - totalWidth);
float currentX = baseX;
ImGui.SameLine();
@@ -266,6 +269,16 @@ public class DrawGroupPair : DrawPairBase
if (showInfo && infoIconWidth > 0f)
{
+ bool centerWarning = permIcon == FontAwesomeIcon.ExclamationTriangle && showPause && showBars && !showShared && !showPlus;
+ if (centerWarning)
+ {
+ float barsClusterWidth = showBars ? (barButtonWidth + spacing * 0.5f) : 0f;
+ float leftAreaWidth = MathF.Max(totalWidth - pauseButtonWidth - barsClusterWidth, 0f);
+ float warningX = baseX + MathF.Max((leftAreaWidth - infoIconWidth) / 2f, 0f);
+ currentX = warningX;
+ ImGui.SetCursorPosX(currentX);
+ }
+
ImGui.SetCursorPosY(textPosY);
if (individualAnimDisabled || individualSoundsDisabled || individualVFXDisabled)
{
@@ -359,7 +372,7 @@ public class DrawGroupPair : DrawPairBase
{
ImGui.SetCursorPosY(originalY);
- if (_uiSharedService.IconButton(FontAwesomeIcon.Plus))
+ if (_uiSharedService.IconPlusButtonCentered())
{
var targetUid = _pair.UserData.UID;
if (!string.IsNullOrEmpty(targetUid))
@@ -376,7 +389,7 @@ public class DrawGroupPair : DrawPairBase
{
float gapToBars = showBars ? spacing * 0.5f : spacing;
ImGui.SetCursorPosY(originalY);
- if (_uiSharedService.IconButton(pauseIcon))
+ if (pauseIcon == FontAwesomeIcon.Pause ? _uiSharedService.IconPauseButtonCentered() : _uiSharedService.IconButtonCentered(pauseIcon))
{
var newPermissions = _fullInfoDto.GroupUserPermissions ^ GroupUserPermissions.Paused;
_fullInfoDto.GroupUserPermissions = newPermissions;
@@ -391,7 +404,7 @@ public class DrawGroupPair : DrawPairBase
if (showBars)
{
ImGui.SetCursorPosY(originalY);
- if (_uiSharedService.IconButton(FontAwesomeIcon.Bars))
+ if (_uiSharedService.IconButtonCentered(FontAwesomeIcon.Bars))
{
ImGui.OpenPopup("Syncshell Flyout Menu");
}
diff --git a/MareSynchronos/UI/Components/DrawPairBase.cs b/MareSynchronos/UI/Components/DrawPairBase.cs
index cc6c88d..a82aebe 100644
--- a/MareSynchronos/UI/Components/DrawPairBase.cs
+++ b/MareSynchronos/UI/Components/DrawPairBase.cs
@@ -42,8 +42,8 @@ public abstract class DrawPairBase
var menuButtonSize = _uiSharedService.GetIconButtonSize(FontAwesomeIcon.Bars);
float pauseClusterWidth = Math.Max(pauseButtonSize.X, playButtonSize.X);
- float pauseClusterHeight = Math.Max(pauseButtonSize.Y, playButtonSize.Y);
- float reservedSpacing = style.ItemSpacing.X * 2.4f;
+ float pauseClusterHeight = Math.Max(Math.Max(pauseButtonSize.Y, playButtonSize.Y), ImGui.GetFrameHeight());
+ float reservedSpacing = style.ItemSpacing.X * 1.6f;
float rightButtonWidth =
menuButtonSize.X +
pauseClusterWidth +
@@ -84,11 +84,15 @@ public abstract class DrawPairBase
ImGui.SetCursorPos(new Vector2(rowStartCursor.X + padding.X, iconTop));
DrawLeftSide(iconTop, iconTop);
- ImGui.SameLine();
- ImGui.SetCursorPosY(textTop);
- var posX = ImGui.GetCursorPosX();
+
+ float leftReserved = GetLeftSideReservedWidth();
+ float nameStartX = rowStartCursor.X + padding.X + leftReserved;
+
var rightSide = DrawRightSide(buttonTop, buttonTop);
- DrawName(textTop + padding.Y * 0.15f, posX, rightSide);
+
+ ImGui.SameLine(nameStartX);
+ ImGui.SetCursorPosY(textTop);
+ DrawName(textTop + padding.Y * 0.15f, nameStartX, rightSide);
ImGui.SetCursorPos(new Vector2(rowStartCursor.X, rowStartCursor.Y + totalHeight));
ImGui.SetCursorPosX(rowStartCursor.X);
@@ -100,6 +104,8 @@ public abstract class DrawPairBase
protected virtual float GetRightSideExtraWidth() => 0f;
+ protected virtual float GetLeftSideReservedWidth() => UiSharedService.GetIconSize(FontAwesomeIcon.Moon).X * 2f + ImGui.GetStyle().ItemSpacing.X * 1.5f;
+
private void DrawName(float originalY, float leftSide, float rightSide)
{
_displayHandler.DrawPairText(_id, _pair, leftSide, originalY, () => rightSide - leftSide);
diff --git a/MareSynchronos/UI/Components/DrawUserPair.cs b/MareSynchronos/UI/Components/DrawUserPair.cs
index 6de3e2b..86f62c5 100644
--- a/MareSynchronos/UI/Components/DrawUserPair.cs
+++ b/MareSynchronos/UI/Components/DrawUserPair.cs
@@ -60,8 +60,28 @@ public class DrawUserPair : DrawPairBase
width += _uiSharedService.GetIconButtonSize(FontAwesomeIcon.Running).X + spacingX * 0.5f;
}
+ width += spacingX * 1.2f;
return width;
}
+
+ protected override float GetLeftSideReservedWidth()
+ {
+ var style = ImGui.GetStyle();
+ float spacing = style.ItemSpacing.X;
+ float iconW = UiSharedService.GetIconSize(FontAwesomeIcon.Moon).X;
+
+ int icons = 1;
+ if (!(_pair.UserPair!.OwnPermissions.IsPaired() && _pair.UserPair!.OtherPermissions.IsPaired()))
+ icons++;
+ else if (_pair.UserPair!.OwnPermissions.IsPaused() || _pair.UserPair!.OtherPermissions.IsPaused())
+ icons++;
+ if (_pair.IsOnline && _pair.IsVisible)
+ icons++;
+
+ float iconsTotal = icons * iconW + Math.Max(0, icons - 1) * spacing;
+ float cushion = spacing * 0.6f;
+ return iconsTotal + cushion;
+ }
protected override void DrawLeftSide(float textPosY, float originalY)
{
@@ -133,7 +153,8 @@ public class DrawUserPair : DrawPairBase
var entryUID = _pair.UserData.AliasOrUID;
var spacingX = ImGui.GetStyle().ItemSpacing.X;
var edgePadding = UiSharedService.GetCardContentPaddingX() + 6f * ImGuiHelpers.GlobalScale;
- var windowEndX = ImGui.GetWindowContentRegionMin().X + UiSharedService.GetWindowContentRegionWidth() - edgePadding;
+ var rightEdgeGap = spacingX * 1.2f;
+ var windowEndX = ImGui.GetWindowContentRegionMin().X + UiSharedService.GetWindowContentRegionWidth() - edgePadding - rightEdgeGap;
var rightSidePos = windowEndX - barButtonSize.X;
// Flyout Menu
@@ -150,13 +171,12 @@ public class DrawUserPair : DrawPairBase
ImGui.EndPopup();
}
- // Pause (mutual pairs only)
if (_pair.UserPair!.OwnPermissions.IsPaired() && _pair.UserPair!.OtherPermissions.IsPaired())
{
rightSidePos -= pauseIconSize.X + spacingX;
ImGui.SameLine(rightSidePos);
ImGui.SetCursorPosY(originalY);
- if (_uiSharedService.IconButton(pauseIcon))
+ if (pauseIcon == FontAwesomeIcon.Pause ? _uiSharedService.IconPauseButtonCentered() : _uiSharedService.IconButtonCentered(pauseIcon))
{
var perm = _pair.UserPair!.OwnPermissions;
perm.SetPaused(!perm.IsPaused());
diff --git a/MareSynchronos/UI/SyncshellAdminUI.cs b/MareSynchronos/UI/SyncshellAdminUI.cs
index 58c0559..0365d5a 100644
--- a/MareSynchronos/UI/SyncshellAdminUI.cs
+++ b/MareSynchronos/UI/SyncshellAdminUI.cs
@@ -41,6 +41,16 @@ public class SyncshellAdminUI : WindowMediatorSubscriberBase
private bool _autoDetectVisible;
private bool _autoDetectPasswordDisabled;
private string? _autoDetectMessage;
+
+ private bool _autoDetectDesiredVisibility;
+ private int _adDurationHours = 2;
+ private bool _adRecurring = false;
+ private readonly bool[] _adWeekdays = new bool[7];
+ private int _adStartHour = 21;
+ private int _adStartMinute = 0;
+ private int _adEndHour = 23;
+ private int _adEndMinute = 0;
+ private const string AutoDetectTimeZone = "Europe/Paris";
public SyncshellAdminUI(ILogger logger, MareMediator mediator, ApiController apiController,
UiSharedService uiSharedService, PairManager pairManager, SyncshellDiscoveryService syncshellDiscoveryService,
@@ -58,6 +68,7 @@ public class SyncshellAdminUI : WindowMediatorSubscriberBase
_multiInvites = 30;
_pwChangeSuccess = true;
_autoDetectVisible = groupFullInfo.AutoDetectVisible;
+ _autoDetectDesiredVisibility = _autoDetectVisible;
_autoDetectPasswordDisabled = groupFullInfo.PasswordTemporarilyDisabled;
Mediator.Subscribe(this, OnSyncshellAutoDetectStateChanged);
IsOpen = true;
@@ -89,6 +100,9 @@ public class SyncshellAdminUI : WindowMediatorSubscriberBase
ImGui.Separator();
var perm = GroupFullInfo.GroupPermissions;
+ using var tabColor = ImRaii.PushColor(ImGuiCol.Tab, UiSharedService.AccentColor);
+ using var tabHoverColor = ImRaii.PushColor(ImGuiCol.TabHovered, UiSharedService.AccentHoverColor);
+ using var tabActiveColor = ImRaii.PushColor(ImGuiCol.TabActive, UiSharedService.AccentActiveColor);
using var tabbar = ImRaii.TabBar("syncshell_tab_" + GroupFullInfo.GID);
if (tabbar)
@@ -498,26 +512,93 @@ public class SyncshellAdminUI : WindowMediatorSubscriberBase
UiSharedService.ColorTextWrapped(_autoDetectMessage!, ImGuiColors.DalamudYellow);
}
- bool desiredVisibility = _autoDetectVisible;
using (ImRaii.Disabled(_autoDetectToggleInFlight || _autoDetectStateLoading))
{
- if (ImGui.Checkbox("Afficher cette Syncshell dans l'AutoDetect", ref desiredVisibility))
+ if (ImGui.Checkbox("Afficher cette Syncshell dans l'AutoDetect", ref _autoDetectDesiredVisibility))
{
- _ = ToggleAutoDetectAsync(desiredVisibility);
+ // Only change local desired state; sending is done via the validate button
}
}
_uiSharedService.DrawHelpText("Quand cette option est activée, le mot de passe devient inactif tant que la visibilité est maintenue.");
+ if (_autoDetectDesiredVisibility)
+ {
+ ImGuiHelpers.ScaledDummy(4);
+ ImGui.TextUnformatted("Options d'affichage AutoDetect");
+ ImGui.Separator();
+
+ // Recurring toggle first
+ ImGui.Checkbox("Affichage récurrent", ref _adRecurring);
+ _uiSharedService.DrawHelpText("Si activé, vous pouvez choisir les jours et une plage horaire récurrents. Si désactivé, seule la durée sera prise en compte.");
+
+ // Duration in hours (only when NOT recurring)
+ if (!_adRecurring)
+ {
+ ImGuiHelpers.ScaledDummy(4);
+ int duration = _adDurationHours;
+ ImGui.PushItemWidth(120 * ImGuiHelpers.GlobalScale);
+ if (ImGui.InputInt("Durée (heures)", ref duration))
+ {
+ _adDurationHours = Math.Clamp(duration, 1, 240);
+ }
+ ImGui.PopItemWidth();
+ _uiSharedService.DrawHelpText("Combien de temps la Syncshell doit rester visible, en heures.");
+ }
+
+ ImGuiHelpers.ScaledDummy(4);
+ if (_adRecurring)
+ {
+ ImGuiHelpers.ScaledDummy(4);
+ ImGui.TextUnformatted("Jours de la semaine actifs :");
+ string[] daysFr = new[] { "Lun", "Mar", "Mer", "Jeu", "Ven", "Sam", "Dim" };
+ for (int i = 0; i < 7; i++)
+ {
+ ImGui.SameLine(i == 0 ? 0 : 0);
+ bool v = _adWeekdays[i];
+ if (ImGui.Checkbox($"##adwd{i}", ref v)) _adWeekdays[i] = v;
+ ImGui.SameLine();
+ ImGui.TextUnformatted(daysFr[i]);
+ if (i < 6) ImGui.SameLine();
+ }
+ ImGui.NewLine();
+ _uiSharedService.DrawHelpText("Sélectionnez les jours où l'affichage est autorisé (ex: jeudi et dimanche).");
+
+ ImGuiHelpers.ScaledDummy(4);
+ ImGui.TextUnformatted("Plage horaire (heure locale Europe/Paris) :");
+ ImGui.PushItemWidth(60 * ImGuiHelpers.GlobalScale);
+ ImGui.InputInt("Début heure", ref _adStartHour); ImGui.SameLine();
+ ImGui.InputInt("min", ref _adStartMinute);
+ _adStartHour = Math.Clamp(_adStartHour, 0, 23);
+ _adStartMinute = Math.Clamp(_adStartMinute, 0, 59);
+ ImGui.SameLine();
+ ImGui.TextUnformatted("→"); ImGui.SameLine();
+ ImGui.InputInt("Fin heure", ref _adEndHour); ImGui.SameLine();
+ ImGui.InputInt("min ", ref _adEndMinute);
+ _adEndHour = Math.Clamp(_adEndHour, 0, 23);
+ _adEndMinute = Math.Clamp(_adEndMinute, 0, 59);
+ ImGui.PopItemWidth();
+ _uiSharedService.DrawHelpText("Exemple : de 21h00 à 23h00. Le fuseau utilisé est Europe/Paris (avec changements été/hiver).");
+ }
+ }
+
if (_autoDetectPasswordDisabled && _autoDetectVisible)
{
UiSharedService.ColorTextWrapped("Le mot de passe est actuellement désactivé pendant la visibilité AutoDetect.", ImGuiColors.DalamudYellow);
}
ImGuiHelpers.ScaledDummy(6);
- if (ImGui.Button("Recharger l'état"))
+ using (ImRaii.Disabled(_autoDetectToggleInFlight || _autoDetectStateLoading))
{
- _autoDetectStateLoading = true;
- _ = EnsureAutoDetectStateAsync(true);
+ if (ImGui.Button("Valider et envoyer"))
+ {
+ _ = SubmitAutoDetectAsync();
+ }
+ ImGui.SameLine();
+ if (ImGui.Button("Recharger l'état"))
+ {
+ _autoDetectStateLoading = true;
+ _ = EnsureAutoDetectStateAsync(true);
+ }
}
}
@@ -580,6 +661,67 @@ public class SyncshellAdminUI : WindowMediatorSubscriberBase
}
}
+ private async Task SubmitAutoDetectAsync()
+ {
+ if (_autoDetectToggleInFlight)
+ {
+ return;
+ }
+
+ _autoDetectToggleInFlight = true;
+ _autoDetectMessage = null;
+
+ try
+ {
+ // Duration always used when visible
+ int? duration = _autoDetectDesiredVisibility ? _adDurationHours : null;
+
+ // Scheduling fields only if recurring is enabled
+ int[]? weekdaysArr = null;
+ TimeSpan? start = null;
+ TimeSpan? end = null;
+ string? tz = null;
+ if (_autoDetectDesiredVisibility && _adRecurring)
+ {
+ List weekdays = new();
+ for (int i = 0; i < 7; i++) if (_adWeekdays[i]) weekdays.Add(i);
+ weekdaysArr = weekdays.Count > 0 ? weekdays.ToArray() : Array.Empty();
+ start = new TimeSpan(_adStartHour, _adStartMinute, 0);
+ end = new TimeSpan(_adEndHour, _adEndMinute, 0);
+ tz = AutoDetectTimeZone;
+ }
+
+ var ok = await _syncshellDiscoveryService.SetVisibilityAsync(
+ GroupFullInfo.GID,
+ _autoDetectDesiredVisibility,
+ duration,
+ weekdaysArr,
+ start,
+ end,
+ tz,
+ CancellationToken.None).ConfigureAwait(false);
+
+ if (!ok)
+ {
+ _autoDetectMessage = "Impossible d'envoyer les paramètres AutoDetect.";
+ return;
+ }
+
+ await EnsureAutoDetectStateAsync(true).ConfigureAwait(false);
+ _autoDetectMessage = _autoDetectDesiredVisibility
+ ? "Paramètres AutoDetect envoyés. La Syncshell sera visible selon le planning défini."
+ : "La Syncshell n'est plus visible dans AutoDetect.";
+ }
+ catch (Exception ex)
+ {
+ _autoDetectMessage = $"Erreur lors de l'envoi des paramètres AutoDetect : {ex.Message}";
+ }
+ finally
+ {
+ _autoDetectToggleInFlight = false;
+ }
+ }
+
private void ApplyAutoDetectState(bool visible, bool passwordDisabled, bool fromServer)
{
_autoDetectVisible = visible;
diff --git a/MareSynchronos/UI/UISharedService.cs b/MareSynchronos/UI/UISharedService.cs
index d2a7456..8ecbc77 100644
--- a/MareSynchronos/UI/UISharedService.cs
+++ b/MareSynchronos/UI/UISharedService.cs
@@ -541,6 +541,100 @@ public partial class UiSharedService : DisposableMediatorSubscriberBase
return result;
}
+
+ public bool IconButtonCentered(FontAwesomeIcon icon, float? height = null, float xOffset = 0f, float yOffset = 0f, bool square = false)
+ {
+ string text = icon.ToIconString();
+
+ ImGui.PushID($"centered-{text}");
+ Vector2 glyphSize;
+ using (IconFont.Push())
+ glyphSize = ImGui.CalcTextSize(text);
+ ImDrawListPtr drawList = ImGui.GetWindowDrawList();
+ Vector2 cursorScreenPos = ImGui.GetCursorScreenPos();
+ float frameHeight = height ?? ImGui.GetFrameHeight();
+ float buttonWidth = square ? frameHeight : glyphSize.X + ImGui.GetStyle().FramePadding.X * 2f;
+ using var hoverColor = ImRaii.PushColor(ImGuiCol.ButtonHovered, AccentHoverColor);
+ using var activeColor = ImRaii.PushColor(ImGuiCol.ButtonActive, AccentActiveColor);
+ bool clicked = ImGui.Button(string.Empty, new Vector2(buttonWidth, frameHeight));
+ Vector2 pos = new Vector2(
+ cursorScreenPos.X + (buttonWidth - glyphSize.X) / 2f + xOffset,
+ cursorScreenPos.Y + frameHeight / 2f - glyphSize.Y / 2f + yOffset);
+ using (IconFont.Push())
+ drawList.AddText(pos, ImGui.GetColorU32(ImGuiCol.Text), text);
+ ImGui.PopID();
+
+ return clicked;
+ }
+ public bool IconPauseButtonCentered(float? height = null)
+ {
+ ImGui.PushID("centered-pause-custom");
+ Vector2 glyphSize;
+ using (IconFont.Push())
+ glyphSize = ImGui.CalcTextSize(FontAwesomeIcon.Pause.ToIconString());
+ float frameHeight = height ?? ImGui.GetFrameHeight();
+ float buttonWidth = glyphSize.X + ImGui.GetStyle().FramePadding.X * 2f;
+
+ using var hoverColor = ImRaii.PushColor(ImGuiCol.ButtonHovered, AccentHoverColor);
+ using var activeColor = ImRaii.PushColor(ImGuiCol.ButtonActive, AccentActiveColor);
+
+ var drawList = ImGui.GetWindowDrawList();
+ var buttonTopLeft = ImGui.GetCursorScreenPos();
+ bool clicked = ImGui.Button(string.Empty, new Vector2(buttonWidth, frameHeight));
+
+ var textColor = ImGui.GetColorU32(ImGuiCol.Text);
+
+ float h = frameHeight * 0.55f; // bar height
+ float w = MathF.Max(1f, frameHeight * 0.16f); // bar width
+ float gap = MathF.Max(1f, w * 0.9f); // gap between bars
+ float total = 2f * w + gap;
+
+ float startX = buttonTopLeft.X + (buttonWidth - total) / 2f;
+ float startY = buttonTopLeft.Y + (frameHeight - h) / 2f;
+ float rounding = w * 0.35f;
+
+ drawList.AddRectFilled(new Vector2(startX, startY), new Vector2(startX + w, startY + h), textColor, rounding);
+ float rightX = startX + w + gap;
+ drawList.AddRectFilled(new Vector2(rightX, startY), new Vector2(rightX + w, startY + h), textColor, rounding);
+
+ ImGui.PopID();
+ return clicked;
+ }
+
+ public bool IconPlusButtonCentered(float? height = null)
+ {
+ ImGui.PushID("centered-plus-custom");
+ Vector2 glyphSize;
+ using (IconFont.Push())
+ glyphSize = ImGui.CalcTextSize(FontAwesomeIcon.Plus.ToIconString());
+ float frameHeight = height ?? ImGui.GetFrameHeight();
+ float buttonWidth = glyphSize.X + ImGui.GetStyle().FramePadding.X * 2f;
+
+ using var hoverColor = ImRaii.PushColor(ImGuiCol.ButtonHovered, AccentHoverColor);
+ using var activeColor = ImRaii.PushColor(ImGuiCol.ButtonActive, AccentActiveColor);
+
+ var drawList = ImGui.GetWindowDrawList();
+ var buttonTopLeft = ImGui.GetCursorScreenPos();
+ bool clicked = ImGui.Button(string.Empty, new Vector2(buttonWidth, frameHeight));
+
+ var color = ImGui.GetColorU32(ImGuiCol.Text);
+
+ float armThickness = MathF.Max(1f, frameHeight * 0.14f);
+ float crossSize = frameHeight * 0.55f; // total length of vertical/horizontal arms
+ float startX = buttonTopLeft.X + (buttonWidth - crossSize) / 2f;
+ float startY = buttonTopLeft.Y + (frameHeight - crossSize) / 2f;
+ float endX = startX + crossSize;
+ float endY = startY + crossSize;
+ float r = armThickness * 0.35f;
+
+ float hY1 = startY + (crossSize - armThickness) / 2f;
+ drawList.AddRectFilled(new Vector2(startX, hY1), new Vector2(endX, hY1 + armThickness), color, r);
+ float vX1 = startX + (crossSize - armThickness) / 2f;
+ drawList.AddRectFilled(new Vector2(vX1, startY), new Vector2(vX1 + armThickness, endY), color, r);
+
+ ImGui.PopID();
+ return clicked;
+ }
private bool IconTextButtonInternal(FontAwesomeIcon icon, string text, Vector4? defaultColor = null, float? width = null, bool useAccentHover = true)
{