mirror of
https://gitlab.acidiclight.dev/sociallydistant/sociallydistant.git
synced 2025-01-22 17:41:49 -05:00
Port floating windows and Message Dialogs
This commit is contained in:
parent
81ce9df56a
commit
e91de52dc0
27 changed files with 1150 additions and 18 deletions
|
@ -14,6 +14,7 @@ public struct LayoutRect
|
|||
|
||||
public Vector2 TopLeft => new Vector2(Left, Top);
|
||||
public Vector2 Size => new Vector2(Width, Height);
|
||||
public Vector2 Center => TopLeft + (Size / 2);
|
||||
|
||||
public LayoutRect(float left, float top, float width, float height)
|
||||
{
|
||||
|
|
|
@ -59,6 +59,33 @@ public class GeometryHelper : IFontStashRenderer2
|
|||
AddRoundedRectangle(rectangle, uniformRadius, uniformRadius, uniformRadius, uniformRadius, color, texture);
|
||||
}
|
||||
|
||||
public void AddQuadOutline(LayoutRect rectangle, float thickness, Color color, Texture2D? texture = null)
|
||||
{
|
||||
float smallerHalf = Math.Min(rectangle.Width, rectangle.Height) / 2;
|
||||
if (smallerHalf <= thickness)
|
||||
{
|
||||
AddQuad(rectangle, color, texture);
|
||||
return;
|
||||
}
|
||||
|
||||
var mesh = GetMeshBuilder(texture);
|
||||
|
||||
var tl = mesh.AddVertex(new Vector2(rectangle.Left, rectangle.Top), color);
|
||||
var tr = mesh.AddVertex(new Vector2(rectangle.Right, rectangle.Top), color);
|
||||
var bl = mesh.AddVertex(new Vector2(rectangle.Left, rectangle.Bottom), color);
|
||||
var br = mesh.AddVertex(new Vector2(rectangle.Right, rectangle.Bottom), color);
|
||||
|
||||
var tlInner = mesh.AddVertex(new Vector2(rectangle.Left + thickness, rectangle.Top + thickness), color);
|
||||
var trInner = mesh.AddVertex(new Vector2(rectangle.Right - thickness, rectangle.Top + thickness), color);
|
||||
var blInner = mesh.AddVertex(new Vector2(rectangle.Left + thickness, rectangle.Bottom - thickness), color);
|
||||
var brInner = mesh.AddVertex(new Vector2(rectangle.Right - thickness, rectangle.Bottom - thickness), color);
|
||||
|
||||
mesh.AddQuad(tl, tr, tlInner, trInner);
|
||||
mesh.AddQuad(tl, tlInner, bl, blInner);
|
||||
mesh.AddQuad(trInner, tr, brInner, br);
|
||||
mesh.AddQuad(blInner, brInner, bl, br);
|
||||
}
|
||||
|
||||
public void AddQuad(LayoutRect rectangle, Color color, Texture2D? texture = null)
|
||||
{
|
||||
var mesh = GetMeshBuilder(texture);
|
||||
|
|
|
@ -7,10 +7,13 @@ namespace AcidicGUI.Widgets;
|
|||
public sealed class Button :
|
||||
ContentWidget,
|
||||
IMouseEnterHandler,
|
||||
IMouseClickHandler,
|
||||
IMouseLeaveHandler
|
||||
{
|
||||
private bool hovered;
|
||||
|
||||
public event Action? Clicked;
|
||||
|
||||
public void OnMouseEnter(MouseMoveEvent e)
|
||||
{
|
||||
hovered = true;
|
||||
|
@ -30,4 +33,13 @@ public sealed class Button :
|
|||
geometry.AddQuad(ContentArea, Color.Red);
|
||||
}
|
||||
}
|
||||
|
||||
public void OnMouseClick(MouseButtonEvent e)
|
||||
{
|
||||
if (e.Button != MouseButton.Left)
|
||||
return;
|
||||
|
||||
e.Handle();
|
||||
Clicked?.Invoke();
|
||||
}
|
||||
}
|
|
@ -6,7 +6,7 @@ namespace AcidicGUI.Widgets;
|
|||
|
||||
public sealed class Icon : Widget
|
||||
{
|
||||
private Color color = Color.White;
|
||||
private Color? color;
|
||||
private string iconString = string.Empty;
|
||||
private int iconSize = 24;
|
||||
|
||||
|
@ -30,7 +30,7 @@ public sealed class Icon : Widget
|
|||
}
|
||||
}
|
||||
|
||||
public Color Color
|
||||
public Color? Color
|
||||
{
|
||||
get => color;
|
||||
set
|
||||
|
@ -59,6 +59,6 @@ public sealed class Icon : Widget
|
|||
if (font == null)
|
||||
return;
|
||||
|
||||
font.Draw(geometry, new Vector2(x, y), color, iconString, iconSize);
|
||||
font.Draw(geometry, new Vector2(x, y), color ?? Microsoft.Xna.Framework.Color.White, iconString, iconSize);
|
||||
}
|
||||
}
|
6
src/AcidicGUI/Widgets/OverlayWidget.cs
Normal file
6
src/AcidicGUI/Widgets/OverlayWidget.cs
Normal file
|
@ -0,0 +1,6 @@
|
|||
namespace AcidicGUI.Widgets;
|
||||
|
||||
public sealed class OverlayWidget : ContainerWidget
|
||||
{
|
||||
|
||||
}
|
|
@ -124,9 +124,15 @@ public partial class Widget
|
|||
if (item.Parent != this.parent)
|
||||
throw new InvalidOperationException("The specified widget is not a member of this parent.");
|
||||
|
||||
bool wasSuccess = children.Remove(item);
|
||||
|
||||
if (wasSuccess)
|
||||
{
|
||||
item.parent = null;
|
||||
this.parent.InvalidateLayout();
|
||||
return children.Remove(item);
|
||||
}
|
||||
|
||||
return wasSuccess;
|
||||
}
|
||||
|
||||
public int Count => children.Count;
|
||||
|
|
|
@ -201,6 +201,9 @@ public partial class Widget
|
|||
|
||||
contentSize.X += margin.Horizontal;
|
||||
contentSize.Y += margin.Vertical;
|
||||
contentSize.X += padding.Horizontal;
|
||||
contentSize.Y += padding.Vertical;
|
||||
|
||||
|
||||
if (maximumSize.X > 0)
|
||||
{
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
#nullable enable
|
||||
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
|
||||
namespace SociallyDistant.Core.Shell.Common
|
||||
|
@ -9,7 +10,7 @@ namespace SociallyDistant.Core.Shell.Common
|
|||
{
|
||||
public string textIcon;
|
||||
public Texture2D? spriteIcon;
|
||||
public ShellColor iconColor;
|
||||
public Color? iconColor;
|
||||
|
||||
public static implicit operator CompositeIcon(string unicodeTextIcon)
|
||||
{
|
||||
|
@ -17,7 +18,7 @@ namespace SociallyDistant.Core.Shell.Common
|
|||
{
|
||||
textIcon = unicodeTextIcon,
|
||||
spriteIcon = null,
|
||||
iconColor = new ShellColor(1, 1, 1, 1)
|
||||
iconColor = null
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
using SociallyDistant.Core.Shell.Common;
|
||||
using SociallyDistant.Core.Shell.InfoPanel;
|
||||
using SociallyDistant.Core.Shell.Windowing;
|
||||
|
||||
namespace SociallyDistant.Core.Shell
|
||||
{
|
||||
|
@ -15,5 +16,7 @@ namespace SociallyDistant.Core.Shell
|
|||
IInfoPanelService InfoPanelService { get; }
|
||||
|
||||
Task ShowInfoDialog(string title, string message);
|
||||
|
||||
IMessageDialog CreateMessageDialog(string title);
|
||||
}
|
||||
}
|
|
@ -3,6 +3,7 @@ using Microsoft.Xna.Framework;
|
|||
|
||||
namespace SociallyDistant.Core.Shell.Windowing
|
||||
{
|
||||
[Obsolete("IFloatingGui is being replaced with IWindowWithClient<T>.")]
|
||||
public interface IFloatingGui : IWindow, IContentHolder
|
||||
{
|
||||
Vector2 MinimumSize { get; set; }
|
||||
|
|
|
@ -3,12 +3,18 @@ using SociallyDistant.Core.Shell.Common;
|
|||
|
||||
namespace SociallyDistant.Core.Shell.Windowing
|
||||
{
|
||||
public interface IWindowTab
|
||||
{
|
||||
bool Active { get; }
|
||||
string Title { get; }
|
||||
}
|
||||
|
||||
public interface IWindow : ICloseable
|
||||
{
|
||||
public event Action<IWindow>? WindowClosed;
|
||||
|
||||
WindowHints Hints { get; }
|
||||
IWorkspaceDefinition? Workspace { get; set; }
|
||||
IWorkspaceDefinition Workspace { get; }
|
||||
CompositeIcon Icon { get; set; }
|
||||
|
||||
bool IsActive { get; }
|
||||
|
|
|
@ -5,6 +5,5 @@
|
|||
IReadOnlyList < IWindow > WindowList { get; }
|
||||
|
||||
IFloatingGui CreateFloatingGui(string title);
|
||||
IMessageDialog CreateMessageDialog(string title);
|
||||
}
|
||||
}
|
30
src/SociallyDistant.Framework/UI/Common/DecorativeBlock.cs
Normal file
30
src/SociallyDistant.Framework/UI/Common/DecorativeBlock.cs
Normal file
|
@ -0,0 +1,30 @@
|
|||
using Microsoft.Xna.Framework;
|
||||
using AcidicGUI.Widgets;
|
||||
|
||||
namespace SociallyDistant.Core.UI.Common;
|
||||
|
||||
public sealed class DecorativeBlock : Widget
|
||||
{
|
||||
private Color? color;
|
||||
private bool opaque;
|
||||
|
||||
public bool Opaque
|
||||
{
|
||||
get => opaque;
|
||||
set
|
||||
{
|
||||
opaque = value;
|
||||
InvalidateGeometry();
|
||||
}
|
||||
}
|
||||
|
||||
public Color? BoxColor
|
||||
{
|
||||
get => color;
|
||||
set
|
||||
{
|
||||
color = value;
|
||||
InvalidateGeometry();
|
||||
}
|
||||
}
|
||||
}
|
36
src/SociallyDistant.Framework/UI/Common/TextButton.cs
Normal file
36
src/SociallyDistant.Framework/UI/Common/TextButton.cs
Normal file
|
@ -0,0 +1,36 @@
|
|||
using AcidicGUI.Layout;
|
||||
using AcidicGUI.Widgets;
|
||||
|
||||
namespace SociallyDistant.Core.UI.Common;
|
||||
|
||||
public class TextButton : Widget
|
||||
{
|
||||
private readonly Button button = new();
|
||||
private readonly TextWidget textWidget = new();
|
||||
|
||||
public Action? ClickCallback { get; set; }
|
||||
|
||||
public string Text
|
||||
{
|
||||
get => textWidget.Text;
|
||||
set => textWidget.Text = value;
|
||||
}
|
||||
|
||||
public TextButton()
|
||||
{
|
||||
Children.Add(button);
|
||||
button.Content = textWidget;
|
||||
|
||||
button.Margin = new Padding(15, 0);
|
||||
|
||||
textWidget.HorizontalAlignment = HorizontalAlignment.Center;
|
||||
textWidget.VerticalAlignment = VerticalAlignment.Middle;
|
||||
|
||||
button.Clicked += HandleClicked;
|
||||
}
|
||||
|
||||
private void HandleClicked()
|
||||
{
|
||||
ClickCallback?.Invoke();
|
||||
}
|
||||
}
|
|
@ -6,6 +6,8 @@ using AcidicGUI.Widgets;
|
|||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using SociallyDistant.Core.Modules;
|
||||
using SociallyDistant.Core.Shell.Windowing;
|
||||
using SociallyDistant.Core.UI.Common;
|
||||
|
||||
namespace SociallyDistant.Core.UI.VisualStyles;
|
||||
|
||||
|
@ -14,6 +16,15 @@ public class SociallyDistantVisualStyle : IVisualStyle
|
|||
private readonly IGameContext game;
|
||||
private readonly Color mainBackground = new Color(0x11, 0x13, 0x15);
|
||||
private readonly Color statusBarColor = new Color(0x01,0x22, 0x37, 0xff);
|
||||
private readonly Color accentPrimary = new Color(0x13, 0x85, 0xC3, 0xff);
|
||||
|
||||
// Tabs - Inactive
|
||||
private readonly Color tabInactiveBackgroundDefault = new Color(0x44, 0x44, 0x44, 191);
|
||||
private readonly Color tabInactiveBorderDefault = new Color(0x44, 0x44, 0x44);
|
||||
|
||||
// Tabs - Active
|
||||
private readonly Color tabActiveBackgroundDefault = new Color(0x16, 0x93, 0xD6, 190);
|
||||
private readonly Color tabActiveBorderDefault = new Color(0x16, 0x93, 0xD6);
|
||||
|
||||
private Font iconFont;
|
||||
private Font defaultFont = null!;
|
||||
|
@ -80,10 +91,77 @@ public class SociallyDistantVisualStyle : IVisualStyle
|
|||
}
|
||||
}
|
||||
|
||||
private void DrawWindowTab(Widget widget, IWindowTab tab, GeometryHelper geometry)
|
||||
{
|
||||
var thickness = 1;
|
||||
var background = tabInactiveBackgroundDefault;
|
||||
var border = tabInactiveBorderDefault;
|
||||
|
||||
if (tab.Active)
|
||||
{
|
||||
background = tabActiveBackgroundDefault;
|
||||
border = tabActiveBorderDefault;
|
||||
}
|
||||
|
||||
geometry.AddRoundedRectangle(
|
||||
widget.ContentArea,
|
||||
3,
|
||||
3,
|
||||
0,
|
||||
0,
|
||||
background
|
||||
);
|
||||
|
||||
geometry.AddRoundedRectangleOutline(
|
||||
widget.ContentArea,
|
||||
thickness,
|
||||
3,
|
||||
3,
|
||||
0,
|
||||
0,
|
||||
border
|
||||
);
|
||||
}
|
||||
|
||||
private void DrawDecorativeBlock(DecorativeBlock widget, GeometryHelper geometry)
|
||||
{
|
||||
var color = (widget.BoxColor ?? accentPrimary);
|
||||
|
||||
if (widget.Opaque)
|
||||
{
|
||||
geometry.AddRoundedRectangle(
|
||||
widget.ContentArea,
|
||||
3,
|
||||
color
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
geometry.AddRoundedRectangle(
|
||||
widget.ContentArea,
|
||||
3,
|
||||
color * 0.5f
|
||||
);
|
||||
|
||||
geometry.AddRoundedRectangleOutline(
|
||||
widget.ContentArea,
|
||||
2,
|
||||
3,
|
||||
color
|
||||
);
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public void DrawWidgetBackground(Widget widget, GeometryHelper geometryHelper)
|
||||
{
|
||||
if (widget is InputField inputField)
|
||||
DrawInputField(inputField, geometryHelper);
|
||||
else if (widget is IWindowTab tab)
|
||||
DrawWindowTab(widget, tab, geometryHelper);
|
||||
else if (widget is DecorativeBlock box)
|
||||
DrawDecorativeBlock(box, geometryHelper);
|
||||
else
|
||||
{
|
||||
WidgetBackgrounds background = widget.GetCustomProperty<WidgetBackgrounds>();
|
||||
|
@ -93,9 +171,18 @@ public class SociallyDistantVisualStyle : IVisualStyle
|
|||
case WidgetBackgrounds.StatusBar:
|
||||
DrawStatusBar(widget, geometryHelper);
|
||||
break;
|
||||
case WidgetBackgrounds.Overlay:
|
||||
geometryHelper.AddQuad(widget.ContentArea, mainBackground * 0.67f);
|
||||
break;
|
||||
case WidgetBackgrounds.Dock:
|
||||
DrawDock(widget, geometryHelper);
|
||||
break;
|
||||
case WidgetBackgrounds.WindowClient:
|
||||
geometryHelper.AddQuad(widget.ContentArea, mainBackground);
|
||||
break;
|
||||
case WidgetBackgrounds.WindowBorder:
|
||||
geometryHelper.AddQuadOutline(widget.ContentArea, 1, accentPrimary);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -139,6 +226,9 @@ public enum InputFieldStyle
|
|||
public enum WidgetBackgrounds
|
||||
{
|
||||
None,
|
||||
Overlay,
|
||||
StatusBar,
|
||||
Dock
|
||||
Dock,
|
||||
WindowClient,
|
||||
WindowBorder,
|
||||
}
|
55
src/SociallyDistant/UI/Common/CompositeIconWidget.cs
Normal file
55
src/SociallyDistant/UI/Common/CompositeIconWidget.cs
Normal file
|
@ -0,0 +1,55 @@
|
|||
using AcidicGUI.Widgets;
|
||||
using Microsoft.Xna.Framework;
|
||||
using SociallyDistant.Core.Shell.Common;
|
||||
|
||||
namespace SociallyDistant.UI.Common;
|
||||
|
||||
public class CompositeIconWidget : Widget
|
||||
{
|
||||
private readonly Image image = new();
|
||||
private readonly Icon textIcon = new();
|
||||
|
||||
private CompositeIcon compositeIcon;
|
||||
private int iconSize = 24;
|
||||
|
||||
public int IconSize
|
||||
{
|
||||
get => iconSize;
|
||||
set
|
||||
{
|
||||
iconSize = value;
|
||||
UpdateIconSize();
|
||||
}
|
||||
}
|
||||
|
||||
public CompositeIcon Icon
|
||||
{
|
||||
get => compositeIcon;
|
||||
set
|
||||
{
|
||||
compositeIcon = value;
|
||||
UpdateIcon();
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateIconSize()
|
||||
{
|
||||
image.MaximumSize = new Vector2(iconSize, iconSize);
|
||||
image.MinimumSize = image.MaximumSize;
|
||||
textIcon.IconSize = iconSize;
|
||||
}
|
||||
|
||||
public CompositeIconWidget()
|
||||
{
|
||||
Children.Add(image);
|
||||
Children.Add(textIcon);
|
||||
}
|
||||
|
||||
private void UpdateIcon()
|
||||
{
|
||||
image.Texture = compositeIcon.spriteIcon;
|
||||
textIcon.IconString = compositeIcon.textIcon;
|
||||
|
||||
textIcon.Color = compositeIcon.iconColor;
|
||||
}
|
||||
}
|
46
src/SociallyDistant/UI/Common/InfoBox.cs
Normal file
46
src/SociallyDistant/UI/Common/InfoBox.cs
Normal file
|
@ -0,0 +1,46 @@
|
|||
using AcidicGUI.Layout;
|
||||
using AcidicGUI.Widgets;
|
||||
using Microsoft.Xna.Framework;
|
||||
using SociallyDistant.Core.UI.Common;
|
||||
|
||||
namespace SociallyDistant.UI.Common;
|
||||
|
||||
public sealed class InfoBox : Widget
|
||||
{
|
||||
private readonly StackPanel root = new();
|
||||
private readonly DecorativeBlock decorativeBlock = new();
|
||||
private readonly StackPanel contentStack = new();
|
||||
private readonly TextWidget title = new();
|
||||
private readonly Box contentBox = new();
|
||||
|
||||
public string TitleText
|
||||
{
|
||||
get => title.Text;
|
||||
set => title.Text = value;
|
||||
}
|
||||
|
||||
public Widget? Content
|
||||
{
|
||||
get => contentBox.Content;
|
||||
set => contentBox.Content = value;
|
||||
}
|
||||
|
||||
public InfoBox()
|
||||
{
|
||||
Children.Add(root);
|
||||
root.ChildWidgets.Add(decorativeBlock);
|
||||
root.ChildWidgets.Add(contentStack);
|
||||
contentStack.ChildWidgets.Add(title);
|
||||
contentStack.ChildWidgets.Add(contentBox);
|
||||
|
||||
root.Padding = 3;
|
||||
root.Spacing = 3;
|
||||
root.Direction = Direction.Horizontal;
|
||||
|
||||
contentStack.Spacing = 3;
|
||||
contentStack.Padding = new Padding(0, 3);
|
||||
contentStack.Direction = Direction.Vertical;
|
||||
|
||||
decorativeBlock.MinimumSize = new Vector2(18, 0);
|
||||
}
|
||||
}
|
219
src/SociallyDistant/UI/Common/MessageDalog.cs
Normal file
219
src/SociallyDistant/UI/Common/MessageDalog.cs
Normal file
|
@ -0,0 +1,219 @@
|
|||
using System.Collections;
|
||||
using AcidicGUI.Layout;
|
||||
using AcidicGUI.Widgets;
|
||||
using SociallyDistant.Core.Shell;
|
||||
using SociallyDistant.Core.Shell.Common;
|
||||
using SociallyDistant.Core.Shell.Windowing;
|
||||
using SociallyDistant.Core.UI.Common;
|
||||
using SociallyDistant.UI.Windowing;
|
||||
|
||||
namespace SociallyDistant.UI.Common;
|
||||
|
||||
public sealed class MessageDialog : IMessageDialog
|
||||
{
|
||||
private readonly Window window;
|
||||
private readonly InfoBox infoBox = new();
|
||||
private readonly StackPanel contentStack = new();
|
||||
private readonly TextWidget message = new();
|
||||
private readonly WrapPanel buttonsPanel = new();
|
||||
private readonly ButtonList buttonList;
|
||||
private readonly List<TextButton> views = new();
|
||||
|
||||
private MessageDialogResult dismissResult = MessageDialogResult.Cancel;
|
||||
|
||||
public MessageDialog(Window window)
|
||||
{
|
||||
buttonList = new ButtonList(this);
|
||||
|
||||
this.window = window;
|
||||
this.window.SetClient(infoBox);
|
||||
|
||||
infoBox.Content = contentStack;
|
||||
contentStack.ChildWidgets.Add(message);
|
||||
contentStack.ChildWidgets.Add(buttonsPanel);
|
||||
|
||||
contentStack.Spacing = 6;
|
||||
buttonsPanel.SpacingX = 3;
|
||||
buttonsPanel.SpacingY = 3;
|
||||
buttonsPanel.Direction = Direction.Horizontal;
|
||||
}
|
||||
|
||||
public bool CanClose
|
||||
{
|
||||
get => window.CanClose;
|
||||
set => window.CanClose = value;
|
||||
}
|
||||
|
||||
public void Close()
|
||||
{
|
||||
DismissCallback?.Invoke(dismissResult);
|
||||
window.ForceClose();
|
||||
}
|
||||
|
||||
public void ForceClose()
|
||||
{
|
||||
Close();
|
||||
}
|
||||
|
||||
public event Action<IWindow>? WindowClosed
|
||||
{
|
||||
add
|
||||
{
|
||||
window.WindowClosed += value;
|
||||
}
|
||||
remove
|
||||
{
|
||||
window.WindowClosed -= value;
|
||||
}
|
||||
}
|
||||
|
||||
public WindowHints Hints => window.Hints;
|
||||
public IWorkspaceDefinition Workspace => window.Workspace;
|
||||
|
||||
public CompositeIcon Icon
|
||||
{
|
||||
get => window.Icon;
|
||||
set => window.Icon = value;
|
||||
}
|
||||
|
||||
public bool IsActive => window.IsActive;
|
||||
public IWorkspaceDefinition CreateWindowOverlay()
|
||||
{
|
||||
return window.CreateWindowOverlay();
|
||||
}
|
||||
|
||||
public void SetWindowHints(WindowHints hints)
|
||||
{
|
||||
window.SetWindowHints(hints);
|
||||
}
|
||||
|
||||
public string Title
|
||||
{
|
||||
get => infoBox.TitleText;
|
||||
set => infoBox.TitleText = value;
|
||||
}
|
||||
|
||||
public string Message
|
||||
{
|
||||
get => message.Text;
|
||||
set => message.Text = value;
|
||||
}
|
||||
public CommonColor Color { get; set; }
|
||||
public MessageBoxType MessageType { get; set; }
|
||||
public IList<MessageBoxButtonData> Buttons => buttonList;
|
||||
public Action<MessageDialogResult>? DismissCallback { get; set; }
|
||||
|
||||
private void Dismiss(MessageDialogResult result)
|
||||
{
|
||||
dismissResult = result;
|
||||
Close();
|
||||
}
|
||||
|
||||
private void RefreshButtons()
|
||||
{
|
||||
while (views.Count > buttonList.Count)
|
||||
{
|
||||
buttonsPanel.ChildWidgets.Remove(views[^1]);
|
||||
views.RemoveAt(views.Count-1);
|
||||
}
|
||||
|
||||
while (views.Count < buttonList.Count)
|
||||
{
|
||||
var button = new TextButton();
|
||||
views.Add(button);
|
||||
buttonsPanel.ChildWidgets.Add(button);
|
||||
}
|
||||
|
||||
for (var i = 0; i < buttonList.Count; i++)
|
||||
{
|
||||
var button = buttonList[i];
|
||||
var view = views[i];
|
||||
|
||||
view.Text = button.Text;
|
||||
view.ClickCallback = () =>
|
||||
{
|
||||
Dismiss(button.Result);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private class ButtonList : IList<MessageBoxButtonData>
|
||||
{
|
||||
private readonly MessageDialog dialog;
|
||||
private readonly List<MessageBoxButtonData> data = new();
|
||||
|
||||
public ButtonList(MessageDialog dialog)
|
||||
{
|
||||
this.dialog = dialog;
|
||||
}
|
||||
|
||||
public IEnumerator<MessageBoxButtonData> GetEnumerator()
|
||||
{
|
||||
return data.GetEnumerator();
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
|
||||
public void Add(MessageBoxButtonData item)
|
||||
{
|
||||
data.Add(item);
|
||||
dialog.RefreshButtons();
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
data.Clear();
|
||||
dialog.RefreshButtons();
|
||||
}
|
||||
|
||||
public bool Contains(MessageBoxButtonData item)
|
||||
{
|
||||
return data.Contains(item);
|
||||
}
|
||||
|
||||
public void CopyTo(MessageBoxButtonData[] array, int arrayIndex)
|
||||
{
|
||||
data.CopyTo(array, arrayIndex);
|
||||
}
|
||||
|
||||
public bool Remove(MessageBoxButtonData item)
|
||||
{
|
||||
bool result = data.Remove(item);
|
||||
if (result)
|
||||
dialog.RefreshButtons();
|
||||
return result;
|
||||
}
|
||||
|
||||
public int Count => data.Count;
|
||||
public bool IsReadOnly => false;
|
||||
public int IndexOf(MessageBoxButtonData item)
|
||||
{
|
||||
return data.IndexOf(item);
|
||||
}
|
||||
|
||||
public void Insert(int index, MessageBoxButtonData item)
|
||||
{
|
||||
data.Insert(index, item);
|
||||
dialog.RefreshButtons();
|
||||
}
|
||||
|
||||
public void RemoveAt(int index)
|
||||
{
|
||||
data.RemoveAt(index);
|
||||
dialog.RefreshButtons();
|
||||
}
|
||||
|
||||
public MessageBoxButtonData this[int index]
|
||||
{
|
||||
get => data[index];
|
||||
set
|
||||
{
|
||||
data[index] = value;
|
||||
dialog.RefreshButtons();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,19 +1,23 @@
|
|||
using System.Text;
|
||||
using AcidicGUI.CustomProperties;
|
||||
using AcidicGUI.Layout;
|
||||
using AcidicGUI.Widgets;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using Serilog;
|
||||
using SociallyDistant.Core.Core;
|
||||
using SociallyDistant.Core.Modules;
|
||||
using SociallyDistant.Core.OS.Devices;
|
||||
using SociallyDistant.Core.Shell;
|
||||
using SociallyDistant.Core.Shell.Common;
|
||||
using SociallyDistant.Core.Shell.InfoPanel;
|
||||
using SociallyDistant.Core.Shell.Windowing;
|
||||
using SociallyDistant.Core.UI;
|
||||
using SociallyDistant.Player;
|
||||
using SociallyDistant.UI.Common;
|
||||
using SociallyDistant.UI.InfoWidgets;
|
||||
using SociallyDistant.UI.Shell;
|
||||
using SociallyDistant.UI.Windowing;
|
||||
|
||||
namespace SociallyDistant.UI;
|
||||
|
||||
|
@ -25,7 +29,9 @@ public class GuiController :
|
|||
private readonly InfoPanelController infoPanel = new();
|
||||
private readonly FlexPanel mainPanel = new();
|
||||
private readonly StatusBar statusBar = new();
|
||||
private readonly OverlayWidget workArea = new();
|
||||
private readonly Box mainBox = new();
|
||||
private readonly FloatingWorkspace floatingWindowArea = new();
|
||||
private readonly GuiService guiService;
|
||||
private readonly PlayerManager playerManager;
|
||||
private readonly DesktopController desktopController;
|
||||
|
@ -48,14 +54,37 @@ public class GuiController :
|
|||
guiService.GuiRoot.TopLevels.Add(mainPanel);
|
||||
|
||||
mainPanel.ChildWidgets.Add(statusBar);
|
||||
mainPanel.ChildWidgets.Add(mainBox);
|
||||
mainPanel.ChildWidgets.Add(workArea);
|
||||
|
||||
mainBox.GetCustomProperties<FlexPanelProperties>().Mode = FlexMode.Proportional;
|
||||
workArea.ChildWidgets.Add(mainBox);
|
||||
workArea.ChildWidgets.Add(floatingWindowArea);
|
||||
|
||||
workArea.GetCustomProperties<FlexPanelProperties>().Mode = FlexMode.Proportional;
|
||||
}
|
||||
|
||||
public async Task ShowExceptionMessage(Exception ex)
|
||||
public Task ShowExceptionMessage(Exception ex)
|
||||
{
|
||||
// TODO
|
||||
var dialog = CreateMessageDialog("System Error");
|
||||
var completionSource = new TaskCompletionSource<MessageDialogResult>();
|
||||
|
||||
var messageBuilder = new StringBuilder();
|
||||
|
||||
messageBuilder.AppendLine("Socially Distant has encountered an unexpected problem.");
|
||||
messageBuilder.AppendLine();
|
||||
messageBuilder.AppendLine("<b>What happened?</b>");
|
||||
messageBuilder.AppendLine(
|
||||
$"An unhandled .NET runtime exception ({ex.GetType().FullName}) occurred. {ex.Message}");
|
||||
|
||||
messageBuilder.AppendLine();
|
||||
messageBuilder.AppendLine(
|
||||
"This generally happens as a result of a broken mod or a bug in the game. Details have been logged to the game's log.");
|
||||
|
||||
Log.Error(ex.ToString());
|
||||
|
||||
dialog.Message = messageBuilder.ToString();
|
||||
dialog.Buttons.Add("OK");
|
||||
|
||||
return completionSource.Task;
|
||||
}
|
||||
|
||||
private void OnGameModeChanged(GameMode gameMode)
|
||||
|
@ -76,9 +105,41 @@ public class GuiController :
|
|||
public INotificationManager NotificationManager => notificationManager;
|
||||
public IInfoPanelService InfoPanelService => infoPanel;
|
||||
|
||||
public async Task ShowInfoDialog(string title, string message)
|
||||
public Task ShowInfoDialog(string title, string message)
|
||||
{
|
||||
// TODO
|
||||
var completionSource = new TaskCompletionSource<MessageDialogResult>();
|
||||
var dialog = CreateMessageDialog(title);
|
||||
|
||||
dialog.Message = message;
|
||||
dialog.DismissCallback = completionSource.SetResult;
|
||||
dialog.Buttons.Add("OK");
|
||||
|
||||
return completionSource.Task;
|
||||
}
|
||||
|
||||
private OverlayWorkspace CreateOverlayWorkspace()
|
||||
{
|
||||
var workspace = new OverlayWorkspace();
|
||||
this.workArea.ChildWidgets.Add(workspace);
|
||||
return workspace;
|
||||
}
|
||||
|
||||
public IMessageDialog CreateMessageDialog(string title)
|
||||
{
|
||||
var overlay = CreateOverlayWorkspace();
|
||||
var window = overlay.CreateWindow(title);
|
||||
var dialog = new MessageDialog(window);
|
||||
|
||||
dialog.Title = title;
|
||||
|
||||
dialog.WindowClosed += HandleDialogClosed;
|
||||
|
||||
return dialog;
|
||||
|
||||
void HandleDialogClosed(IWindow obj)
|
||||
{
|
||||
workArea.ChildWidgets.Remove(overlay);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
138
src/SociallyDistant/UI/Windowing/FloatingWorkspace.cs
Normal file
138
src/SociallyDistant/UI/Windowing/FloatingWorkspace.cs
Normal file
|
@ -0,0 +1,138 @@
|
|||
using AcidicGUI;
|
||||
using AcidicGUI.CustomProperties;
|
||||
using AcidicGUI.Layout;
|
||||
using AcidicGUI.Widgets;
|
||||
using Microsoft.Xna.Framework;
|
||||
using SociallyDistant.Core.Shell.Windowing;
|
||||
using SociallyDistant.Core.UI.VisualStyles;
|
||||
|
||||
namespace SociallyDistant.UI.Windowing;
|
||||
|
||||
public class OverlayWorkspace :
|
||||
Widget,
|
||||
IClientWorkspaceDefinition<Window, Widget?>
|
||||
{
|
||||
private readonly FloatingWorkspace workspace = new();
|
||||
|
||||
public OverlayWorkspace()
|
||||
{
|
||||
Children.Add(workspace);
|
||||
this.SetCustomProperty(WidgetBackgrounds.Overlay);
|
||||
}
|
||||
|
||||
public Window CreateWindow(string title, Widget? client = default)
|
||||
{
|
||||
return workspace.CreateWindow(title, client);
|
||||
}
|
||||
|
||||
public IReadOnlyList<IWindow> WindowList => workspace.WindowList;
|
||||
public IFloatingGui CreateFloatingGui(string title)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
public class FloatingWorkspace :
|
||||
Widget,
|
||||
IClientWorkspaceDefinition<Window, Widget?>,
|
||||
INotifyCloseWorkspace
|
||||
{
|
||||
private readonly List<WindowBase> windows = new();
|
||||
|
||||
public FloatingWorkspace()
|
||||
{
|
||||
LayoutRoot = this;
|
||||
}
|
||||
|
||||
public Window CreateWindow(string title, Widget? client = default)
|
||||
{
|
||||
var win = new Window(this);
|
||||
|
||||
win.SetClient(client);
|
||||
|
||||
this.Children.Add(win);
|
||||
windows.Add(win);
|
||||
|
||||
return win;
|
||||
}
|
||||
|
||||
public void OnCloseWindow(WindowBase window)
|
||||
{
|
||||
windows.Remove(window);
|
||||
Children.Remove(window);
|
||||
}
|
||||
|
||||
public IReadOnlyList<IWindow> WindowList => windows;
|
||||
public IFloatingGui CreateFloatingGui(string title)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public IMessageDialog CreateMessageDialog(string title)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
protected override Vector2 GetContentSize(Vector2 availableSize)
|
||||
{
|
||||
return availableSize;
|
||||
}
|
||||
|
||||
protected override void ArrangeChildren(IGuiContext context, LayoutRect availableSpace)
|
||||
{
|
||||
foreach (Widget win in Children)
|
||||
{
|
||||
var childSize = win.GetCachedContentSize(availableSpace.Size);
|
||||
var windowSettings = win.GetCustomProperties<WindowSettings>();
|
||||
|
||||
if (windowSettings.Maximized)
|
||||
{
|
||||
win.UpdateLayout(context, availableSpace);
|
||||
}
|
||||
else
|
||||
{
|
||||
var positionInWorkspace = availableSpace.Center + windowSettings.Position;
|
||||
var layoutPosition = positionInWorkspace - (childSize / 2);
|
||||
|
||||
win.UpdateLayout(context, new LayoutRect(
|
||||
layoutPosition.X,
|
||||
layoutPosition.Y,
|
||||
childSize.X,
|
||||
childSize.Y
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class WindowSettings : CustomPropertyObject
|
||||
{
|
||||
private Vector2 position;
|
||||
private bool maximized;
|
||||
|
||||
public bool Maximized
|
||||
{
|
||||
get => maximized;
|
||||
set
|
||||
{
|
||||
maximized = value;
|
||||
Widget.InvalidateLayout();
|
||||
}
|
||||
}
|
||||
|
||||
public Vector2 Position
|
||||
{
|
||||
get => position;
|
||||
set
|
||||
{
|
||||
position = value;
|
||||
Widget.InvalidateLayout();
|
||||
}
|
||||
}
|
||||
|
||||
public WindowSettings(Widget owner) : base(owner)
|
||||
{
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
using AcidicGUI.Widgets;
|
||||
using SociallyDistant.Core.Shell.Windowing;
|
||||
|
||||
namespace SociallyDistant.UI.Windowing;
|
||||
|
||||
internal interface INotifyCloseWorkspace : IWorkspaceDefinition
|
||||
{
|
||||
void OnCloseWindow(WindowBase window);
|
||||
}
|
23
src/SociallyDistant/UI/Windowing/Window.cs
Normal file
23
src/SociallyDistant/UI/Windowing/Window.cs
Normal file
|
@ -0,0 +1,23 @@
|
|||
using AcidicGUI.Widgets;
|
||||
using SociallyDistant.Core.Shell.Windowing;
|
||||
|
||||
namespace SociallyDistant.UI.Windowing;
|
||||
|
||||
public sealed class Window :
|
||||
WindowBase,
|
||||
IWindowWithClient<Widget?>
|
||||
{
|
||||
private readonly ITabDefinition tab;
|
||||
|
||||
internal Window(INotifyCloseWorkspace workspace) : base(workspace)
|
||||
{
|
||||
tab = Tabs.CreateTab();
|
||||
tab.Active = true;
|
||||
}
|
||||
|
||||
public Widget? Client => Decorations.Client;
|
||||
public void SetClient(Widget? newClient)
|
||||
{
|
||||
Decorations.Client = newClient;
|
||||
}
|
||||
}
|
58
src/SociallyDistant/UI/Windowing/WindowBase.cs
Normal file
58
src/SociallyDistant/UI/Windowing/WindowBase.cs
Normal file
|
@ -0,0 +1,58 @@
|
|||
using AcidicGUI.Widgets;
|
||||
using SociallyDistant.Core.Shell.Common;
|
||||
using SociallyDistant.Core.Shell.Windowing;
|
||||
|
||||
namespace SociallyDistant.UI.Windowing;
|
||||
|
||||
public abstract class WindowBase :
|
||||
Widget,
|
||||
IWindow
|
||||
{
|
||||
private readonly INotifyCloseWorkspace workspace;
|
||||
private readonly WindowDecoration decoration;
|
||||
|
||||
public bool CanClose { get; set; }
|
||||
|
||||
protected WindowDecoration Decorations => decoration;
|
||||
protected WindowTabList Tabs => decoration.Tabs;
|
||||
|
||||
internal WindowBase(INotifyCloseWorkspace workspace)
|
||||
{
|
||||
decoration = new WindowDecoration(this);
|
||||
|
||||
this.workspace = workspace;
|
||||
|
||||
Children.Add(decoration);
|
||||
}
|
||||
|
||||
public void Close()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public void ForceClose()
|
||||
{
|
||||
workspace.OnCloseWindow(this);
|
||||
WindowClosed?.Invoke(this);
|
||||
}
|
||||
|
||||
public CompositeIcon Icon
|
||||
{
|
||||
get => decoration.Icon;
|
||||
set => decoration.Icon = value;
|
||||
}
|
||||
|
||||
public event Action<IWindow>? WindowClosed;
|
||||
public WindowHints Hints => decoration.Hints;
|
||||
public IWorkspaceDefinition Workspace => workspace;
|
||||
public bool IsActive => IsChildFocused;
|
||||
public IWorkspaceDefinition CreateWindowOverlay()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public void SetWindowHints(WindowHints hints)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
87
src/SociallyDistant/UI/Windowing/WindowDecoration.cs
Normal file
87
src/SociallyDistant/UI/Windowing/WindowDecoration.cs
Normal file
|
@ -0,0 +1,87 @@
|
|||
using AcidicGUI.CustomProperties;
|
||||
using AcidicGUI.Layout;
|
||||
using AcidicGUI.Widgets;
|
||||
using Microsoft.Xna.Framework;
|
||||
using SociallyDistant.Core.Shell;
|
||||
using SociallyDistant.Core.Shell.Common;
|
||||
using SociallyDistant.Core.Shell.Windowing;
|
||||
using SociallyDistant.Core.UI.VisualStyles;
|
||||
using SociallyDistant.UI.Common;
|
||||
|
||||
namespace SociallyDistant.UI.Windowing;
|
||||
|
||||
public class WindowDecoration : Widget
|
||||
{
|
||||
private readonly FlexPanel flexPanel = new();
|
||||
private readonly FlexPanel titleBar = new();
|
||||
private readonly Box borderBox = new();
|
||||
private readonly Box clientBox = new();
|
||||
private readonly WindowBase window;
|
||||
private readonly WindowDragSurface dragSurface;
|
||||
private readonly CompositeIconWidget titleIcon = new();
|
||||
private readonly WindowTabList tabList = new();
|
||||
|
||||
private WindowHints hints;
|
||||
|
||||
public WindowHints Hints => hints;
|
||||
|
||||
public CompositeIcon Icon
|
||||
{
|
||||
get => titleIcon.Icon;
|
||||
set => titleIcon.Icon = value;
|
||||
}
|
||||
|
||||
public Widget? Client
|
||||
{
|
||||
get => clientBox.Content;
|
||||
set => clientBox.Content = value;
|
||||
}
|
||||
|
||||
public WindowTabList Tabs => tabList;
|
||||
|
||||
public WindowDecoration(WindowBase window)
|
||||
{
|
||||
this.window = window;
|
||||
this.dragSurface = new WindowDragSurface((window));
|
||||
|
||||
Children.Add(flexPanel);
|
||||
|
||||
flexPanel.ChildWidgets.Add(dragSurface);
|
||||
flexPanel.ChildWidgets.Add(borderBox);
|
||||
|
||||
dragSurface.Content = titleBar;
|
||||
|
||||
titleBar.Direction = Direction.Horizontal;
|
||||
titleBar.Padding = new Padding(3, 0);
|
||||
titleBar.Spacing = 3;
|
||||
|
||||
titleBar.ChildWidgets.Add(titleIcon);
|
||||
titleBar.ChildWidgets.Add(tabList);
|
||||
|
||||
tabList.GetCustomProperties<FlexPanelProperties>().Mode = FlexMode.Proportional;
|
||||
|
||||
borderBox.Margin = 1;
|
||||
borderBox.Content = clientBox;
|
||||
|
||||
titleIcon.VerticalAlignment = VerticalAlignment.Bottom;
|
||||
titleIcon.Padding = new Padding(0, 3);
|
||||
|
||||
borderBox.SetCustomProperty(WidgetBackgrounds.WindowBorder);
|
||||
clientBox.SetCustomProperty(WidgetBackgrounds.WindowClient);
|
||||
clientBox.GetCustomProperties<FlexPanelProperties>().Mode = FlexMode.Proportional;
|
||||
|
||||
tabList.VerticalAlignment = VerticalAlignment.Bottom;
|
||||
|
||||
Icon = new CompositeIcon()
|
||||
{
|
||||
textIcon = MaterialIcons.Window
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public interface ITabDefinition
|
||||
{
|
||||
string Title { get; set; }
|
||||
bool Closeable { get; set; }
|
||||
bool Active { get; set; }
|
||||
}
|
57
src/SociallyDistant/UI/Windowing/WindowDragSurface.cs
Normal file
57
src/SociallyDistant/UI/Windowing/WindowDragSurface.cs
Normal file
|
@ -0,0 +1,57 @@
|
|||
using AcidicGUI.Events;
|
||||
using AcidicGUI.Widgets;
|
||||
using Microsoft.Xna.Framework;
|
||||
|
||||
namespace SociallyDistant.UI.Windowing;
|
||||
|
||||
public sealed class WindowDragSurface :
|
||||
ContentWidget,
|
||||
IDragStartHandler,
|
||||
IDragHandler,
|
||||
IDragEndHandler
|
||||
{
|
||||
private readonly WindowBase window;
|
||||
|
||||
private bool dragging;
|
||||
|
||||
public WindowDragSurface(WindowBase window)
|
||||
{
|
||||
MinimumSize = new Vector2(0, 24);
|
||||
this.window = window;
|
||||
}
|
||||
|
||||
public void OnDragStart(MouseButtonEvent e)
|
||||
{
|
||||
if (e.Button != MouseButton.Left)
|
||||
return;
|
||||
|
||||
e.RequestFocus();
|
||||
dragging = true;
|
||||
}
|
||||
|
||||
public void OnDrag(MouseButtonEvent e)
|
||||
{
|
||||
if (!dragging)
|
||||
return;
|
||||
|
||||
var settings = window.GetCustomProperties<WindowSettings>();
|
||||
|
||||
if (settings.Maximized)
|
||||
return;
|
||||
|
||||
settings.Position += e.Movement;
|
||||
|
||||
e.RequestFocus();
|
||||
}
|
||||
|
||||
public void OnDragEnd(MouseButtonEvent e)
|
||||
{
|
||||
if (!dragging)
|
||||
return;
|
||||
|
||||
if (e.Button != MouseButton.Left)
|
||||
return;
|
||||
|
||||
dragging = false;
|
||||
}
|
||||
}
|
49
src/SociallyDistant/UI/Windowing/WindowTab.cs
Normal file
49
src/SociallyDistant/UI/Windowing/WindowTab.cs
Normal file
|
@ -0,0 +1,49 @@
|
|||
using AcidicGUI.Layout;
|
||||
using AcidicGUI.Widgets;
|
||||
using SociallyDistant.Core.Shell;
|
||||
using SociallyDistant.Core.Shell.Windowing;
|
||||
|
||||
namespace SociallyDistant.UI.Windowing;
|
||||
|
||||
public sealed class WindowTab :
|
||||
Widget,
|
||||
IWindowTab
|
||||
{
|
||||
private readonly StackPanel stackPanel = new();
|
||||
private readonly TextWidget titleText = new();
|
||||
private readonly Button closeButton = new();
|
||||
private readonly Icon closeIcon = new();
|
||||
|
||||
private bool active = false;
|
||||
|
||||
public string Title
|
||||
{
|
||||
get => titleText.Text;
|
||||
set => titleText.Text = value;
|
||||
}
|
||||
|
||||
public bool Active
|
||||
{
|
||||
get => active;
|
||||
set
|
||||
{
|
||||
active = value;
|
||||
InvalidateGeometry();
|
||||
}
|
||||
}
|
||||
|
||||
public WindowTab()
|
||||
{
|
||||
Children.Add(stackPanel);
|
||||
stackPanel.ChildWidgets.Add(titleText);
|
||||
stackPanel.ChildWidgets.Add(closeButton);
|
||||
closeButton.Content = closeIcon;
|
||||
|
||||
closeIcon.IconSize = 18;
|
||||
closeIcon.IconString = MaterialIcons.Close;
|
||||
|
||||
stackPanel.Direction = Direction.Horizontal;
|
||||
stackPanel.Spacing = 3;
|
||||
stackPanel.Margin = 3;
|
||||
}
|
||||
}
|
109
src/SociallyDistant/UI/Windowing/WindowTabList.cs
Normal file
109
src/SociallyDistant/UI/Windowing/WindowTabList.cs
Normal file
|
@ -0,0 +1,109 @@
|
|||
using AcidicGUI.Layout;
|
||||
using AcidicGUI.Widgets;
|
||||
using SociallyDistant.Core.Shell.Windowing;
|
||||
|
||||
namespace SociallyDistant.UI.Windowing;
|
||||
|
||||
public sealed class WindowTabList : Widget
|
||||
{
|
||||
private readonly WrapPanel wrapPanel = new();
|
||||
private readonly List<WindowTab> views = new();
|
||||
private readonly List<Definition> definitions = new();
|
||||
|
||||
private bool showNewTab = false;
|
||||
|
||||
public WindowTabList()
|
||||
{
|
||||
Children.Add(wrapPanel);
|
||||
|
||||
wrapPanel.Direction = Direction.Horizontal;
|
||||
}
|
||||
|
||||
public ITabDefinition CreateTab()
|
||||
{
|
||||
var definition = new Definition(this);
|
||||
|
||||
definitions.Add(definition);
|
||||
|
||||
UpdateViews();
|
||||
|
||||
return definition;
|
||||
}
|
||||
|
||||
public void RemoveTab(ITabDefinition definition)
|
||||
{
|
||||
if (definition is not Definition actualDefinition)
|
||||
return;
|
||||
|
||||
definitions.Remove(actualDefinition);
|
||||
UpdateViews();
|
||||
}
|
||||
|
||||
private void UpdateViews()
|
||||
{
|
||||
while (views.Count > definitions.Count)
|
||||
{
|
||||
wrapPanel.ChildWidgets.Remove(views[^1]);
|
||||
views.RemoveAt(views.Count-1);
|
||||
}
|
||||
|
||||
while (views.Count < definitions.Count)
|
||||
{
|
||||
var view = new WindowTab();
|
||||
views.Add(view);
|
||||
wrapPanel.ChildWidgets.Add(view);
|
||||
}
|
||||
|
||||
for (var i = 0; i < definitions.Count; i++)
|
||||
{
|
||||
Definition definition = definitions[i];
|
||||
WindowTab view = views[i];
|
||||
|
||||
view.Title = definition.Title;
|
||||
view.Active = definition.Active;
|
||||
}
|
||||
}
|
||||
|
||||
internal class Definition : ITabDefinition
|
||||
{
|
||||
private readonly WindowTabList list;
|
||||
private string title = "Tab title";
|
||||
private bool closeable;
|
||||
private bool active;
|
||||
|
||||
public Definition(WindowTabList list)
|
||||
{
|
||||
this.list = list;
|
||||
}
|
||||
|
||||
public string Title
|
||||
{
|
||||
get => title;
|
||||
set
|
||||
{
|
||||
title = value;
|
||||
list.UpdateViews();
|
||||
}
|
||||
}
|
||||
|
||||
public bool Closeable
|
||||
{
|
||||
get => closeable;
|
||||
set
|
||||
{
|
||||
closeable = value;
|
||||
list.UpdateViews();
|
||||
}
|
||||
}
|
||||
|
||||
public bool Active
|
||||
{
|
||||
get => active;
|
||||
set
|
||||
{
|
||||
active = value;
|
||||
list.UpdateViews();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue