Fix even more platform-related issues

* Show more descriptive OS version info in neofetch

We now show the distribution name (i.e, "Arch Linux")
as well as the OS architecture and .NET version in
the "Host" string in neofetch.

This should theoretically also mean Windows versions
are displayed properly, but I don't have a local
Windows system to test.

Issue: #1
Signed-off-by: Ritchie Frodomar <alkalinethunder@gmail.com>

* Document new visual style system

* Use new style system for Progress Bars

* Allow custom widgets to more easily take advantage of the visual style system

* Add support for setting confirmations in toggles

Signed-off-by: Ritchie Frodomar <alkalinethunder@gmail.com>

* Fix background blur effect rendering solid black after fullscreen changes

Changing the game resolution requires throwing away old blur buffers and
generating new ones. This requires throwing away cached blur geometry on
all widgets, but we were not doing that - resulting in sampling a destroyed
texture. In MonoGame, the failure mode for this is sampling black.

Issue: #1
Signed-off-by: Ritchie Frodomar <alkalinethunder@gmail.com>

* Fix issue where toggling Fullscreen doesn't change window mode

We did not apply the XNA presentation parameters this whole time.
I feel stupid.

Issue: #1
Signed-off-by: Ritchie Frodomar <alkalinethunder@gmail.com>

* Prioritize Graphics category in System Settings

* Fix annoying ordering of resolutions in Graphics Settings

* Fix crash on lower screen resolutions when clicking on Terminal

Mouse coordinates were not being scaled from screenspace into
canvas space, resulting in a crash when clicking on the Terminal.

Also add documentation for internal VirtualScreen
implementation, and define dynamic properties for
CanvasWidth and CanvasHeight in the UI system.

Issue: #13
Signed-off-by: Ritchie Frodomar <alkalinethunder@gmail.com>

* Remove old GuiService virtual screen and document IVirtualScreen

Whoever wrote that old code was high, fuck... wait a minute...

Signed-off-by: Ritchie Frodomar <alkalinethunder@gmail.com>

* Fix various errors after emergency rebase

* Remove Rider caches

* Attempt to enable Wayland support

* Add support for enabling/disabling Wayland support on Linux

* Move SettingsManager to GameApplication so settings can be loaded before the game engine fully boots.

* Test commit.

Signed-off-by: Ritchie Frodomar <alkalinethunder@gmail.com>

* Move Terminals to their own tile

* Add support for disabling background blur

* Testing the DCO acknowledgement job

Signed-off-by: Ritchie Frodomar <alkalinethunder@gmail.com>
This commit is contained in:
Ritchie Frodomar 2024-10-29 19:32:18 +00:00
parent 75c8b5432c
commit de73d0800b
37 changed files with 994 additions and 220 deletions

View file

@ -26,4 +26,8 @@ items:
- name: List Adapters - name: List Adapters
href: ui/list-adapters.md href: ui/list-adapters.md
- name: Optimizing UI for Performance - name: Optimizing UI for Performance
href: ui/optimization.md href: ui/optimization.md
- name: Advanced UI features
items:
- name: Visual Styles
href: ui/advanced/visual-styles.md

View file

@ -0,0 +1,212 @@
# Visual Styles
Visual Styles control how all widgets in the UI system look. They're responsible for determining both visual and layout attributes for each widget. While most game UI systems tend to allow direct control over how widgets look on an individual basis, Socially Distant (and AcidicGUI) **does not.**. This forces the user interface to maintain a consistent visual appearance across the entire game, to improve accessibility. All buttons should look like a button.
The visual style system is designed to allow custom widgets to take advantage of it. Therefore, the visual style system does take some learning.
## The `IVisualStyle` Interface
The `IVisualStyle` interface defines methods that must be implemented by all visual styles in order for core widgets to function. AcidicGUI comes with a built-in `FallbackVisualStyle` that gets used when no other option is available. `SociallyDistant.Framework` provides `SociallyDistantVisualStyle`, which implements the visual appearance of the in-game operating system.
## The `IVisual<TVisualProperties>` interface
This interface should be implemented by a widget that wants to be rendered using the visual style system. You use this interface to communicate important state about the widget being rendered. For example, when styling a progress bar, you will need to know its fill percentage. This is how the `IVisual` interface is used to implement that.
```csharp
public sealed class ProgressBar : Widget,
IVisual<ProgressBar.ProgressBarVisualProperties>
{
private ProgressBarVisualProperties visualProperties;
public ref readonly ProgressBarCisualProperties VisualProperties => ref visualProperties;
public float Value
{
get => visualProperties.FillPercentage;
set
{
visualProperties.FillPercentage = value;
InvalidateGeometry();
}
}
protected override void RebuildGeometry(GeometryHelper geometry)
{
geometry.DrawVisual<ProgressBar, ProgressBarVisualProperties>(this);
}
public struct ProgressBarVisualProperties
{
public float FillPercentage;
}
}
```
Note that `ProgressBar` itself does not implement any actual rendering code. It just provides a means of retrieving its fill percentage (and any other visual state) through the `VisualProperyies` property. Instead, we call the `DrawVisual()` extension method of `GeometryHelper`, which instructs the visual style system to paint the widget.
## The `IVisualRenderer<TVisualProperties>` Interface
This interface allows you to define how a widget should be rendered, given its visual properties. If implemented by a class implementing `IVisualStyle`, the visual style system will be able to render any widgets implementing `IVisual<TVisualProperties>` using this implementation.
You should delegate the implementation of the `Draw` method to a nested type within your visual style implementation, like this:
```csharp
public sealed class MyVisualStyle :
IVisualStyle,
IVisualRenderer<ProgressBar.ProgressBarVisualProperties>
{
private readonly ProgressBarStyle progressBarStyle = new();
`public void Draw(
Widget widget,
GeometryHelper geometry,
in LayoutRect contentRect,
ProgressBarVisualProperties properties
)
{
progressBarStyle.Draw(widget, geometry, in contentRect, properties);
}
public sealed class ProgressBarStyle :
IVisualRenderer<ProgressBar.ProgressBarVisualProperties>
{
public void Draw(
Widget widget,
GeometryHelper geometry,
in LayoutRect contentRect,
ProgressBarVisualProperties properties
)
{
var fillPercentage = properties.FillPercentage;
// Draw stuff!
}
}
}
```
Because C# does not have a way to name the `Draw` method based on what type of visual properties being used, delegating the rendering implementation like this allows you to more easily navigate the visual style's code. It also allows you to more easily communicate that certain style attributes only affect progress bars, and therefore only need to cause progress bars to repaint when changed.
## The `IGetLayoutProperties<TLayoutProperties>` Interface
Sometimes, widgets need to use properties from a visual style to determine their layout. For example, a progress bar's height is controlled by the visual style system. That's the role of this interface. When implemented by a visual style, the visual style system can query layout properties from your own visual style dynamically. If you don't implement this interface for a given widget's layout properties, the widget itself will define default values that it will not allow you to change directly.
Here's how you can change the height of all progress bars.
```csharp
public sealed class MyVisualStyle :
IVisualStyle,
IVisualRenderer<ProgressBar.ProgressBarVisualProperties>,
IGetLayoutProperties<ProgressBar.ProgressBarLayoutProperties>
{
private readonly ProgressBarStyle progressBarStyle = new();
public bool GetLayoutProperties(ref ProgressBar.ProgressBarLayoutProperties layoutProperties)
{
return progressBarStyle.GetLayoutProperties(ref layoutProperties);
}
`public void Draw(
Widget widget,
GeometryHelper geometry,
in LayoutRect contentRect,
ProgressBarVisualProperties properties
)
{
progressBarStyle.Draw(widget, geometry, in contentRect, properties);
}
public sealed class ProgressBarStyle :
IVisualRenderer<ProgressBar.ProgressBarVisualProperties>,
IGetLayoutProperties<ProgressBar.ProgressBarLayoutProperties>
{
public bool GetLayoutProperties(ref ProgressBar.ProgressBarLayoutProperties layoutProperties)
{
layoutProperties.ProgressBarHeight = 4;
return false;
}
public void Draw(
Widget widget,
GeometryHelper geometry,
in LayoutRect contentRect,
ProgressBarVisualProperties properties
)
{
var fillPercentage = properties.FillPercentage;
// Draw stuff!
}
}
}
```
Just like `IVisualRenderer`, you should delegate your implementation of the `GetLayoutProperties()` method.
Note that the `GetLayoutProperties` method returns `bool`. When a widget asks for layout properties, the value you return determines whether that widget should invalidate its layout. You should return `true` if the layout properties are dirty, and `false` otherwise. If they're static, always return `false` as all widgets get dirtied automatically when a visual style is loaded.
## The `UserStyle` class in `SociallyDistant.Framework`
This is an extremely simple base class for widget styles in Socially Distant. It handles the dirtiness of layout properties for you, and provides access to accessibility and UI settings set by the player.
You can adjust the above code example with `ProgressBarStyle` to make progress bar height dynamic.
```csharp
public sealed class ProgressBarStyle :
UserStyle,
IVisualRenderer<ProgressBar.ProgressBarVisualProperties>,
IGetLayoutProperties<ProgressBar.ProgressBarLayoutProperties>
{
public int ProgressBarHeight
{
get => progressBarHeight;
set
{
if (progressBarHeight == value)
return;
progressBarHeight = value;
SetDirty();
}
}
public bool GetLayoutProperties(ref ProgressBar.ProgressBarLayoutProperties layoutProperties)
{
layoutProperties.ProgressBarHeight = progressBarHeight;
return GetDirtyState();
}
public void Draw(
Widget widget,
GeometryHelper geometry,
in LayoutRect contentRect,
ProgressBarVisualProperties properties
)
{
var fillPercentage = properties.FillPercentage;
// Draw stuff!
}
}
```
## Accessing layout properties in a Widget
Any widget can access the layout properties of any type of widget, so long as the struct is public.
Here's how you can access the layout properties of a `ProgressBar`.
```csharp
public sealed class NotAProgressBar : Widget,
IUpdateLayoutProperties
{
private ProgressBar.ProgressBarLayoutProperties progressBarLayout;
public void UpdateLayoutProperties()
{
this.GetLayoutProperties(ref progressBarLayout);
}
}
```
The widget implements `IUpdateLayoutProperties`, which is called every frame for every widget that implements it, before any actual layout updates occur. Calling `this.GetLayoutProperties()` fills the given field with layout properties from the current visual style. If the visual style doesn't implement `IGetLayoutProperties<TLayoutProperties>` for the given struct type, then it will be filled with default values instead.
By using `GetLayoutProperties()` within the context of `UpdateLayoutProperties()`, any changes reported by the visual style system will automatically invalidate the widget allowing you to immediately apply the layout properties.
## Visual Style Overrides
Sometimes, you want a section of the UI to use a different visual style altogether. For example, in Socially Distant, a website may want to have a different look and feel than the in-game OS. This is where Visual Style Overrides come in.
Each widget has a `VisualStyleOverride` property you can set to an instance of a class implementing `IVisualStyle`. If `VisualStyleOverride` is not null, then this visual style will be used by the widget instead of the current global style of the game. This property is also inherited by child widgets, meaning overriding the style of a widget will affect every widget below it in the hierarchy.

View file

@ -0,0 +1,19 @@
namespace AcidicGUI.Common;
public sealed class StateChange<T>
{
public T CurrentValue { get; }
public T NewValue { get; }
public bool Canceled { get; private set; }
public StateChange(T currentValue, T newValue)
{
this.CurrentValue = currentValue;
this.NewValue = newValue;
}
public void Cancel()
{
Canceled = true;
}
}

View file

@ -23,17 +23,14 @@ public sealed class GuiManager : IFontFamilyProvider
private ButtonState rightShift; private ButtonState rightShift;
private ButtonState leftAlt; private ButtonState leftAlt;
private ButtonState rightAlt; private ButtonState rightAlt;
private int screenWidth;
private int screenHeight;
private IVisualStyle? visualStyleOverride; private bool isRendering;
private int screenWidth; private Widget? hoveredWidget;
private int screenHeight; private Widget? widgetBeingDragged;
private bool isRendering; private Widget? keyboardFocus;
private Widget? hoveredWidget; private MouseState? previousMouseState;
private Widget? widgetBeingDragged; private bool reachedFirstUpdate;
private Widget? keyboardFocus;
private MouseState? previousMouseState;
private bool reachedFirstUpdate;
public bool IsRendering => isRendering; public bool IsRendering => isRendering;
public IOrderedCollection<Widget> TopLevels => topLevels; public IOrderedCollection<Widget> TopLevels => topLevels;
@ -44,9 +41,22 @@ public sealed class GuiManager : IFontFamilyProvider
topLevels = new Widget.TopLevelCollection(this); topLevels = new Widget.TopLevelCollection(this);
renderer = new GuiRenderer(context); renderer = new GuiRenderer(context);
visualStyleOverride = globalStyle; StyleManager.SetVisualStyle(globalStyle);
} }
/// <summary>
/// Invalidates all layout and geometry data of the entire widget tree,
/// forcing a full layout update and geometry repaint. Use only when changing
/// graphics modes, as this is extremely expensive.
/// </summary>
public void ScheduleFullReRender()
{
foreach (Widget widget in TopLevels)
{
widget.InvalidateLayout();
}
}
/// <summary> /// <summary>
/// Invalidates the geometry of all interface elements recursively. Use this when changing a global UI setting that affects how all widgets render. /// Invalidates the geometry of all interface elements recursively. Use this when changing a global UI setting that affects how all widgets render.
/// Note: This is expensive. That should be obvious. So do not call it every frame. /// Note: This is expensive. That should be obvious. So do not call it every frame.
@ -198,6 +208,12 @@ public sealed class GuiManager : IFontFamilyProvider
public void UpdateLayout() public void UpdateLayout()
{ {
reachedFirstUpdate = true; reachedFirstUpdate = true;
foreach (Widget widget in CollapseChildren())
{
if (widget is IUpdateLayoutProperties updateLayoutProperties)
updateLayoutProperties.UpdateLayoutProperties();
}
var mustRebuildLayout = false; var mustRebuildLayout = false;
var tolerance = 0.001f; var tolerance = 0.001f;
@ -229,12 +245,13 @@ public sealed class GuiManager : IFontFamilyProvider
} }
} }
[Obsolete("Please use the new StyleManager API instead.")]
public IVisualStyle GetVisualStyle() public IVisualStyle GetVisualStyle()
{ {
if (fallbackVisualStyle.FallbackFont == null) if (fallbackVisualStyle.FallbackFont == null)
fallbackVisualStyle.FallbackFont = context.GetFallbackFont(); fallbackVisualStyle.FallbackFont = context.GetFallbackFont();
return visualStyleOverride ?? fallbackVisualStyle; return StyleManager.ActiveStyle ?? fallbackVisualStyle;
} }
public void Render() public void Render()

View file

@ -6,9 +6,11 @@ using Microsoft.Xna.Framework;
namespace AcidicGUI.VisualStyles; namespace AcidicGUI.VisualStyles;
internal sealed class FallbackVisualStyle : IVisualStyle internal sealed class FallbackVisualStyle :
IVisualStyle,
IGetLayoutProperties<ProgressBar.ProgressBarLayoutProperties>,
IVisualRenderer<ProgressBar.ProgressBarVisualProperties>
{ {
public int ProgressBarHeight => 16;
public int SliderThickness => 12; public int SliderThickness => 12;
public Point ToggleSize => new Point(18, 18); public Point ToggleSize => new Point(18, 18);
public Point SwitchSize => ToggleSize; public Point SwitchSize => ToggleSize;
@ -119,18 +121,6 @@ internal sealed class FallbackVisualStyle : IVisualStyle
geometry.AddQuad(widget.ContentArea, Color.Gray * 0.25f); geometry.AddQuad(widget.ContentArea, Color.Gray * 0.25f);
} }
public void DrawProgressBar(ProgressBar widget, GeometryHelper geometry, float fillPercentage)
{
int width = widget.ContentArea.Width;
int fillWidth = (int) MathHelper.Clamp(MathHelper.Lerp(0, width, fillPercentage), 0, width);
var background = widget.ContentArea;
var fill = new LayoutRect(background.Left, background.Top, fillWidth, background.Height);
geometry.AddQuad(background, Color.Gray);
geometry.AddQuad(fill, Color.Red);
}
public void DrawSlider( public void DrawSlider(
Slider widget, Slider widget,
GeometryHelper geometry, GeometryHelper geometry,
@ -153,4 +143,27 @@ internal sealed class FallbackVisualStyle : IVisualStyle
geometry.AddQuad(new LayoutRect((int)MathHelper.Lerp(widget.ContentArea.Left + 1, widget.ContentArea.Right - 1, value), widget.ContentArea.Top, 2, widget.ContentArea.Height), Color.White); geometry.AddQuad(new LayoutRect((int)MathHelper.Lerp(widget.ContentArea.Left + 1, widget.ContentArea.Right - 1, value), widget.ContentArea.Top, 2, widget.ContentArea.Height), Color.White);
} }
} }
public bool GetLayoutProperties(ref ProgressBar.ProgressBarLayoutProperties layoutProperties)
{
layoutProperties.ProgressBarHeight = 16;
return false;
}
public void Draw(
Widget widget,
GeometryHelper geometry,
in LayoutRect contentRect,
ProgressBar.ProgressBarVisualProperties properties
)
{
int width = contentRect.Width;
int fillWidth = (int) MathHelper.Clamp(MathHelper.Lerp(0, width, properties.FillPercentage), 0, width);
var background = contentRect;
var fill = new LayoutRect(background.Left, background.Top, fillWidth, background.Height);
geometry.AddQuad(background, Color.Gray);
geometry.AddQuad(fill, Color.Red);
}
} }

View file

@ -0,0 +1,34 @@
using AcidicGUI.Widgets;
namespace AcidicGUI.VisualStyles;
/// <summary>
/// Allows retrieving of layout properties of the given type.
/// When implemented by a visual style, allows widgets to retrieve
/// layout properties from said visual style via <see cref="Widget.GetLayoutProperties{TLayoutProperties}" />.
/// </summary>
/// <typeparam name="TLayoutProperties">Any value type containing layout properties for a given type of widget.</typeparam>
public interface IGetLayoutProperties<TLayoutProperties>
where TLayoutProperties : struct
{
/// <summary>
/// Retrieves layout properties and stores them into the given layout properties reference.
/// </summary>
/// <param name="layoutProperties">The destination to which layout properties should be stored.</param>
/// <returns>True if the layout properties have changed, false otherwise. If true, widgets needing these properties will invalidate their layout.</returns>
bool GetLayoutProperties(ref TLayoutProperties layoutProperties);
}
/// <summary>
/// When implemented by a widget, allows the widget
/// to query layout properties from the visual style.
/// </summary>
public interface IUpdateLayoutProperties
{
/// <summary>
/// Retrieves layout properties from the visual style. Called by the UI system on every
/// frame before layout updates occur. If layout properties change during this method call,
/// the widget will automatically invalidate its own layout.
/// </summary>
void UpdateLayoutProperties();
}

View file

@ -0,0 +1,14 @@
namespace AcidicGUI.VisualStyles;
/// <summary>
/// Interface for a widget that can be drawn by visual styles given properties
/// controlled by the widget.
/// </summary>
/// <typeparam name="TVisualProperties">Any value type containing any widget state needed for visual rendering.</typeparam>
public interface IVisual<TVisualProperties> where TVisualProperties : struct
{
/// <summary>
/// Gets a read-only reference to the visual properties associated with the widget.
/// </summary>
public ref readonly TVisualProperties VisualProperties { get; }
}

View file

@ -0,0 +1,29 @@
using AcidicGUI.Layout;
using AcidicGUI.Rendering;
using AcidicGUI.Widgets;
namespace AcidicGUI.VisualStyles;
/// <summary>
/// When implemented by a visual style, any <see cref="Widget"/> implementing
/// <see cref="IVisual{TVisualProperties}"/> to be rendered with the visual style
/// via <see cref="StyleManager.DrawVisual{TWidget, TVisualProperties}"/> where <b>TWidget</b>
/// is a <see cref="Widget" /> implementing <see cref="IVisual{TWidgetProperties}" />
/// </summary>
/// <typeparam name="TVisualProperties">The type of visual properties needed to render widgets of the given type.</typeparam>
public interface IVisualRenderer<in TVisualProperties> where TVisualProperties : struct
{
/// <summary>
/// Draws a widget using the specified visual properties.
/// </summary>
/// <param name="widget">The widget to draw</param>
/// <param name="geometry">The destination to which the widget should be drawn</param>
/// <param name="contentRect">The widget's calculated content bounds</param>
/// <param name="properties">The visual properties associated with the widget</param>
void Draw(
Widget widget,
GeometryHelper geometry,
in LayoutRect contentRect,
TVisualProperties properties
);
}

View file

@ -6,9 +6,9 @@ using Microsoft.Xna.Framework;
namespace AcidicGUI.VisualStyles; namespace AcidicGUI.VisualStyles;
public interface IVisualStyle : IFontFamilyProvider public interface IVisualStyle :
IFontFamilyProvider
{ {
int ProgressBarHeight { get; }
int SliderThickness { get; } int SliderThickness { get; }
Point ToggleSize { get; } Point ToggleSize { get; }
Point SwitchSize { get; } Point SwitchSize { get; }
@ -61,12 +61,6 @@ public interface IVisualStyle : IFontFamilyProvider
bool pressed, bool pressed,
bool selected bool selected
); );
void DrawProgressBar(
ProgressBar widget,
GeometryHelper geometry,
float fillPercentage
);
void DrawSlider( void DrawSlider(
Slider widget, Slider widget,

View file

@ -0,0 +1,73 @@
using AcidicGUI.CustomProperties;
using AcidicGUI.Rendering;
using AcidicGUI.Widgets;
namespace AcidicGUI.VisualStyles;
/// <summary>
/// API for interacting with global styles throughout the UI.
/// </summary>
public static class StyleManager
{
private static IVisualStyle? activeStyle;
[Obsolete("This property is for compatibility with older widgets, and is destined to be removed.")]
internal static IVisualStyle? ActiveStyle => activeStyle;
/// <summary>
/// Changes the active visual style, without notifying any widgets that this has happened.
/// </summary>
/// <param name="newStyle">The new style to use. If null, styled widgets will use fallback rendering.</param>
internal static void SetVisualStyle(IVisualStyle? newStyle)
{
activeStyle = newStyle;
}
/// <summary>
/// Gets layout properties of the given type from the specified style override,
/// or the active global style if the style override is null. If the style doesn't
/// implement <see cref="IGetLayoutProperties{TLayoutProperties}"/>, then defaults
/// will be returned instead.
///
/// </summary>
/// <param name="styleOverride">An optional style override.</param>
/// <param name="properties">A reference to a <see cref="TLayoutProperties"/> value where layouit properties will be written to.</param>
/// <typeparam name="TLayoutProperties">Any value type.</typeparam>
/// <returns>True if the properties have changed in the visual style, false otherwise.</returns>
internal static bool GetLayoutProperties<TLayoutProperties>(IVisualStyle? styleOverride, ref TLayoutProperties properties)
where TLayoutProperties : struct
{
var style = styleOverride ?? activeStyle;
properties = default(TLayoutProperties);
if (style is IGetLayoutProperties<TLayoutProperties> retriever)
return retriever.GetLayoutProperties(ref properties);
return false;
}
/// <summary>
/// Uses the current visual style to draw the given widget.
/// If the visual style does not support drawing the given widget
/// (it doesn't implement <see cref="IVisualRenderer{TWidget}"/>), then
/// no geometry will be drawn.
/// </summary>
/// <param name="geometryHelper">The destination <see cref="GeometryHelper" /> to which geometry should be drawn.</param>
/// <param name="widget">The widget to be drawn. Must implement <see cref="IVisual{TVisualProperties}" />.</param>
/// <typeparam name="TWidget">Any type deriving <see cref="Widget"/> implementing <see cref="IVisual{TVisualProperties}" />.</typeparam>
/// <typeparam name="TVisualProperties">Any value type.</typeparam>
public static void DrawVisual<TWidget, TVisualProperties>(this GeometryHelper geometryHelper, TWidget widget)
where TWidget : Widget, IVisual<TVisualProperties>
where TVisualProperties : struct
{
var style = widget.GetVisualStyleOverride() ?? activeStyle;
if (style is not IVisualRenderer<TVisualProperties> renderer)
return;
var contentArea = widget.ContentArea;
var styleProperties = widget.VisualProperties;
renderer.Draw(widget, geometryHelper, in contentArea, styleProperties);
}
}

View file

@ -55,7 +55,7 @@ public abstract class Dropdown<TItemType, TView> : Widget,
protected override Point GetContentSize(Point availableSize) protected override Point GetContentSize(Point availableSize)
{ {
Padding buttonPadding = GetVisualStyle().DropdownButtonPadding; Padding buttonPadding = GetVisualStyleOverride().DropdownButtonPadding;
Point buttonSize = buttonBox.GetCachedContentSize(availableSize); Point buttonSize = buttonBox.GetCachedContentSize(availableSize);
return new Point(buttonSize.X + buttonPadding.Horizontal, buttonSize.Y + buttonPadding.Vertical); return new Point(buttonSize.X + buttonPadding.Horizontal, buttonSize.Y + buttonPadding.Vertical);
@ -63,7 +63,7 @@ public abstract class Dropdown<TItemType, TView> : Widget,
protected override void ArrangeChildren(IGuiContext context, LayoutRect availableSpace) protected override void ArrangeChildren(IGuiContext context, LayoutRect availableSpace)
{ {
Padding buttonPadding = GetVisualStyle().DropdownButtonPadding; Padding buttonPadding = GetVisualStyleOverride().DropdownButtonPadding;
buttonBox.UpdateLayout(context, new LayoutRect(availableSpace.Left + buttonPadding.Left, availableSpace.Top + buttonPadding.Top, availableSpace.Width - buttonPadding.Horizontal, availableSpace.Height - buttonPadding.Vertical)); buttonBox.UpdateLayout(context, new LayoutRect(availableSpace.Left + buttonPadding.Left, availableSpace.Top + buttonPadding.Top, availableSpace.Width - buttonPadding.Horizontal, availableSpace.Height - buttonPadding.Vertical));
Point screen = new Point(context.CanvasWidth, context.CanvasHeight); Point screen = new Point(context.CanvasWidth, context.CanvasHeight);
@ -258,7 +258,7 @@ public abstract class Dropdown<TItemType, TView> : Widget,
protected override void RebuildGeometry(GeometryHelper geometry) protected override void RebuildGeometry(GeometryHelper geometry)
{ {
GetVisualStyle().DrawDropdownItemBackground(this, geometry, hovered, pressed, active); GetVisualStyleOverride().DrawDropdownItemBackground(this, geometry, hovered, pressed, active);
} }
public void OnMouseClick(MouseButtonEvent e) public void OnMouseClick(MouseButtonEvent e)
@ -404,7 +404,7 @@ public abstract class Dropdown<TItemType, TView> : Widget,
protected override void RebuildGeometry(GeometryHelper geometry) protected override void RebuildGeometry(GeometryHelper geometry)
{ {
GetVisualStyle().DrawDropdownItemsBackground(geometry, DropdownArea); GetVisualStyleOverride().DrawDropdownItemsBackground(geometry, DropdownArea);
} }
} }
} }

View file

@ -43,7 +43,7 @@ public sealed class Icon : Widget
protected override Point GetContentSize(Point availableSize) protected override Point GetContentSize(Point availableSize)
{ {
Font? iconFont = GetVisualStyle().IconFont; Font? iconFont = GetVisualStyleOverride().IconFont;
if (iconFont == null || string.IsNullOrEmpty(iconString)) if (iconFont == null || string.IsNullOrEmpty(iconString))
return Point.Zero; return Point.Zero;
@ -52,7 +52,7 @@ public sealed class Icon : Widget
protected override void RebuildGeometry(GeometryHelper geometry) protected override void RebuildGeometry(GeometryHelper geometry)
{ {
var font = GetVisualStyle().IconFont; var font = GetVisualStyleOverride().IconFont;
float x = ContentArea.Left + (ContentArea.Width - actualIconSize.X) / 2f; float x = ContentArea.Left + (ContentArea.Width - actualIconSize.X) / 2f;
float y = ContentArea.Top + (ContentArea.Height - actualIconSize.Y) / 2f; float y = ContentArea.Top + (ContentArea.Height - actualIconSize.Y) / 2f;

View file

@ -1,29 +1,58 @@
using AcidicGUI.Animation;
using AcidicGUI.Rendering; using AcidicGUI.Rendering;
using AcidicGUI.VisualStyles;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
namespace AcidicGUI.Widgets; namespace AcidicGUI.Widgets;
public sealed class ProgressBar : Widget public sealed class ProgressBar :
Widget,
IVisual<ProgressBar.ProgressBarVisualProperties>,
IUpdateLayoutProperties
{ {
private float fillPercentage = 0; private ProgressBarVisualProperties visualProperties;
private ProgressBarLayoutProperties layoutProperties;
/// <inheritdoc />
public ref readonly ProgressBarVisualProperties VisualProperties => ref visualProperties;
public float Value public float Value
{ {
get => fillPercentage; get => visualProperties.FillPercentage;
set set
{ {
fillPercentage = MathHelper.Clamp(value, 0, 1); var newPercentage = MathHelper.Clamp(value, 0, 1);
if (Math.Abs(newPercentage - visualProperties.FillPercentage) < float.Epsilon)
return;
visualProperties.FillPercentage = newPercentage;
InvalidateGeometry(); InvalidateGeometry();
} }
} }
protected override Point GetContentSize(Point availableSize) protected override Point GetContentSize(Point availableSize)
{ {
return new Point(availableSize.X, GetVisualStyle().ProgressBarHeight); return new Point(availableSize.X, layoutProperties.ProgressBarHeight);
} }
protected override void RebuildGeometry(GeometryHelper geometry) protected override void RebuildGeometry(GeometryHelper geometry)
{ {
GetVisualStyle().DrawProgressBar(this, geometry, fillPercentage); geometry.DrawVisual<ProgressBar, ProgressBarVisualProperties>(this);
}
public void UpdateLayoutProperties()
{
this.GetLayoutProperties(ref layoutProperties);
}
public struct ProgressBarVisualProperties
{
public float FillPercentage;
}
public struct ProgressBarLayoutProperties()
{
public int ProgressBarHeight = 16;
} }
} }

View file

@ -60,7 +60,7 @@ public sealed class ScrollView :
availableSpace = new LayoutRect( availableSpace = new LayoutRect(
availableSpace.Left, availableSpace.Left,
availableSpace.Top, availableSpace.Top,
availableSpace.Width - GetVisualStyle().ScrollBarSize, availableSpace.Width - GetVisualStyleOverride().ScrollBarSize,
availableSpace.Height availableSpace.Height
); );
} }
@ -104,10 +104,10 @@ public sealed class ScrollView :
if (showScrollBar) if (showScrollBar)
{ {
GetVisualStyle().DrawScrollBar(this, geometry, new LayoutRect( GetVisualStyleOverride().DrawScrollBar(this, geometry, new LayoutRect(
ContentArea.Right - GetVisualStyle().ScrollBarSize, ContentArea.Right - GetVisualStyleOverride().ScrollBarSize,
ContentArea.Top, ContentArea.Top,
GetVisualStyle().ScrollBarSize, GetVisualStyleOverride().ScrollBarSize,
ContentArea.Height ContentArea.Height
), pageOffset, innerSize); ), pageOffset, innerSize);
} }

View file

@ -41,7 +41,7 @@ public sealed class Slider : Widget,
protected override Point GetContentSize(Point availableSize) protected override Point GetContentSize(Point availableSize)
{ {
int thickness = GetVisualStyle().SliderThickness; int thickness = GetVisualStyleOverride().SliderThickness;
return direction == Direction.Horizontal return direction == Direction.Horizontal
? new Point(100, thickness) ? new Point(100, thickness)
@ -50,7 +50,7 @@ public sealed class Slider : Widget,
protected override void RebuildGeometry(GeometryHelper geometry) protected override void RebuildGeometry(GeometryHelper geometry)
{ {
GetVisualStyle().DrawSlider(this, geometry, hovered, pressed, direction == Direction.Vertical, currentValue); GetVisualStyleOverride().DrawSlider(this, geometry, hovered, pressed, direction == Direction.Vertical, currentValue);
} }
private void SetValue(float newValue) private void SetValue(float newValue)

View file

@ -282,13 +282,13 @@ public class TextWidget : Widget,
} }
var renderColor = element.MarkupData.IsSelected var renderColor = element.MarkupData.IsSelected
? GetVisualStyle().TextSelectionForeground ? GetVisualStyleOverride().TextSelectionForeground
: (element.MarkupData.ColorOverride ?? TextColor) ?? GetVisualStyle().GetTextColor(this); : (element.MarkupData.ColorOverride ?? TextColor) ?? GetVisualStyleOverride().GetTextColor(this);
if (element.MeasuredSize.HasValue && element.MarkupData.Highlight.A > 0 || element.MarkupData.IsSelected) if (element.MeasuredSize.HasValue && element.MarkupData.Highlight.A > 0 || element.MarkupData.IsSelected)
{ {
var highlight = element.MarkupData.IsSelected var highlight = element.MarkupData.IsSelected
? GetVisualStyle().TextSelectionBackground ? GetVisualStyleOverride().TextSelectionBackground
: element.MarkupData.Highlight; : element.MarkupData.Highlight;
var highlightRect = new LayoutRect( var highlightRect = new LayoutRect(

View file

@ -1,3 +1,4 @@
using AcidicGUI.Common;
using AcidicGUI.Events; using AcidicGUI.Events;
using AcidicGUI.Layout; using AcidicGUI.Layout;
using AcidicGUI.Rendering; using AcidicGUI.Rendering;
@ -20,6 +21,7 @@ public sealed class Toggle : Widget,
private bool focused; private bool focused;
private bool toggleValue; private bool toggleValue;
public event Action<StateChange<bool>>? OnBeforeValueChanged;
public event Action<bool>? OnValueChanged; public event Action<bool>? OnValueChanged;
public bool UseSwitchVariant public bool UseSwitchVariant
@ -41,7 +43,7 @@ public sealed class Toggle : Widget,
return; return;
toggleValue = value; toggleValue = value;
NotifyChange(); OnValueChanged?.Invoke(value);
InvalidateGeometry(); InvalidateGeometry();
} }
} }
@ -51,22 +53,22 @@ public sealed class Toggle : Widget,
protected override Point GetContentSize(Point availableSize) protected override Point GetContentSize(Point availableSize)
{ {
return isSwitchVariant return isSwitchVariant
? GetVisualStyle().SwitchSize ? GetVisualStyleOverride().SwitchSize
: GetVisualStyle().ToggleSize; : GetVisualStyleOverride().ToggleSize;
} }
protected override void RebuildGeometry(GeometryHelper geometry) protected override void RebuildGeometry(GeometryHelper geometry)
{ {
var size = isSwitchVariant var size = isSwitchVariant
? GetVisualStyle().SwitchSize ? GetVisualStyleOverride().SwitchSize
: GetVisualStyle().ToggleSize; : GetVisualStyleOverride().ToggleSize;
var rect = new LayoutRect(ContentArea.Left + ((ContentArea.Width - size.X) / 2), ContentArea.Top + ((ContentArea.Height - size.Y) / 2), size.X, size.Y); var rect = new LayoutRect(ContentArea.Left + ((ContentArea.Width - size.X) / 2), ContentArea.Top + ((ContentArea.Height - size.Y) / 2), size.X, size.Y);
if (isSwitchVariant) if (isSwitchVariant)
GetVisualStyle().DrawToggleSwitch(this, geometry, rect, hovered, pressed, focused, toggleValue); GetVisualStyleOverride().DrawToggleSwitch(this, geometry, rect, hovered, pressed, focused, toggleValue);
else else
GetVisualStyle().DrawToggle(this, geometry, rect, hovered, pressed, focused, toggleValue); GetVisualStyleOverride().DrawToggle(this, geometry, rect, hovered, pressed, focused, toggleValue);
} }
public void OnMouseEnter(MouseMoveEvent e) public void OnMouseEnter(MouseMoveEvent e)
@ -121,14 +123,22 @@ public sealed class Toggle : Widget,
if (!hovered) if (!hovered)
return; return;
toggleValue = !toggleValue;
InvalidateGeometry(); InvalidateGeometry();
NotifyChange(); NotifyChange(!toggleValue);
} }
private void NotifyChange() private void NotifyChange(bool newValue)
{ {
var currentValue = toggleValue;
var change = new StateChange<bool>(currentValue, newValue);
OnBeforeValueChanged?.Invoke(change);
if (change.Canceled)
return;
toggleValue = newValue;
OnValueChanged?.Invoke(toggleValue); OnValueChanged?.Invoke(toggleValue);
} }
} }

View file

@ -1,23 +1,25 @@
using AcidicGUI.Layout; using AcidicGUI.Layout;
using AcidicGUI.VisualStyles;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
namespace AcidicGUI.Widgets; namespace AcidicGUI.Widgets;
public partial class Widget public partial class Widget
{ {
private Widget? layoutRoot; private readonly HashSet<Type> dirtyLayoutPropertyTypes = new();
private bool layoutIsDirty = true; private Widget? layoutRoot;
private HorizontalAlignment horizontalAlignment; private bool layoutIsDirty = true;
private VerticalAlignment verticalAlignment; private HorizontalAlignment horizontalAlignment;
private LayoutRect geometryRect; private VerticalAlignment verticalAlignment;
private LayoutRect calculatedLayoutRect; private LayoutRect geometryRect;
private Point? cachedContentSize; private LayoutRect calculatedLayoutRect;
private Padding padding; private Point? cachedContentSize;
private Padding margin; private Padding padding;
private Point minimumSize; private Padding margin;
private Point maximumSize; private Point minimumSize;
private Point previousAvailableSize; private Point maximumSize;
private Visibility visibility; private Point previousAvailableSize;
private Visibility visibility;
public bool IsVisible => Visibility == Visibility.Visible && (Parent == null || Parent.IsVisible); public bool IsVisible => Visibility == Visibility.Visible && (Parent == null || Parent.IsVisible);
@ -125,7 +127,20 @@ public partial class Widget
layoutRoot = value; layoutRoot = value;
} }
} }
protected void GetLayoutProperties<TLayoutProperties>(ref TLayoutProperties properties)
where TLayoutProperties : struct
{
var wasDirty = StyleManager.GetLayoutProperties<TLayoutProperties>(GetVisualStyleOverride(), ref properties)
|| dirtyLayoutPropertyTypes.Contains(typeof(TLayoutProperties));
if (wasDirty)
{
dirtyLayoutPropertyTypes.Add(typeof(TLayoutProperties));
InvalidateLayout();
}
}
public void InvalidateOwnLayout() public void InvalidateOwnLayout()
{ {
InvalidateLayoutInternal(); InvalidateLayoutInternal();
@ -145,6 +160,8 @@ public partial class Widget
if (!layoutIsDirty) if (!layoutIsDirty)
return; return;
dirtyLayoutPropertyTypes.Clear();
if (visibility == Visibility.Collapsed) if (visibility == Visibility.Collapsed)
{ {
calculatedLayoutRect = new LayoutRect(0, 0, 0, 0); calculatedLayoutRect = new LayoutRect(0, 0, 0, 0);

View file

@ -22,7 +22,7 @@ public abstract partial class Widget : IFontFamilyProvider
private ClippingMode clippingMode; private ClippingMode clippingMode;
private LayoutRect clipRect; private LayoutRect clipRect;
private IWidgetEffect? effectOverride; private IWidgetEffect? effectOverride;
public IWidgetEffect? RenderEffect public IWidgetEffect? RenderEffect
{ {
get => effectOverride; get => effectOverride;
@ -103,6 +103,9 @@ public abstract partial class Widget : IFontFamilyProvider
get => visualStyleOverride; get => visualStyleOverride;
set set
{ {
if (visualStyleOverride != value)
return;
visualStyleOverride = value; visualStyleOverride = value;
InvalidateLayout(); InvalidateLayout();
} }
@ -154,20 +157,27 @@ public abstract partial class Widget : IFontFamilyProvider
} }
} }
public IVisualStyle GetVisualStyle() /// <summary>
/// Walks up the widget tree from this widget to the top-level, returning
/// the first visual style override that isn't null. If no widgets in the
/// path override the global visual style, then null will be returned.
/// </summary>
/// <returns>The visual style override for this widget or one of its parents, or null if there are no overriding widgets.</returns>
public IVisualStyle? GetVisualStyleOverride()
{ {
if (visualStyleOverride != null) if (visualStyleOverride != null)
return visualStyleOverride; return visualStyleOverride;
if (parent != null) if (parent != null)
return parent.GetVisualStyle(); return parent.GetVisualStyleOverride();
return GuiManager!.GetVisualStyle(); // TODO: Return null and remove that property when we've ported all the widgets.
return StyleManager.ActiveStyle;
} }
protected virtual void RebuildGeometry(GeometryHelper geometry) protected virtual void RebuildGeometry(GeometryHelper geometry)
{ {
GetVisualStyle().DrawWidgetBackground(this, geometry); GetVisualStyleOverride().DrawWidgetBackground(this, geometry);
} }
private LayoutRect? GetClippingRectangle() private LayoutRect? GetClippingRectangle()

View file

@ -1,14 +1,66 @@
namespace SociallyDistant.Core.Core.Config namespace SociallyDistant.Core.Core.Config
{ {
/// <summary>
/// Interfaces for building settings pages, and adding
/// fields to said pages.
/// </summary>
public interface ISettingsUiBuilder public interface ISettingsUiBuilder
{ {
/// <summary>
/// Creates a new labeled section to categorize fields under.
/// </summary>
/// <param name="sectionTitle">The name of the section as shown in the UI</param>
/// <param name="sectionId">A unique identifier representing the section</param>
/// <returns></returns>
ISettingsUiBuilder AddSection(string sectionTitle, out int sectionId); ISettingsUiBuilder AddSection(string sectionTitle, out int sectionId);
ISettingsUiBuilder WithLabel(string labelText, int sectionId); ISettingsUiBuilder WithLabel(string labelText, int sectionId);
ISettingsUiBuilder WithToggle(string title, string? description, bool value, Action<bool> changeCallback, int sectionId);
ISettingsUiBuilder WithSlider(string title, string? description, float value, float minimum, float maximum, Action<float> changeCallback, int sectionId); ISettingsUiBuilder WithToggle(
ISettingsUiBuilder WithSlider(string title, string? description, int value, int minimum, int maximum, Action<int> changeCallback, int sectionId); string title,
ISettingsUiBuilder WithTextField(string title, string? description, string? currentValue, Action<string?> changeCallback, int sectionId); string? description,
ISettingsUiBuilder WithStringDropdown(string title, string? description, int currentIndex, string[] choices, Action<int> changeCallback, int sectionId); bool value,
Action<bool> changeCallback,
int sectionId,
bool requireConfirmation = false,
string? confirmationMessage = null
);
ISettingsUiBuilder WithSlider(
string title,
string? description,
float value,
float minimum,
float maximum,
Action<float> changeCallback,
int sectionId
);
ISettingsUiBuilder WithSlider(
string title,
string? description,
int value,
int minimum,
int maximum,
Action<int> changeCallback,
int sectionId
);
ISettingsUiBuilder WithTextField(
string title,
string? description,
string? currentValue,
Action<string?> changeCallback,
int sectionId
);
ISettingsUiBuilder WithStringDropdown(
string title,
string? description,
int currentIndex,
string[] choices,
Action<int> changeCallback,
int sectionId
);
} }
} }

View file

@ -11,6 +11,7 @@
public string Name => reflectionAttribute.DisplayName; public string Name => reflectionAttribute.DisplayName;
public string SectionName => reflectionAttribute.SectionName; public string SectionName => reflectionAttribute.SectionName;
public bool Hidden => reflectionAttribute.Hidden; public bool Hidden => reflectionAttribute.Hidden;
public int Priority => reflectionAttribute.Priority;
protected SettingsCategory(ISettingsManager settingsManager) protected SettingsCategory(ISettingsManager settingsManager)
{ {

View file

@ -3,17 +3,19 @@
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
public class SettingsCategoryAttribute : Attribute public class SettingsCategoryAttribute : Attribute
{ {
public SettingsCategoryAttribute(string key, string displayName, string sectionName, bool hidden = false) public SettingsCategoryAttribute(string key, string displayName, string sectionName, bool hidden = false, int priority = 0)
{ {
Key = key; Key = key;
DisplayName = displayName; DisplayName = displayName;
Hidden = hidden; Hidden = hidden;
SectionName = sectionName; SectionName = sectionName;
this.Priority = priority;
} }
public string SectionName { get; } public string SectionName { get; }
public string Key { get; } public string Key { get; }
public string DisplayName { get; } public string DisplayName { get; }
public bool Hidden { get; } public bool Hidden { get; }
public int Priority { get; }
} }
} }

View file

@ -5,7 +5,7 @@ using SociallyDistant.Core.Core.Config;
namespace SociallyDistant.Core.Config.SystemConfigCategories namespace SociallyDistant.Core.Config.SystemConfigCategories
{ {
[SettingsCategory("graphics", "Graphics", CommonSettingsCategorySections.Hardware)] [SettingsCategory("graphics", "Graphics", CommonSettingsCategorySections.Hardware, priority: 1)]
public class GraphicsSettings : SettingsCategory public class GraphicsSettings : SettingsCategory
{ {
public string? DisplayResolution public string? DisplayResolution
@ -62,7 +62,13 @@ namespace SociallyDistant.Core.Config.SystemConfigCategories
private string[] GetAvailableResolutions() private string[] GetAvailableResolutions()
{ {
return GraphicsAdapter.DefaultAdapter.SupportedDisplayModes.OrderByDescending(x => x.Width * x.Height).Where(x => x.Height > 720).Select(x => $"{x.Width}x{x.Height}").Distinct().ToArray(); return GraphicsAdapter.DefaultAdapter.SupportedDisplayModes.
OrderByDescending(x => x.Width)
.ThenByDescending(x => x.Height)
.Where(x => x.Height >= 720)
.Select(x => $"{x.Width}x{x.Height}")
.Distinct()
.ToArray();
} }
/// <inheritdoc /> /// <inheritdoc />
@ -79,58 +85,56 @@ namespace SociallyDistant.Core.Config.SystemConfigCategories
if (currentIndex == -1) if (currentIndex == -1)
currentIndex = 0; currentIndex = 0;
#endif #endif
uiBuilder.AddSection("Display", out int display).WithStringDropdown(
"Resolution",
null,
currentIndex,
resolutions,
x => DisplayResolution = resolutions[x],
display
).WithToggle(
"Fullscreen",
null,
Fullscreen,
x => Fullscreen = x,
display
).WithToggle(
"Enable V-sync",
null,
VSync,
x => VSync = x,
display
);
uiBuilder.AddSection("Display", out int display) uiBuilder.AddSection("Desktop Effects", out int effects).WithToggle(
.WithStringDropdown( "Background blurs",
"Resolution", "Adds translucent, blurred backgrounds to various interface elements like the terminal",
null, BlurEffect,
currentIndex, x => BlurEffect = x,
resolutions, effects
x => DisplayResolution = resolutions[x], ).WithToggle(
display "Bloom",
).WithToggle( "Adds subtle glow to brighter UI elements and text",
"Fullscreen", BloomEffect,
null, x => BloomEffect = x,
Fullscreen, effects
x => Fullscreen = x, ).WithToggle(
display "Glitch bands",
).WithToggle( "Enable or disable the flashing bands of color that appear during malware infections and high system load.",
"Enable V-sync", EnableXorgGlitches,
null, x => EnableXorgGlitches = x,
VSync, effects
x => VSync = x, );
display
);
uiBuilder.AddSection("Desktop Effects", out int effects)
.WithToggle("Background blurs",
"Adds translucent, blurred backgrounds to various interface elements like the terminal",
BlurEffect,
x => BlurEffect = x,
effects)
.WithToggle("Bloom",
"Adds subtle glow to brighter UI elements and text",
BloomEffect,
x => BloomEffect = x,
effects)
.WithToggle(
"Glitch bands",
"Enable or disable the flashing bands of color that appear during malware infections and high system load.",
EnableXorgGlitches,
x => EnableXorgGlitches = x,
effects
);
if (OperatingSystem.IsLinux()) if (OperatingSystem.IsLinux())
{ {
uiBuilder.AddSection("Linux", out int linux) uiBuilder.AddSection("Linux", out int linux).WithToggle(
.WithToggle( "Wayland Support",
"Wayland Support", "Change whether Socially Distant uses Wayland or X11 to communicate with your desktop environment. Disabling Wayland support may work around bugs on certain desktops. Changing this setting requires a game restart.",
"Change whether Socially Distant uses Wayland or X11 to communicate with your desktop environment. Disabling Wayland support may work around bugs on certain desktops. Changing this setting requires a game restart.", UseWaylandBackend,
UseWaylandBackend, x => UseWaylandBackend = x,
x => UseWaylandBackend = x, linux
linux );
);
} }
} }

View file

@ -3,6 +3,7 @@ using System.Security;
using System.Text; using System.Text;
using SociallyDistant.Core.Modules; using SociallyDistant.Core.Modules;
using SociallyDistant.Core.OS.Network; using SociallyDistant.Core.OS.Network;
using SociallyDistant.Core.Shell.Windowing;
namespace SociallyDistant.Core.Core namespace SociallyDistant.Core.Core
{ {
@ -34,6 +35,21 @@ namespace SociallyDistant.Core.Core
public static readonly string PlayerHomeId = "player"; public static readonly string PlayerHomeId = "player";
public static Task<MessageDialogResult> GetResultAsync(this IMessageDialog messageDialog)
{
var completionSource = new TaskCompletionSource<MessageDialogResult>();
messageDialog.DismissCallback += OnDialogDismiss;
return completionSource.Task;
void OnDialogDismiss(MessageDialogResult result)
{
messageDialog.DismissCallback -= OnDialogDismiss;
completionSource.SetResult(result);
}
}
public static IEnumerable<Exception> UnravelAggregateExceptions(AggregateException? aggregate) public static IEnumerable<Exception> UnravelAggregateExceptions(AggregateException? aggregate)
{ {
if (aggregate == null) if (aggregate == null)

View file

@ -1,11 +1,12 @@
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices;
using AcidicGUI.Widgets; using AcidicGUI.Widgets;
using Microsoft.Win32; using Microsoft.Win32;
using Serilog; using Serilog;
namespace SociallyDistant.Core.Modules; namespace SociallyDistant.Core.Modules;
public abstract class Application public abstract class Application : IDisposable
{ {
private static Application? _current; private static Application? _current;
private bool started; private bool started;
@ -37,6 +38,14 @@ public abstract class Application
} }
} }
public void Restart()
{
if (!started)
return;
OnRestart();
}
public void Start() public void Start()
{ {
if (started) if (started)
@ -55,11 +64,12 @@ public abstract class Application
} }
protected abstract void Run(); protected abstract void Run();
protected abstract void OnRestart();
private void DetermineHardwareData() private void DetermineHardwareData()
{ {
Log.Information("Determining system info and whether we can run..."); Log.Information("Determining system info and whether we can run...");
OperatingSystem = Environment.OSVersion.ToString(); OperatingSystem = $"{RuntimeInformation.OSDescription} {RuntimeInformation.OSArchitecture} ({RuntimeInformation.FrameworkDescription})";
Log.Information($"Operating system is {OperatingSystem}"); Log.Information($"Operating system is {OperatingSystem}");
switch (Environment.OSVersion.Platform) switch (Environment.OSVersion.Platform)
@ -120,4 +130,9 @@ public abstract class Application
// Pick the first. // Pick the first.
CpuName = cpuModels[0]; CpuName = cpuModels[0];
} }
public virtual void Dispose()
{
_current = null;
}
} }

View file

@ -40,19 +40,41 @@ public sealed class BackgroundBlurWidgetEffect : IWidgetEffect,
ColorSourceBlend = Blend.SourceAlpha, ColorDestinationBlend = Blend.InverseSourceAlpha, AlphaSourceBlend = Blend.One, AlphaDestinationBlend = Blend.InverseSourceAlpha, ColorSourceBlend = Blend.SourceAlpha, ColorDestinationBlend = Blend.InverseSourceAlpha, AlphaSourceBlend = Blend.One, AlphaDestinationBlend = Blend.InverseSourceAlpha,
}; };
private BackgroundBlurWidgetEffect(MonoGameEffect defaultUiShader, Effect bluReFfect) private BackgroundBlurWidgetEffect(MonoGameEffect defaultUiShader, Effect blurEffect)
{ {
this.defaultUiShader = defaultUiShader; this.defaultUiShader = defaultUiShader;
this.gaussianShader = bluReFfect; this.gaussianShader = blurEffect;
blurrinessParameter = bluReFfect.Parameters["Blurriness"]; blurrinessParameter = blurEffect.Parameters["Blurriness"];
texelSizeParameter = bluReFfect.Parameters["TexelSize"]; texelSizeParameter = blurEffect.Parameters["TexelSize"];
curveParameter = bluReFfect.Parameters["Curve"]; curveParameter = blurEffect.Parameters["Curve"];
vertexBuffer = new VertexBuffer(bluReFfect.GraphicsDevice, typeof(VertexPositionColorTexture), 4, BufferUsage.None); vertexBuffer = new VertexBuffer(blurEffect.GraphicsDevice, typeof(VertexPositionColorTexture), 4, BufferUsage.None);
indexBuffer = new IndexBuffer(bluReFfect.GraphicsDevice, typeof(int), 6, BufferUsage.None); indexBuffer = new IndexBuffer(blurEffect.GraphicsDevice, typeof(int), 6, BufferUsage.None);
vertexBuffer.SetData(new VertexPositionColorTexture[] { new VertexPositionColorTexture(new Vector3(-1, -1, 0), Color.White, new Vector2(0, 0)), new VertexPositionColorTexture(new Vector3(1, -1, 0), Color.White, new Vector2(1, 0)), new VertexPositionColorTexture(new Vector3(-1, 1, 0), Color.White, new Vector2(0, 1)), new VertexPositionColorTexture(new Vector3(1, 1, 0), Color.White, new Vector2(1, 1)) }); vertexBuffer.SetData(new VertexPositionColorTexture[]
{
new VertexPositionColorTexture(
new Vector3(-1, -1, 0),
Color.White,
new Vector2(0, 0)
),
new VertexPositionColorTexture(
new Vector3(1, -1, 0),
Color.White,
new Vector2(1, 0)
),
new VertexPositionColorTexture(
new Vector3(-1, 1, 0),
Color.White,
new Vector2(0, 1)
),
new VertexPositionColorTexture(
new Vector3(1, 1, 0),
Color.White,
new Vector2(1, 1)
)
});
indexBuffer.SetData(new int[] { 0, 1, 2, 2, 1, 3 }); indexBuffer.SetData(new int[] { 0, 1, 2, 2, 1, 3 });
settingsObserver = Application.Instance.Context.SettingsManager.ObserveChanges(OnGameSettingsChanged); settingsObserver = Application.Instance.Context.SettingsManager.ObserveChanges(OnGameSettingsChanged);
@ -87,15 +109,19 @@ public sealed class BackgroundBlurWidgetEffect : IWidgetEffect,
} }
var blurSettings = widget.GetCustomProperties<BackgroundBlurProperties>(); var blurSettings = widget.GetCustomProperties<BackgroundBlurProperties>();
var cachedGeometry = widget.GetCustomProperties<CachedGeometry>();
blurriness = blurSettings.Blurriness; blurriness = blurSettings.Blurriness;
curve = blurSettings.ComputedCurve; curve = blurSettings.ComputedCurve;
Viewport viewport = renderer.GraphicsDevice.Viewport; Viewport viewport = renderer.GraphicsDevice.Viewport;
ResizeIfNeeded(ref blurTarget1, viewport.Width, viewport.Height); var needsToResize = ResizeIfNeeded(ref blurTarget1, viewport.Width, viewport.Height);
ResizeIfNeeded(ref blurTarget2, viewport.Width, viewport.Height); needsToResize |= ResizeIfNeeded(ref blurTarget2, viewport.Width, viewport.Height);
if (needsToResize)
cachedGeometry.Geometry = null;
if (blurTarget1 == null) if (blurTarget1 == null)
return; return;
@ -146,13 +172,17 @@ public sealed class BackgroundBlurWidgetEffect : IWidgetEffect,
{ {
} }
private void ResizeIfNeeded(ref RenderTarget2D? target, int width, int height) private bool ResizeIfNeeded(ref RenderTarget2D? target, int width, int height)
{ {
if (target == null || target.Width != width || target.Height != height) if (target == null || target.Width != width || target.Height != height)
{ {
target?.Dispose(); target?.Dispose();
target = new RenderTarget2D(gaussianShader.GraphicsDevice, width, height, false, SurfaceFormat.Color, DepthFormat.Depth24Stencil8, 4, RenderTargetUsage.PreserveContents); target = new RenderTarget2D(gaussianShader.GraphicsDevice, width, height, false, SurfaceFormat.Color, DepthFormat.Depth24Stencil8, 4, RenderTargetUsage.PreserveContents);
return true;
} }
return false;
} }
private void DoBlur(RenderTarget2D source, RenderTarget2D destination, int pass) private void DoBlur(RenderTarget2D source, RenderTarget2D destination, int pass)

View file

@ -50,6 +50,11 @@ public sealed class GuiService :
refreshGeometryListener = EventBus.Listen<RefreshGeometryEvent>(OnGeometryRefreshRequested); refreshGeometryListener = EventBus.Listen<RefreshGeometryEvent>(OnGeometryRefreshRequested);
} }
public void ScheduleFullReRender()
{
acidicGui.ScheduleFullReRender();
}
public override void Initialize() public override void Initialize()
{ {
base.Initialize(); base.Initialize();

View file

@ -40,10 +40,25 @@ public class WidgetListSettingsUiBuilder : ISettingsUiBuilder
string? description, string? description,
bool value, bool value,
Action<bool> changeCallback, Action<bool> changeCallback,
int sectionId int sectionId,
bool requireConfirmation,
string? confirmationMessage = null
) )
{ {
widgets.AddWidget(new SettingsFieldWidget { Title = title, Description = description, Slot = new SwitchWidget { IsActive = value, Callback = changeCallback }, UseReverseLayout = false }, sectionMap[sectionId]); widgets.AddWidget(new SettingsFieldWidget
{
Title = title,
Description = description,
Slot = new SwitchWidget
{
IsActive = value,
Callback = changeCallback,
RequireConfirmation = requireConfirmation,
ConfirmationTitle = title,
ConfirmationMessage = confirmationMessage
},
UseReverseLayout = false
}, sectionMap[sectionId]);
return this; return this;
} }

View file

@ -6,9 +6,20 @@ public sealed class SwitchWidget : IWidget
{ {
public bool IsActive { get; set; } public bool IsActive { get; set; }
public Action<bool>? Callback { get; set; } public Action<bool>? Callback { get; set; }
public bool RequireConfirmation { get; set; }
public string? ConfirmationTitle { get; set; }
public string? ConfirmationMessage { get; set; }
public RecyclableWidgetController Build() public RecyclableWidgetController Build()
{ {
return new SwitchWidgetController() { IsActive = IsActive, Callback = Callback }; return new SwitchWidgetController()
{
IsActive = IsActive,
Callback = Callback,
RequireConfirmation = this.RequireConfirmation,
ConfirmationTitle = this.ConfirmationTitle,
ConfirmationMessage = this.ConfirmationMessage
};
} }
} }

View file

@ -1,5 +1,9 @@
using AcidicGUI.Common;
using AcidicGUI.ListAdapters; using AcidicGUI.ListAdapters;
using AcidicGUI.Widgets; using AcidicGUI.Widgets;
using SociallyDistant.Core.Core;
using SociallyDistant.Core.Modules;
using SociallyDistant.Core.Shell.Windowing;
namespace SociallyDistant.Core.UI.Recycling; namespace SociallyDistant.Core.UI.Recycling;
@ -9,7 +13,9 @@ public sealed class SwitchWidgetController : RecyclableWidgetController
public bool IsActive { get; set; } public bool IsActive { get; set; }
public Action<bool>? Callback { get; set; } public Action<bool>? Callback { get; set; }
public bool RequireConfirmation { get; set; }
public string? ConfirmationTitle { get; set; }
public string? ConfirmationMessage { get; set; }
public override void Build(ContentWidget destination) public override void Build(ContentWidget destination)
{ {
@ -17,22 +23,53 @@ public sealed class SwitchWidgetController : RecyclableWidgetController
toggle.UseSwitchVariant = true; toggle.UseSwitchVariant = true;
toggle.ToggleValue = IsActive; toggle.ToggleValue = IsActive;
toggle.OnValueChanged += HandleValueChanged; toggle.OnBeforeValueChanged += HandleValueChanged;
destination.Content = toggle; destination.Content = toggle;
} }
private void HandleValueChanged(bool value) private void HandleValueChanged(StateChange<bool> change)
{ {
Callback?.Invoke(value); if (!RequireConfirmation)
{
Callback?.Invoke(change.NewValue);
return;
}
change.Cancel();
ConfirmChange(change.NewValue);
} }
public override void Recycle() public override void Recycle()
{ {
if (toggle != null) if (toggle != null)
toggle.OnValueChanged -= HandleValueChanged; toggle.OnBeforeValueChanged -= HandleValueChanged;
Recyclewidget(toggle); Recyclewidget(toggle);
toggle = null; toggle = null;
} }
private async void ConfirmChange(bool newValue)
{
var sociallyDistant = Application.Instance.Context;
var messageDialog = sociallyDistant.Shell.CreateMessageDialog(ConfirmationTitle ?? "Confirm setting change");
messageDialog.Message = ConfirmationMessage ?? "Are you sure you want to change this setting?";
messageDialog.MessageType = MessageBoxType.Warning;
messageDialog.Buttons.Add(new MessageBoxButtonData("Yes", MessageDialogResult.Yes));
messageDialog.Buttons.Add(new MessageBoxButtonData("No", MessageDialogResult.No));
var result = await messageDialog.GetResultAsync();
if (result != MessageDialogResult.Yes)
return;
if (toggle == null)
return;
toggle.ToggleValue = newValue;
Callback?.Invoke(newValue);
}
} }

View file

@ -13,47 +13,66 @@ using SociallyDistant.Core.UI.Common;
namespace SociallyDistant.Core.UI.VisualStyles; namespace SociallyDistant.Core.UI.VisualStyles;
public class SociallyDistantVisualStyle : IVisualStyle public abstract class UserStyle
{
private bool isDirty = true;
protected bool GetDirtyState()
{
var wasDirty = isDirty;
isDirty = false;
return wasDirty;
}
protected void SetDirty()
{
isDirty = true;
}
}
public class SociallyDistantVisualStyle : IVisualStyle,
IGetLayoutProperties<ProgressBar.ProgressBarLayoutProperties>,
IVisualRenderer<ProgressBar.ProgressBarVisualProperties>
{ {
private readonly IGameContext game; private readonly IGameContext game;
private readonly Color mainBackground = new Color(0x11, 0x13, 0x15); private readonly ProgressBarStyle progressBarSTyle = new();
private readonly Color statusBarColor = new Color(0x01, 0x22, 0x37, 0xff); private readonly Color mainBackground = new Color(0x11, 0x13, 0x15);
private readonly Color accentPrimary = new Color(0x13, 0x85, 0xC3, 0xff); private readonly Color statusBarColor = new Color(0x01, 0x22, 0x37, 0xff);
private readonly Color accentEvil = new(0xDE, 0x17, 0x17); private readonly Color accentPrimary = new Color(0x13, 0x85, 0xC3, 0xff);
private readonly Color accentWarning = new Color(0xE0, 0x86, 0x17); private readonly Color accentEvil = new(0xDE, 0x17, 0x17);
private readonly Color accentSuccess = new Color(0x17, 0x82, 0x0E); private readonly Color accentWarning = new Color(0xE0, 0x86, 0x17);
private readonly Color accentCyberspace = new Color(0x34, 0xB1, 0xFD); private readonly Color accentSuccess = new Color(0x17, 0x82, 0x0E);
private readonly Color fieldBackground = new Color(0x25, 0x28, 0x2B); private readonly Color accentCyberspace = new Color(0x34, 0xB1, 0xFD);
private readonly Color fieldStroke = new Color(0x60, 0x64, 0x67); private readonly Color fieldBackground = new Color(0x25, 0x28, 0x2B);
private readonly Color tabInactiveBackgroundDefault = new Color(0x44, 0x44, 0x44, 191); private readonly Color fieldStroke = new Color(0x60, 0x64, 0x67);
private readonly Color tabInactiveBorderDefault = new Color(0x44, 0x44, 0x44); private readonly Color tabInactiveBackgroundDefault = new Color(0x44, 0x44, 0x44, 191);
private readonly Color tabActiveBackgroundDefault = new Color(0x16, 0x93, 0xD6, 190); private readonly Color tabInactiveBorderDefault = new Color(0x44, 0x44, 0x44);
private readonly Color tabActiveBorderDefault = new Color(0x16, 0x93, 0xD6); private readonly Color tabActiveBackgroundDefault = new Color(0x16, 0x93, 0xD6, 190);
private readonly Color sectionTextColor = new(0x85, 0x85, 0x85); private readonly Color tabActiveBorderDefault = new Color(0x16, 0x93, 0xD6);
private readonly Color inputInactiveBorderColor = new Color(0x54, 0x57, 0x5A); private readonly Color sectionTextColor = new(0x85, 0x85, 0x85);
private readonly Color inputInactiveHoveredBorderColor = new Color(0x6F, 0x74, 0x77); private readonly Color inputInactiveBorderColor = new Color(0x54, 0x57, 0x5A);
private readonly Color inputActiveBorderColor = new Color(0x19, 0xA1, 0xEA); private readonly Color inputInactiveHoveredBorderColor = new Color(0x6F, 0x74, 0x77);
private readonly Color inputActiveHoveredBorderColor = new Color(0x80, 0xC3, 0xFD); private readonly Color inputActiveBorderColor = new Color(0x19, 0xA1, 0xEA);
private readonly Color inputInactiveBackground = new Color(0x19, 0x1C, 0x1D); private readonly Color inputActiveHoveredBorderColor = new Color(0x80, 0xC3, 0xFD);
private readonly Color inputInactiveHoveredBackground = new Color(0x2C, 0x2F, 0x32); private readonly Color inputInactiveBackground = new Color(0x19, 0x1C, 0x1D);
private readonly Color inputInactivePressedBackground = new Color(0x1F, 0x22, 0x25); private readonly Color inputInactiveHoveredBackground = new Color(0x2C, 0x2F, 0x32);
private readonly Color inputActiveBackground = new Color(0x13, 0x85, 0xC3); private readonly Color inputInactivePressedBackground = new Color(0x1F, 0x22, 0x25);
private readonly Color inputActiveHoveredBackground = new Color(0x19, 0xA1, 0xEA); private readonly Color inputActiveBackground = new Color(0x13, 0x85, 0xC3);
private readonly Color inputActivePressedBackground = new Color(0x13, 0x85, 0xC3); private readonly Color inputActiveHoveredBackground = new Color(0x19, 0xA1, 0xEA);
private readonly Color buttonBackground = new Color(0x44, 0x44, 0x44); private readonly Color inputActivePressedBackground = new Color(0x13, 0x85, 0xC3);
private readonly Color buttonBorder = new Color(0x16, 0x93, 0xD6); private readonly Color buttonBackground = new Color(0x44, 0x44, 0x44);
private readonly Color buttonHoveredBackground = new Color(0x0F, 0x73, 0xA9); private readonly Color buttonBorder = new Color(0x16, 0x93, 0xD6);
private readonly Color buttonPressedBackground = new Color(0x08, 0x53, 0x7B); private readonly Color buttonHoveredBackground = new Color(0x0F, 0x73, 0xA9);
private readonly Color selectionColor = new(0x08, 0x53, 0x7B); private readonly Color buttonPressedBackground = new Color(0x08, 0x53, 0x7B);
private readonly Color playerBubbleBackground = new Color(0x08, 0x53, 0x7B); private readonly Color selectionColor = new(0x08, 0x53, 0x7B);
private readonly Color playerBubbleBackground = new Color(0x08, 0x53, 0x7B);
private Font iconFont = null!; private Font iconFont = null!;
private IFontFamily defaultFont = null!; private IFontFamily defaultFont = null!;
private IFontFamily monospace = null!; private IFontFamily monospace = null!;
private Texture2D? checkboxEmblem; private Texture2D? checkboxEmblem;
public int ProgressBarHeight => 16;
public int SliderThickness => 18; public int SliderThickness => 18;
public Point ToggleSize => new Point(20, 20); public Point ToggleSize => new Point(20, 20);
public Point SwitchSize => new Point(40, 22); public Point SwitchSize => new Point(40, 22);
@ -540,18 +559,6 @@ public class SociallyDistantVisualStyle : IVisualStyle
} }
} }
public void DrawProgressBar(ProgressBar widget, GeometryHelper geometry, float fillPercentage)
{
int width = widget.ContentArea.Width;
int fillWidth = (int) MathHelper.Clamp(MathHelper.Lerp(0, width, fillPercentage), 0, width);
var background = widget.ContentArea;
var fill = new LayoutRect(background.Left, background.Top, fillWidth, background.Height);
geometry.AddQuad(background, Color.Gray);
geometry.AddQuad(fill, Color.Red);
}
public void DrawSlider( public void DrawSlider(
Slider widget, Slider widget,
GeometryHelper geometry, GeometryHelper geometry,
@ -605,6 +612,56 @@ public class SociallyDistantVisualStyle : IVisualStyle
geometry.AddRoundedRectangleOutline(new LayoutRect(nubOffsetX, nubOffsetY, SliderThickness, SliderThickness), thickness, halfThickness, border); geometry.AddRoundedRectangleOutline(new LayoutRect(nubOffsetX, nubOffsetY, SliderThickness, SliderThickness), thickness, halfThickness, border);
} }
public sealed class ProgressBarStyle : UserStyle,
IGetLayoutProperties<ProgressBar.ProgressBarLayoutProperties>,
IVisualRenderer<ProgressBar.ProgressBarVisualProperties>
{
private readonly int progressBarHeight = 16;
public bool GetLayoutProperties(ref ProgressBar.ProgressBarLayoutProperties layoutProperties)
{
layoutProperties.ProgressBarHeight = progressBarHeight;
return GetDirtyState();
}
public void Draw(
Widget widget,
GeometryHelper geometry,
in LayoutRect contentRect,
ProgressBar.ProgressBarVisualProperties properties
)
{
int width = contentRect.Width;
int fillWidth = (int) MathHelper.Clamp(MathHelper.Lerp(0, width, properties.FillPercentage), 0, width);
var background = contentRect;
var fill = new LayoutRect(background.Left, background.Top, fillWidth, background.Height);
geometry.AddQuad(background, Color.Gray);
geometry.AddQuad(fill, Color.Red);
}
}
public bool GetLayoutProperties(ref ProgressBar.ProgressBarLayoutProperties layoutProperties)
{
return progressBarSTyle.GetLayoutProperties(ref layoutProperties);
}
public void Draw(
Widget widget,
GeometryHelper geometry,
in LayoutRect contentRect,
ProgressBar.ProgressBarVisualProperties properties
)
{
progressBarSTyle.Draw(
widget,
geometry,
in contentRect,
properties
);
}
} }
public enum InputFieldStyle public enum InputFieldStyle

View file

@ -1,4 +1,5 @@
using SociallyDistant.Core.Core.Config; using SociallyDistant.Core.Core.Config;
using SociallyDistant.Core.Modules;
namespace SociallyDistant.Core.Config namespace SociallyDistant.Core.Config
{ {
@ -41,8 +42,14 @@ namespace SociallyDistant.Core.Config
"Mod debug mode", "Mod debug mode",
"Enable or disable Mod Debug Mode. Mod Debug mode sets up a safe, temporary environment for testing in-development mods with, and doesn't allow you to play regular save files. Changing this setting requires restarting the game.", "Enable or disable Mod Debug Mode. Mod Debug mode sets up a safe, temporary environment for testing in-development mods with, and doesn't allow you to play regular save files. Changing this setting requires restarting the game.",
ModDebugMode, ModDebugMode,
x => ModDebugMode = x, x =>
development {
ModDebugMode = x;
Application.Instance.Restart();
},
development,
true,
"Changing this setting requires a game restart. Any unsaved progress will be lost."
); );
} }
} }

View file

@ -1,3 +1,4 @@
using System.Diagnostics;
using Serilog; using Serilog;
using SociallyDistant.Core.Config; using SociallyDistant.Core.Config;
using SociallyDistant.Core.Config.SystemConfigCategories; using SociallyDistant.Core.Config.SystemConfigCategories;
@ -7,14 +8,14 @@ using SociallyDistant.Core.Shell;
namespace SociallyDistant; namespace SociallyDistant;
internal sealed class GameApplication : Application, internal sealed class GameApplication : Application
IDisposable
{ {
private readonly SociallyDistantGame game; private readonly SociallyDistantGame game;
private readonly SettingsManager settingsManager = new(); private readonly SettingsManager settingsManager = new();
public override IGameContext Context => game; public override IGameContext Context => game;
public bool RestartWasRequested { get; private set; }
public ISettingsManager SettingsManager => settingsManager; public ISettingsManager SettingsManager => settingsManager;
public GameApplication() public GameApplication()
@ -52,8 +53,32 @@ internal sealed class GameApplication : Application,
} }
} }
public void Dispose() protected override void OnRestart()
{
game.Exit();
var processPath = Environment.ProcessPath;
var args = Environment.GetCommandLineArgs();
if (!string.IsNullOrWhiteSpace(processPath))
return;
var startInfo = new ProcessStartInfo();
startInfo.FileName = processPath;
foreach (string arg in args)
startInfo.ArgumentList.Add(arg);
startInfo.UseShellExecute = false;
startInfo.WorkingDirectory = Environment.CurrentDirectory;
Process.Start(startInfo);
}
/// <inheritdoc />
public override void Dispose()
{ {
game.Dispose(); game.Dispose();
base.Dispose();
} }
} }

View file

@ -2,6 +2,7 @@
using System.Reactive.Subjects; using System.Reactive.Subjects;
using System.Text; using System.Text;
using AcidicGUI; using AcidicGUI;
using JetBrains.Annotations;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Graphics;
using Serilog; using Serilog;
@ -26,6 +27,7 @@ using SociallyDistant.Core.Shell;
using SociallyDistant.Core.Shell.Windowing; using SociallyDistant.Core.Shell.Windowing;
using SociallyDistant.Core.Social; using SociallyDistant.Core.Social;
using SociallyDistant.Core.UI; using SociallyDistant.Core.UI;
using SociallyDistant.Core.UI.Effects;
using SociallyDistant.DevTools; using SociallyDistant.DevTools;
using SociallyDistant.GamePlatform; using SociallyDistant.GamePlatform;
using SociallyDistant.GamePlatform.ContentManagement; using SociallyDistant.GamePlatform.ContentManagement;
@ -601,7 +603,7 @@ internal sealed class SociallyDistantGame :
Log.Logger = new LoggerConfiguration() Log.Logger = new LoggerConfiguration()
.WriteTo.Console() .WriteTo.Console()
.CreateLogger(); .CreateLogger();
AppDomain.CurrentDomain.UnhandledException += Fuck; AppDomain.CurrentDomain.UnhandledException += Fuck;
void Fuck(object sender, UnhandledExceptionEventArgs e) void Fuck(object sender, UnhandledExceptionEventArgs e)
@ -610,15 +612,24 @@ internal sealed class SociallyDistantGame :
Log.Fatal(e.ExceptionObject.ToString() ?? "Unknown exception details."); Log.Fatal(e.ExceptionObject.ToString() ?? "Unknown exception details.");
} }
restart:
var restartRequested = false;
try try
{ {
using var game = new GameApplication(); using var game = new GameApplication();
game.Start(); game.Start();
restartRequested = game.RestartWasRequested;
} }
finally finally
{ {
Log.CloseAndFlush(); Log.CloseAndFlush();
} }
if (restartRequested)
goto restart;
} }
public static void ScheduleAction(Action action) public static void ScheduleAction(Action action)
@ -692,6 +703,10 @@ internal sealed class SociallyDistantGame :
if (explicitApply) if (explicitApply)
{ {
graphicsManager.PreferredBackBufferWidth = parameters.BackBufferWidth;
graphicsManager.PreferredBackBufferHeight = parameters.BackBufferHeight;
graphicsManager.IsFullScreen = settings.Fullscreen;
graphicsManager.ApplyChanges(); graphicsManager.ApplyChanges();
ApplyVirtualDisplayMode(settings); ApplyVirtualDisplayMode(settings);
} }

View file

@ -83,7 +83,9 @@ public class SystemSettingsController :
{ {
var models = new List<SettingsCategoryModel>(); var models = new List<SettingsCategoryModel>();
foreach (SettingsCategory category in game.SettingsManager.GetCategoriesInSection(sectionTitle)) foreach (SettingsCategory category in game.SettingsManager.GetCategoriesInSection(sectionTitle)
.OrderByDescending(x=>x.Priority)
.ThenBy(x=> x.Name))
{ {
var model = new SettingsCategoryModel var model = new SettingsCategoryModel
{ {

View file

@ -28,7 +28,7 @@ public sealed class DockIconView : Widget
protected override void RebuildGeometry(GeometryHelper geometry) protected override void RebuildGeometry(GeometryHelper geometry)
{ {
var color = GetVisualStyle().SelectionColor; var color = GetVisualStyleOverride().SelectionColor;
if (isActive) if (isActive)
{ {

View file

@ -5,8 +5,13 @@
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/KEEP_EXISTING_INITIALIZER_ARRANGEMENT/@EntryValue">False</s:Boolean> <s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/KEEP_EXISTING_INITIALIZER_ARRANGEMENT/@EntryValue">False</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/KEEP_USER_LINEBREAKS/@EntryValue">False</s:Boolean> <s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/KEEP_USER_LINEBREAKS/@EntryValue">False</s:Boolean>
<s:Int64 x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/MAX_FORMAL_PARAMETERS_ON_LINE/@EntryValue">3</s:Int64> <s:Int64 x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/MAX_FORMAL_PARAMETERS_ON_LINE/@EntryValue">3</s:Int64>
<s:Int64 x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/MAX_PRIMARY_CONSTRUCTOR_PARAMETERS_ON_LINE/@EntryValue">10000</s:Int64> <s:Int64 x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/MAX_INVOCATION_ARGUMENTS_ON_LINE/@EntryValue">3</s:Int64>
<s:Int64 x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/MAX_PRIMARY_CONSTRUCTOR_PARAMETERS_ON_LINE/@EntryValue">3</s:Int64>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_TYPE_CONSTRAINTS_ON_SAME_LINE/@EntryValue">False</s:Boolean> <s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_TYPE_CONSTRAINTS_ON_SAME_LINE/@EntryValue">False</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_AFTER_INVOCATION_LPAR/@EntryValue">True</s:Boolean>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_ARGUMENTS_STYLE/@EntryValue">CHOP_IF_LONG</s:String>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_BEFORE_INVOCATION_RPAR/@EntryValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_BEFORE_PRIMARY_CONSTRUCTOR_DECLARATION_LPAR/@EntryValue">False</s:Boolean>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_EXTENDS_LIST_STYLE/@EntryValue">CHOP_ALWAYS</s:String> <s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_EXTENDS_LIST_STYLE/@EntryValue">CHOP_ALWAYS</s:String>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_LINES/@EntryValue">False</s:Boolean> <s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_LINES/@EntryValue">False</s:Boolean>
@ -15,7 +20,7 @@
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_AFTER_DECLARATION_LPAR/@EntryValue">True</s:Boolean> <s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_AFTER_DECLARATION_LPAR/@EntryValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_BEFORE_PRIMARY_CONSTRUCTOR_DECLARATION_RPAR/@EntryValue">False</s:Boolean> <s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_BEFORE_PRIMARY_CONSTRUCTOR_DECLARATION_RPAR/@EntryValue">False</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_BEFORE_DECLARATION_RPAR/@EntryValue">True</s:Boolean> <s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_BEFORE_DECLARATION_RPAR/@EntryValue">True</s:Boolean>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_PRIMARY_CONSTRUCTOR_PARAMETERS_STYLE/@EntryValue">CHOP_ALWAYS</s:String>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_TERNARY_EXPR_STYLE/@EntryValue">CHOP_ALWAYS</s:String> <s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_TERNARY_EXPR_STYLE/@EntryValue">CHOP_ALWAYS</s:String>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_LINQ_EXPRESSIONS/@EntryValue">CHOP_ALWAYS</s:String> <s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_LINQ_EXPRESSIONS/@EntryValue">CHOP_ALWAYS</s:String>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpKeepExistingMigration/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpKeepExistingMigration/@EntryIndexedValue">True</s:Boolean>