naps2/NAPS2.Lib/EtoForms/FormStateController.cs
2024-08-05 10:40:59 -07:00

182 lines
6.3 KiB
C#

using Eto.Drawing;
using Eto.Forms;
using NAPS2.EtoForms.Layout;
namespace NAPS2.EtoForms;
// TODO: Verify all the size handling works correctly with maximized forms.
public class FormStateController
{
private readonly Window _window;
private readonly Naps2Config _config;
private FormState? _formState;
private bool _loaded;
private bool _hasSetSize;
private Size _minimumClientSize;
private Size _maximumClientSize;
public FormStateController(Window window, Naps2Config config)
{
_window = window;
_config = config;
window.SizeChanged += OnResize;
window.LocationChanged += OnMove;
window.PreLoad += OnLoadInternal;
window.Shown += OnShownInternal;
window.Closed += OnClosed;
}
public bool SaveFormState { get; set; } = true;
public bool RestoreFormState { get; set; } = true;
public bool AutoLayoutSize { get; set; } = true;
public Size DefaultExtraLayoutSize { get; set; }
public Size DefaultClientSize { get; set; }
public bool FixedHeightLayout { get; set; }
public bool Resizable { get; set; } = true;
public string FormName => _window.GetType().Name;
public void UpdateLayoutSize(LayoutController layoutController)
{
if (AutoLayoutSize)
{
_minimumClientSize = layoutController.GetLayoutSize(false);
var oldDefaultClientSize = DefaultClientSize;
var oldMaximumClientSize = _maximumClientSize;
DefaultClientSize = layoutController.GetLayoutSize(true) + DefaultExtraLayoutSize;
_maximumClientSize = FixedHeightLayout ? new Size(0, _minimumClientSize.Height) : Size.Empty;
if (_loaded)
{
// Dynamic re-sizing because the layout contents have changed (not just a normal resize/maximize etc).
var size = EtoPlatform.Current.GetClientSize(_window);
if (oldMaximumClientSize.Height > 0)
{
size.Height = Math.Min(size.Height, oldMaximumClientSize.Height);
}
// TODO: Maybe we can add a flag to do this behavior? It makes it so that changes to the layout size
// after the form is loaded cause the form size to change proportionally (even if we're still within
// our min/max bounds). This is causing problems for dynamically sized images/labels etc. but maybe
// there's a world where we want to re-enable this for some forms.
// size += DefaultClientSize - oldDefaultClientSize;
size = Size.Max(size, _minimumClientSize);
if (_maximumClientSize.Height > 0)
{
size.Height = Math.Min(size.Height, _maximumClientSize.Height);
}
EtoPlatform.Current.SetMinimumClientSize(_window, _minimumClientSize);
EtoPlatform.Current.SetClientSize(_window, size);
}
}
}
private void OnLoadInternal(object? sender, EventArgs eventArgs)
{
if (RestoreFormState || SaveFormState)
{
var formStates = _config.Get(c => c.FormStates);
_formState = formStates.SingleOrDefault(x => x.Name == FormName) ?? new FormState {Name = FormName};
}
if (RestoreFormState)
{
DoRestoreFormState();
}
if (!_hasSetSize && !DefaultClientSize.IsEmpty)
{
// TODO: Use size delta to re-center
EtoPlatform.Current.SetClientSize(_window, DefaultClientSize);
}
_loaded = true;
}
private void OnShownInternal(object? sender, EventArgs e)
{
if (!_minimumClientSize.IsEmpty)
{
EtoPlatform.Current.SetMinimumClientSize(_window, _minimumClientSize);
}
_window.Resizable = Resizable;
}
protected void DoRestoreFormState()
{
if (_formState == null)
{
throw new InvalidOperationException();
}
var location = new Point(_formState.Location.X, _formState.Location.Y);
var size = new Size(_formState.Size.Width, _formState.Size.Height);
if (!location.IsZero)
{
if (Screen.Screens.Any(x => x.WorkingArea.Contains(location)))
{
// Only move to the specified location if it's onscreen
// It might be offscreen if the user has disconnected a monitor
EtoPlatform.Current.SetFormLocation(_window, location);
}
}
if (!size.IsEmpty && Resizable)
{
if (!_minimumClientSize.IsEmpty)
{
size = Size.Max(size, _minimumClientSize);
}
if (_maximumClientSize.Height > 0)
{
size.Height = Math.Min(size.Height, _maximumClientSize.Height);
}
EtoPlatform.Current.SetClientSize(_window, size);
_hasSetSize = true;
}
if (_formState.Maximized && _window.Resizable)
{
_window.WindowState = WindowState.Maximized;
}
}
private void OnResize(object? sender, EventArgs eventArgs)
{
if (_loaded && _formState != null && SaveFormState)
{
_formState.Maximized = (_window.WindowState == WindowState.Maximized);
if (_window.WindowState == WindowState.Normal)
{
var size = EtoPlatform.Current.GetClientSize(_window);
_formState.Size = new FormState.FormSize(size.Width, size.Height);
}
}
}
private void OnMove(object? sender, EventArgs eventArgs)
{
if (_loaded && _formState != null && SaveFormState)
{
if (_window.WindowState == WindowState.Normal)
{
_formState.Location = new FormState.FormLocation(_window.Location.X, _window.Location.Y);
}
}
}
private void OnClosed(object? sender, EventArgs eventArgs)
{
DoSaveFormState();
}
public void DoSaveFormState()
{
if (SaveFormState && _formState != null)
{
var formStates = _config.Get(c => c.FormStates);
formStates = formStates.RemoveAll(fs => fs.Name == FormName).Add(_formState);
_config.User.Set(c => c.FormStates, formStates);
}
}
}