Migrate ErrorForm to Eto and implement dynamic visiblity/form size

This commit is contained in:
Ben Olden-Cooligan 2022-12-09 21:39:46 -08:00
parent 9a3915547a
commit 3b02a34d33
20 changed files with 213 additions and 38 deletions

View File

@ -147,8 +147,11 @@ public class WinFormsEtoPlatform : EtoPlatform
{
if (control.ControlObject is wf.Label label)
{
label.AutoSize = false;
label.Width = 0;
if (!control.ParentWindow.Loaded)
{
label.AutoSize = false;
label.Width = 0;
}
return label.GetPreferredSize(new sd.Size(defaultWidth, 0)).ToEto();
}
return base.GetWrappedSize(control, defaultWidth);

View File

@ -16,7 +16,7 @@ public abstract class EtoDialogBase : Dialog, IFormBase
Resizable = true;
ShowInTaskbar = true;
LayoutController.Bind(this);
LayoutController.ContentSet += (_, _) => FormStateController.UpdateLayoutSize(LayoutController);
LayoutController.Invalidated += (_, _) => FormStateController.UpdateLayoutSize(LayoutController);
}
// TODO: PR for Eto to integrate this

View File

@ -14,7 +14,7 @@ public abstract class EtoFormBase : Form, IFormBase
FormStateController = new FormStateController(this, config);
Resizable = true;
LayoutController.Bind(this);
LayoutController.ContentSet += (_, _) => FormStateController.UpdateLayoutSize(LayoutController);
LayoutController.Invalidated += (_, _) => FormStateController.UpdateLayoutSize(LayoutController);
}
public FormStateController FormStateController { get; }

View File

@ -4,6 +4,7 @@ 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;
@ -46,10 +47,27 @@ public class FormStateController
if (AutoLayoutSize)
{
_minimumClientSize = layoutController.GetLayoutSize(false);
var oldDefaultClientSize = DefaultClientSize;
var oldMaximumClientSize = _maximumClientSize;
DefaultClientSize = layoutController.GetLayoutSize(true) + DefaultExtraLayoutSize;
if (FixedHeightLayout)
_maximumClientSize = FixedHeightLayout ? new Size(0, _minimumClientSize.Height) : Size.Empty;
if (_loaded)
{
_maximumClientSize = new Size(0, _minimumClientSize.Height);
// 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);
}
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);
}
}
}

View File

@ -26,7 +26,7 @@ public class ControlWithLayoutAttributes : LayoutElement
Padding? padding = null, int? spacingAfter = null,
int? width = null, int? minWidth = null, int? maxWidth = null, int? naturalWidth = null,
int? height = null, int? minHeight = null, int? maxHeight = null, int? naturalHeight = null,
int? wrapDefaultWidth = null, LayoutAlignment? alignment = null)
int? wrapDefaultWidth = null, LayoutAlignment? alignment = null, LayoutVisibility? visibility = null)
{
Control = control.Control;
XScale = xScale ?? control.XScale;
@ -43,6 +43,7 @@ public class ControlWithLayoutAttributes : LayoutElement
NaturalHeight = naturalHeight ?? control.NaturalHeight;
WrapDefaultWidth = wrapDefaultWidth ?? control.WrapDefaultWidth;
Alignment = alignment ?? control.Alignment;
Visibility = visibility ?? control.Visibility;
}
public static implicit operator ControlWithLayoutAttributes(Control control) =>
@ -87,6 +88,11 @@ public class ControlWithLayoutAttributes : LayoutElement
public override SizeF GetPreferredSize(LayoutContext context, RectangleF parentBounds)
{
var size = SizeF.Empty;
if (!IsVisible)
{
EnsureIsAdded(context);
return size;
}
if (Control != null)
{
EnsureIsAdded(context);
@ -143,10 +149,15 @@ public class ControlWithLayoutAttributes : LayoutElement
private void EnsureIsAdded(LayoutContext context)
{
if (Control == null) return;
Control.Visible = IsVisible;
if (context.IsFirstLayout && !_isAdded)
{
EtoPlatform.Current.AddToContainer(context.Layout, Control, context.InOverlay);
_isAdded = true;
if (Visibility != null)
{
Visibility.IsVisibleChanged += (_, _) => context.Invalidate();
}
}
if (context.IsFirstLayout && !_isWindowSet && context.Window != null)
{

View File

@ -71,6 +71,8 @@ public static class EtoLayoutExtensions
new ControlWithLayoutAttributes(control, padding: new Padding(all));
public static ControlWithLayoutAttributes Padding(this Control control, int left = 0, int top = 0, int right = 0, int bottom = 0) =>
new ControlWithLayoutAttributes(control, padding: new Padding(left, top, right, bottom));
public static ControlWithLayoutAttributes Visible(this Control control, LayoutVisibility visibility) =>
new ControlWithLayoutAttributes(control, visibility: visibility);
public static ControlWithLayoutAttributes Wrap(this Label label, int defaultWidth)
{
@ -90,6 +92,12 @@ public static class EtoLayoutExtensions
new ControlWithLayoutAttributes(control, minHeight: minHeight);
public static ControlWithLayoutAttributes MaxHeight(this ControlWithLayoutAttributes control, int maxHeight) =>
new ControlWithLayoutAttributes(control, maxHeight: maxHeight);
public static ControlWithLayoutAttributes NaturalSize(this ControlWithLayoutAttributes control, int width, int height) =>
new ControlWithLayoutAttributes(control, naturalWidth: width, naturalHeight: height);
public static ControlWithLayoutAttributes NaturalWidth(this ControlWithLayoutAttributes control, int width) =>
new ControlWithLayoutAttributes(control, naturalWidth: width);
public static ControlWithLayoutAttributes NaturalHeight(this ControlWithLayoutAttributes control, int height) =>
new ControlWithLayoutAttributes(control, naturalHeight: height);
public static ControlWithLayoutAttributes XScale(this ControlWithLayoutAttributes control) =>
new ControlWithLayoutAttributes(control, xScale: true);
public static ControlWithLayoutAttributes YScale(this ControlWithLayoutAttributes control) =>
@ -110,6 +118,8 @@ public static class EtoLayoutExtensions
new ControlWithLayoutAttributes(control, padding: new Padding(all));
public static ControlWithLayoutAttributes Padding(this ControlWithLayoutAttributes control, int left = 0, int top = 0, int right = 0, int bottom = 0) =>
new ControlWithLayoutAttributes(control, padding: new Padding(left, top, right, bottom));
public static ControlWithLayoutAttributes Visible(this ControlWithLayoutAttributes control, LayoutVisibility visibility) =>
new ControlWithLayoutAttributes(control, visibility: visibility);
public static LayoutColumn Padding(this LayoutColumn column, Padding padding) =>
new LayoutColumn(column, padding: padding);

View File

@ -44,13 +44,13 @@ public class LayoutColumn : LayoutLine<LayoutRow>
protected internal override bool DoesChildScale(LayoutElement child) => child.YScale;
protected override int GetSpacing(int i, LayoutContext context)
protected override int GetSpacingCore(int i, LayoutContext context)
{
if (i < Children.Length - 1 && Children[i] is ControlWithLayoutAttributes { Control: Label })
{
return Children[i].SpacingAfter ?? LabelSpacing ?? context.DefaultLabelSpacing;
}
return base.GetSpacing(i, context);
return base.GetSpacingCore(i, context);
}
protected override float GetBreadth(SizeF size) => size.Width;

View File

@ -21,4 +21,6 @@ public record LayoutContext(Control Layout)
public Window? Window { get; init; }
public bool InOverlay { get; init; }
public Action Invalidate { get; init; }
}

View File

@ -11,6 +11,7 @@ public class LayoutController
private Window? _window;
private bool _firstLayout = true;
private bool _isShown;
private bool _layoutQueued;
public LayoutElement? Content
{
@ -22,7 +23,7 @@ public class LayoutController
DoLayout();
if (setting)
{
ContentSet?.Invoke(this, EventArgs.Empty);
Invalidated?.Invoke(this, EventArgs.Empty);
}
}
}
@ -55,9 +56,17 @@ public class LayoutController
return naturalSize;
}
public void DoLayout()
public void Invalidate()
{
if (_window == null || _content == null || !_isShown) return;
_layoutQueued = true;
Invalidated?.Invoke(this, EventArgs.Empty);
_layoutQueued = false;
DoLayout();
}
private void DoLayout()
{
if (_window == null || _content == null || !_isShown || _layoutQueued) return;
// TODO: Handle added/removed things
var size = EtoPlatform.Current.GetClientSize(_window);
int p = RootPadding;
@ -82,9 +91,10 @@ public class LayoutController
{
DefaultSpacing = DefaultSpacing,
DefaultLabelSpacing = DefaultLabelSpacing,
IsFirstLayout = _firstLayout
IsFirstLayout = _firstLayout,
Invalidate = Invalidate
};
}
public event EventHandler? ContentSet;
public event EventHandler? Invalidated;
}

View File

@ -16,6 +16,8 @@ public abstract class LayoutElement
protected internal bool XScale { get; set; }
protected internal bool YScale { get; set; }
protected internal LayoutAlignment Alignment { get; set; }
protected internal LayoutVisibility? Visibility { get; set; }
protected internal bool IsVisible => Visibility?.IsVisible ?? true;
protected internal int? SpacingAfter { get; set; }
public static implicit operator LayoutElement(Control control) =>

View File

@ -55,9 +55,18 @@ public abstract class LayoutLine<TOrthogonal> : LayoutContainer
}
}
protected virtual int GetSpacing(int i, LayoutContext context)
private int GetSpacing(int i, LayoutContext context)
{
if (Children.Skip(i + 1).All(child => !child.IsVisible))
{
return 0;
}
if (!Children[i].IsVisible) return 0;
return GetSpacingCore(i, context);
}
protected virtual int GetSpacingCore(int i, LayoutContext context)
{
if (i == Children.Length - 1) return 0;
return Children[i].SpacingAfter ?? Spacing ?? context.DefaultSpacing;
}
@ -111,19 +120,20 @@ public abstract class LayoutLine<TOrthogonal> : LayoutContainer
{
// If this line is supposed to be aligned with adjacent lines (e.g. 2 rows in a parent column or vice versa),
// then our parent will have pre-calculated our cell sizes and scaling for us.
cellLengths = Aligned ? context.CellLengths : null;
cellScaling = Aligned ? context.CellScaling : null;
if (Aligned && context.CellLengths != null && context.CellScaling != null)
{
cellLengths = context.CellLengths;
cellScaling = context.CellScaling;
return;
}
// If we aren't aligned or we don't have a parent to do that pre-calculation, then we just determine our cell
// sizes and scaling directly without any special alignment constraints.
if (cellLengths == null || cellScaling == null)
cellLengths = new List<float>();
cellScaling = new List<bool>();
foreach (var child in Children)
{
cellLengths = new List<float>();
cellScaling = new List<bool>();
foreach (var child in Children)
{
cellLengths.Add(GetLength(child.GetPreferredSize(childContext, bounds)));
cellScaling.Add(DoesChildScale(child));
}
cellLengths.Add(GetLength(child.GetPreferredSize(childContext, bounds)));
cellScaling.Add(DoesChildScale(child));
}
}

View File

@ -0,0 +1,22 @@
namespace NAPS2.EtoForms.Layout;
public class LayoutVisibility
{
private bool _isVisible;
public LayoutVisibility(bool isVisible)
{
_isVisible = isVisible;
}
public bool IsVisible
{
get => _isVisible; set
{
_isVisible = value;
IsVisibleChanged?.Invoke(this, EventArgs.Empty);
}
}
public event EventHandler? IsVisibleChanged;
}

View File

@ -1,14 +1,15 @@
using Eto.Forms;
using NAPS2.EtoForms.Ui;
namespace NAPS2.EtoForms;
public class MessageBoxErrorOutput : ErrorOutput
{
private readonly DialogHelper _dialogHelper;
private readonly IFormFactory _formFactory;
public MessageBoxErrorOutput(DialogHelper dialogHelper)
public MessageBoxErrorOutput(IFormFactory formFactory)
{
_dialogHelper = dialogHelper;
_formFactory = formFactory;
}
public override void DisplayError(string errorMessage)
@ -25,15 +26,12 @@ public class MessageBoxErrorOutput : ErrorOutput
{
Invoker.Current.SafeInvoke(() => ShowErrorWithDetails(errorMessage, exception.ToString()));
}
private void ShowErrorWithDetails(string errorMessage, string details)
{
// TODO: Migrate error form
MessageBox.Show(errorMessage, MessageBoxType.Error);
// var form = new FError
// {
// ErrorMessage = errorMessage,
// Details = details
// };
// form.ShowDialog();
var form = _formFactory.Create<ErrorForm>();
form.ErrorMessage = errorMessage;
form.Details = details;
form.ShowModal();
}
}

View File

@ -91,7 +91,7 @@ public class EmailSettingsForm : EtoDialogBase
_provider.Text = SettingsResources.EmailProvider_NotSelected;
break;
}
LayoutController.DoLayout();
LayoutController.Invalidate();
}
private void Save()

View File

@ -0,0 +1,52 @@
using Eto.Forms;
using NAPS2.EtoForms.Layout;
namespace NAPS2.EtoForms.Ui;
public class ErrorForm : EtoDialogBase
{
private readonly Label _message = new();
private readonly TextArea _details = new() { ReadOnly = true };
private readonly LayoutVisibility _detailsVisibility = new(false);
public ErrorForm(Naps2Config config)
: base(config)
{
Title = UiStrings.ErrorFormTitle;
FormStateController.RestoreFormState = false;
var image = new ImageView { Image = Icons.exclamation.ToEtoImage() };
FormStateController.FixedHeightLayout = true;
LayoutController.Content = L.Column(
L.Row(
image.AlignCenter().Padding(right: 5),
_message.Wrap(350).NaturalWidth(350).AlignCenter().XScale()
),
L.Row(
C.Link(UiStrings.TechnicalDetails, ToggleDetails).AlignCenter(),
C.Filler(),
C.OkButton(this)
),
_details.NaturalHeight(120).Visible(_detailsVisibility).YScale()
);
}
private void ToggleDetails()
{
FormStateController.FixedHeightLayout = _detailsVisibility.IsVisible;
_detailsVisibility.IsVisible = !_detailsVisibility.IsVisible;
}
public string ErrorMessage
{
get => _message.Text;
set => _message.Text = value;
}
public string Details
{
get => _details.Text;
set => _details.Text = value;
}
}

View File

@ -459,6 +459,16 @@ namespace NAPS2 {
}
}
/// <summary>
/// Looks up a localized resource of type System.Byte[].
/// </summary>
internal static byte[] exclamation {
get {
object obj = ResourceManager.GetObject("exclamation", resourceCulture);
return ((byte[])(obj));
}
}
/// <summary>
/// Looks up a localized resource of type System.Byte[].
/// </summary>

View File

@ -370,4 +370,7 @@
<data name="favicon" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>Icons\favicon.ico;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
<data name="exclamation" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>Icons\exclamation.png;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
</root>

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@ -671,6 +671,15 @@ namespace NAPS2.Lang.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to Error.
/// </summary>
internal static string ErrorFormTitle {
get {
return ResourceManager.GetString("ErrorFormTitle", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Estimated download size: {0} MB.
/// </summary>
@ -1481,6 +1490,15 @@ namespace NAPS2.Lang.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to Technical Details.
/// </summary>
internal static string TechnicalDetails {
get {
return ResourceManager.GetString("TechnicalDetails", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Tiff Options.
/// </summary>

View File

@ -636,4 +636,10 @@
<data name="WaitingForAuthorization" xml:space="preserve">
<value>Waiting for authorization...</value>
</data>
<data name="ErrorFormTitle" xml:space="preserve">
<value>Error</value>
</data>
<data name="TechnicalDetails" xml:space="preserve">
<value>Technical Details</value>
</data>
</root>