mirror of
https://github.com/cyanfish/naps2.git
synced 2024-09-11 07:15:31 +03:00
Mac: Listview and thumbnail rendering mostly working
Still some minor issues around selection and multi-select, plus a few UI tweaks to go
This commit is contained in:
parent
445c476689
commit
b1e26712d0
@ -80,23 +80,23 @@ public class MacImageTransformer : AbstractImageTransformer<MacImage>
|
||||
ImagePixelFormat.ARGB32 => ImagePixelFormat.ARGB32,
|
||||
_ => throw new ArgumentException("Unsupported pixel format")
|
||||
};
|
||||
var newImage = (MacImage) ImageContext.Create(transform.Size, transform.Size, pixelFormat);
|
||||
var (left, top, width, height) = transform.GetDrawRect(image.Width, image.Height);
|
||||
var newImage = (MacImage) ImageContext.Create(width, height, pixelFormat);
|
||||
newImage.SetResolution(
|
||||
image.HorizontalResolution * image.Width / width,
|
||||
image.VerticalResolution * image.Height / height);
|
||||
using CGBitmapContext c = GetCgBitmapContext(newImage);
|
||||
CGRect rect = new CGRect(left, top, width, height);
|
||||
CGRect rect = new CGRect(0, 0, width, height);
|
||||
c.DrawImage(rect, image._imageRep.AsCGImage(ref rect, null, null));
|
||||
|
||||
CGRect strokeRect = new CGRect(left + 0.5, top + 0.5, width - 1, height - 1);
|
||||
#if MONOMAC
|
||||
c.SetRGBStrokeColor(
|
||||
#else
|
||||
c.SetStrokeColor(
|
||||
#endif
|
||||
0.ToNFloat(), 0.ToNFloat(), 0.ToNFloat(), 255.ToNFloat());
|
||||
c.StrokeRect(strokeRect);
|
||||
// CGRect strokeRect = new CGRect(left + 0.5, top + 0.5, width - 1, height - 1);
|
||||
// #if MONOMAC
|
||||
// c.SetRGBStrokeColor(
|
||||
// #else
|
||||
// c.SetStrokeColor(
|
||||
// #endif
|
||||
// 0.ToNFloat(), 0.ToNFloat(), 0.ToNFloat(), 255.ToNFloat());
|
||||
// c.StrokeRect(strokeRect);
|
||||
|
||||
return newImage;
|
||||
}
|
||||
|
@ -1,11 +1,16 @@
|
||||
using CoreAnimation;
|
||||
using Eto.Drawing;
|
||||
using Eto.Forms;
|
||||
using Eto.Mac;
|
||||
|
||||
namespace NAPS2.EtoForms.Mac;
|
||||
|
||||
public class MacListView<T> : IListView<T> where T : notnull
|
||||
public class MacListView<T> : NSCollectionViewDelegateFlowLayout, IListView<T> where T : notnull
|
||||
{
|
||||
private readonly NSCollectionView _view = new();
|
||||
private readonly ListViewBehavior<T> _behavior;
|
||||
private readonly NSCollectionView _view = new();
|
||||
private readonly NSCollectionViewFlowLayout _layout;
|
||||
private readonly DataSource<T> _dataSource;
|
||||
|
||||
private ListSelection<T> _selection = ListSelection.Empty<T>();
|
||||
private bool _refreshing;
|
||||
@ -13,12 +18,24 @@ public class MacListView<T> : IListView<T> where T : notnull
|
||||
public MacListView(ListViewBehavior<T> behavior)
|
||||
{
|
||||
_behavior = behavior;
|
||||
_layout = new NSCollectionViewFlowLayout
|
||||
{
|
||||
SectionInset = new NSEdgeInsets(20, 20, 20, 20),
|
||||
MinimumInteritemSpacing = 15,
|
||||
MinimumLineSpacing = 15
|
||||
};
|
||||
_dataSource = new DataSource<T>(this, _behavior);
|
||||
_view.DataSource = _dataSource;
|
||||
_view.Delegate = this;
|
||||
_view.CollectionViewLayout = _layout;
|
||||
_view.Selectable = true;
|
||||
_view.AllowsMultipleSelection = true;
|
||||
}
|
||||
|
||||
public int ImageSize
|
||||
{
|
||||
get => 0;
|
||||
set { }
|
||||
get => (int) _layout.ItemSize.Width;
|
||||
set => _layout.ItemSize = new CGSize(value, value);
|
||||
}
|
||||
|
||||
// TODO: Properties here vs on behavior?
|
||||
@ -45,15 +62,32 @@ public class MacListView<T> : IListView<T> where T : notnull
|
||||
|
||||
public void SetItems(IEnumerable<T> items)
|
||||
{
|
||||
_dataSource.Items.Clear();
|
||||
_dataSource.Items.AddRange(items);
|
||||
_view.ReloadData();
|
||||
}
|
||||
|
||||
// TODO: Do we need this method? Clean up the name/doc at least
|
||||
public void RegenerateImages()
|
||||
{
|
||||
_view.ReloadData();
|
||||
}
|
||||
|
||||
public void ApplyDiffs(ListViewDiffs<T> diffs)
|
||||
{
|
||||
foreach (var op in diffs.AppendOperations)
|
||||
{
|
||||
_dataSource.Items.Add(op.Item);
|
||||
}
|
||||
foreach (var op in diffs.ReplaceOperations)
|
||||
{
|
||||
_dataSource.Items[op.Index] = op.Item;
|
||||
}
|
||||
foreach (var op in diffs.TrimOperations)
|
||||
{
|
||||
_dataSource.Items.RemoveRange(_dataSource.Items.Count - op.Count, op.Count);
|
||||
}
|
||||
_view.ReloadData();
|
||||
}
|
||||
|
||||
public ListSelection<T> Selection
|
||||
@ -66,7 +100,113 @@ public class MacListView<T> : IListView<T> where T : notnull
|
||||
return;
|
||||
}
|
||||
_selection = value ?? throw new ArgumentNullException(nameof(value));
|
||||
UpdateViewSelection();
|
||||
SelectionChanged?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateViewSelection()
|
||||
{
|
||||
if (!_refreshing)
|
||||
{
|
||||
_refreshing = true;
|
||||
_view.SelectionIndexes =
|
||||
NSIndexSet.FromArray(_selection.ToSelectedIndices(_dataSource.Items).ToArray());
|
||||
_refreshing = false;
|
||||
}
|
||||
}
|
||||
|
||||
public override void ItemsSelected(NSCollectionView collectionView, NSSet indexPaths)
|
||||
{
|
||||
_selection = ListSelection.FromSelectedIndices(_dataSource.Items,
|
||||
indexPaths.Cast<NSIndexPath>().Select(x => (int) x.Item));
|
||||
SelectionChanged?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
public override void ItemsChanged(NSCollectionView collectionView, NSSet indexPaths, NSCollectionViewItemHighlightState highlightState)
|
||||
{
|
||||
UpdateViewSelection();
|
||||
}
|
||||
|
||||
public override CGSize SizeForItem(NSCollectionView collectionView, NSCollectionViewLayout collectionViewLayout, NSIndexPath indexPath)
|
||||
{
|
||||
var item = _dataSource.Items[(int) indexPath.Item];
|
||||
var size = _behavior.GetImage(item, ImageSize).Size;
|
||||
var max = (double) Math.Max(size.Width, size.Height);
|
||||
return new CGSize(size.Width * ImageSize / max, size.Height * ImageSize / max);
|
||||
}
|
||||
}
|
||||
|
||||
public class DataSource<T> : NSCollectionViewDataSource where T : notnull
|
||||
{
|
||||
private readonly IListView<T> _listView;
|
||||
private readonly ListViewBehavior<T> _behavior;
|
||||
|
||||
public DataSource(IListView<T> listView, ListViewBehavior<T> behavior)
|
||||
{
|
||||
_listView = listView;
|
||||
_behavior = behavior;
|
||||
}
|
||||
|
||||
public List<T> Items { get; } = new();
|
||||
|
||||
public override nint GetNumberofItems(NSCollectionView collectionView, nint section)
|
||||
{
|
||||
return Items.Count;
|
||||
}
|
||||
|
||||
public override NSCollectionViewItem GetItem(NSCollectionView collectionView, NSIndexPath indexPath)
|
||||
{
|
||||
var i = (int) indexPath.Item;
|
||||
return new Cell
|
||||
{
|
||||
CellImage = _behavior.GetImage(Items[i], _listView.ImageSize)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public class Cell : NSCollectionViewItem
|
||||
{
|
||||
private bool _selected;
|
||||
|
||||
public Image CellImage { get; set; }
|
||||
|
||||
public override void LoadView()
|
||||
{
|
||||
var imageView = new NSImageView
|
||||
{
|
||||
Image = CellImage.ToNS()
|
||||
};
|
||||
Console.WriteLine("Setting up imageview layer " + CellImage.Width + " " + CellImage.Height);
|
||||
imageView.WantsLayer = true;
|
||||
imageView.CanDrawSubviewsIntoLayer = true;
|
||||
imageView.Frame = new CGRect(0, 0, CellImage.Width, CellImage.Height);
|
||||
var layer = new CALayer();
|
||||
layer.Frame = new CGRect(0, 0, CellImage.Width, CellImage.Height);
|
||||
layer.CornerRadius = 4;
|
||||
layer.MasksToBounds = true;
|
||||
layer.Contents = CellImage.ToCG();
|
||||
layer.ZPosition = 1000;
|
||||
imageView.Layer = layer;
|
||||
View = imageView;
|
||||
UpdateViewForSelectedState();
|
||||
}
|
||||
|
||||
public override bool Selected
|
||||
{
|
||||
get => _selected;
|
||||
set
|
||||
{
|
||||
_selected = value;
|
||||
UpdateViewForSelectedState();
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateViewForSelectedState()
|
||||
{
|
||||
Console.WriteLine("UpdateViewForSelectedState " + Selected);
|
||||
var layer = ((NSImageView) View).Layer;
|
||||
layer.BorderWidth = Selected ? 4 : 1;
|
||||
layer.BorderColor = Selected ? NSColor.SelectedContentBackground.ToCG() : NSColor.Black.ToCG();
|
||||
}
|
||||
}
|
@ -1,3 +1,4 @@
|
||||
using System.Threading;
|
||||
using Eto.Drawing;
|
||||
using Eto.Forms;
|
||||
using Eto.Mac;
|
||||
@ -29,10 +30,21 @@ public class MacDesktopForm : DesktopForm
|
||||
{
|
||||
}
|
||||
|
||||
protected override void SetContent(Control content)
|
||||
{
|
||||
var scrollView = new NSScrollView();
|
||||
scrollView.DocumentView = content.ToNative();
|
||||
Content = scrollView.ToEto();
|
||||
}
|
||||
|
||||
protected override void OnLoad(EventArgs e)
|
||||
{
|
||||
// TODO: What's the best place to initialize this? It needs to happen from the UI event loop.
|
||||
Invoker.Current = new SyncContextInvoker(SynchronizationContext.Current);
|
||||
base.OnLoad(e);
|
||||
ClientSize = new Size(1000, 600);
|
||||
// TODO: Initialize everything that needs to be initialized where it's best
|
||||
ResizeThumbnails(Config.ThumbnailSize());
|
||||
}
|
||||
|
||||
protected override void CreateToolbarsAndMenus()
|
||||
@ -126,13 +138,15 @@ public class MacDesktopForm : DesktopForm
|
||||
window.ToolbarStyle = NSWindowToolbarStyle.Unified;
|
||||
// TODO: Subtitle based on active profile?
|
||||
window.Subtitle = "Not Another PDF Scanner 2";
|
||||
// TODO: Do we want full size content?
|
||||
window.StyleMask |= NSWindowStyle.FullSizeContentView;
|
||||
window.StyleMask |= NSWindowStyle.UnifiedTitleAndToolbar;
|
||||
}
|
||||
|
||||
private void ZoomUpdated(NSSlider sender)
|
||||
{
|
||||
Config.User.Set(c => c.ThumbnailSize, sender.IntValue);
|
||||
var size = ThumbnailSizes.CurveToSize(sender.DoubleValue);
|
||||
ResizeThumbnails(size);
|
||||
}
|
||||
|
||||
public class ToolbarDelegate : NSToolbarDelegate
|
||||
@ -179,9 +193,9 @@ public class MacDesktopForm : DesktopForm
|
||||
{
|
||||
View = new NSSlider
|
||||
{
|
||||
MinValue = ThumbnailSizes.MIN_SIZE,
|
||||
MaxValue = ThumbnailSizes.MAX_SIZE,
|
||||
IntValue = _form.Config.ThumbnailSize()
|
||||
MinValue = 0,
|
||||
MaxValue = 1,
|
||||
DoubleValue = ThumbnailSizes.SizeToCurve(_form.Config.ThumbnailSize())
|
||||
}.WithAction(_form.ZoomUpdated),
|
||||
MaxSize = new CGSize(64, 999)
|
||||
},
|
||||
|
@ -70,6 +70,18 @@ public class WinFormsDesktopForm : DesktopForm
|
||||
_toolbarFormatter.RelayoutToolbar(_toolStrip);
|
||||
}
|
||||
|
||||
protected override void SetThumbnailSpacing(int thumbnailSize)
|
||||
{
|
||||
_listView.NativeControl.Padding = new Padding(0, 20, 0, 0);
|
||||
const int MIN_PADDING = 6;
|
||||
const int MAX_PADDING = 66;
|
||||
// Linearly scale the padding with the thumbnail size
|
||||
int padding = MIN_PADDING + (MAX_PADDING - MIN_PADDING) * (thumbnailSize - ThumbnailSizes.MIN_SIZE) /
|
||||
(ThumbnailSizes.MAX_SIZE - ThumbnailSizes.MIN_SIZE);
|
||||
int spacing = thumbnailSize + padding * 2;
|
||||
WinFormsHacks.SetListSpacing(_listView.NativeControl, spacing, spacing);
|
||||
}
|
||||
|
||||
protected override void CreateToolbarButton(Command command)
|
||||
{
|
||||
var item = new wf.ToolStripButton
|
||||
|
@ -32,6 +32,8 @@ public class WinFormsModule : NinjectModule
|
||||
Bind<IDesktopScanController>().To<DesktopScanController>();
|
||||
Bind<IDesktopSubFormController>().To<DesktopSubFormController>();
|
||||
Bind<DesktopFormProvider>().ToSelf().InSingletonScope();
|
||||
Bind<ImageContext>().To<GdiImageContext>();
|
||||
Bind<GdiImageContext>().ToSelf();
|
||||
|
||||
Bind<DesktopForm>().To<WinFormsDesktopForm>();
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
using System.Collections.Immutable;
|
||||
using System.Threading;
|
||||
using Eto.Forms;
|
||||
using NAPS2.ImportExport.Images;
|
||||
using NAPS2.WinForms;
|
||||
@ -72,7 +73,7 @@ public abstract class DesktopForm : EtoFormBase
|
||||
private readonly ListProvider<Command> _scanMenuCommands = new();
|
||||
private readonly ListProvider<Command> _languageMenuCommands = new();
|
||||
|
||||
private IListView<UiImage> _listView;
|
||||
protected IListView<UiImage> _listView;
|
||||
private ImageListSyncer? _imageListSyncer;
|
||||
// private LayoutManager _layoutManager;
|
||||
|
||||
@ -364,6 +365,10 @@ public abstract class DesktopForm : EtoFormBase
|
||||
// imageList.ImagesUpdated += (_, _) => Invoker.Current.SafeInvoke(UpdateToolbar);
|
||||
_profileManager.ProfilesUpdated += (_, _) => UpdateScanButton();
|
||||
_desktopFormProvider.DesktopForm = this;
|
||||
|
||||
// TODO: Initialization needs work
|
||||
_imageListSyncer = new ImageListSyncer(_imageList, _listView.ApplyDiffs, SynchronizationContext.Current!);
|
||||
_desktopController.Initialize();
|
||||
}
|
||||
|
||||
protected virtual void CreateToolbarsAndMenus()
|
||||
@ -874,48 +879,35 @@ public abstract class DesktopForm : EtoFormBase
|
||||
// int thumbnailSize = Config.ThumbnailSize();
|
||||
// thumbnailSize =
|
||||
// (int) ThumbnailSizes.StepNumberToSize(ThumbnailSizes.SizeToStepNumber(thumbnailSize) + step);
|
||||
// thumbnailSize = ThumbnailSizes.Validate(thumbnailSize);
|
||||
// Config.User.Set(c => c.ThumbnailSize, thumbnailSize);
|
||||
// ResizeThumbnails(thumbnailSize);
|
||||
// }
|
||||
//
|
||||
// private void ResizeThumbnails(int thumbnailSize)
|
||||
// {
|
||||
// if (!_imageList.Images.Any())
|
||||
// {
|
||||
// // Can't show visual feedback so don't do anything
|
||||
// // TODO: This is wrong?
|
||||
// return;
|
||||
// }
|
||||
// if (_listView.ImageSize == thumbnailSize)
|
||||
// {
|
||||
// // Same size so no resizing needed
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// // Adjust the visible thumbnail display with the new size
|
||||
// _listView.ImageSize = thumbnailSize;
|
||||
// _listView.RegenerateImages();
|
||||
//
|
||||
// SetThumbnailSpacing(thumbnailSize);
|
||||
// UpdateToolbar(); // TODO: Do we need this?
|
||||
//
|
||||
// // Render high-quality thumbnails at the new size in a background task
|
||||
// // The existing (poorly scaled) thumbnails are used in the meantime
|
||||
// _thumbnailRenderQueue.SetThumbnailSize(thumbnailSize);
|
||||
// }
|
||||
//
|
||||
// private void SetThumbnailSpacing(int thumbnailSize)
|
||||
// {
|
||||
// _listView.NativeControl.Padding = new Padding(0, 20, 0, 0);
|
||||
// const int MIN_PADDING = 6;
|
||||
// const int MAX_PADDING = 66;
|
||||
// // Linearly scale the padding with the thumbnail size
|
||||
// int padding = MIN_PADDING + (MAX_PADDING - MIN_PADDING) * (thumbnailSize - ThumbnailSizes.MIN_SIZE) /
|
||||
// (ThumbnailSizes.MAX_SIZE - ThumbnailSizes.MIN_SIZE);
|
||||
// int spacing = thumbnailSize + padding * 2;
|
||||
// WinFormsHacks.SetListSpacing(_listView.NativeControl, spacing, spacing);
|
||||
// }
|
||||
protected void ResizeThumbnails(int thumbnailSize)
|
||||
{
|
||||
thumbnailSize = ThumbnailSizes.Validate(thumbnailSize);
|
||||
Config.User.Set(c => c.ThumbnailSize, thumbnailSize);
|
||||
if (_listView.ImageSize == thumbnailSize)
|
||||
{
|
||||
// Same size so no resizing needed
|
||||
return;
|
||||
}
|
||||
|
||||
// Adjust the visible thumbnail display with the new size
|
||||
_listView.ImageSize = thumbnailSize;
|
||||
_listView.RegenerateImages();
|
||||
|
||||
SetThumbnailSpacing(thumbnailSize);
|
||||
UpdateToolbar(); // TODO: Do we need this?
|
||||
|
||||
// Render high-quality thumbnails at the new size in a background task
|
||||
// The existing (poorly scaled) thumbnails are used in the meantime
|
||||
_thumbnailRenderQueue.SetThumbnailSize(thumbnailSize);
|
||||
}
|
||||
|
||||
protected virtual void SetThumbnailSpacing(int thumbnailSize)
|
||||
{
|
||||
}
|
||||
|
||||
//
|
||||
// private void btnZoomOut_Click(object sender, EventArgs e) => StepThumbnailSize(-1);
|
||||
// private void btnZoomIn_Click(object sender, EventArgs e) => StepThumbnailSize(1);
|
||||
|
@ -74,7 +74,7 @@ public class ThumbnailRenderQueue : IDisposable
|
||||
{
|
||||
// TODO: Make this run as async?
|
||||
// TODO: Verify WorkerFactory is not null? Or handle this better for tests?
|
||||
bool useWorker = PlatformCompat.Runtime.UseWorker;
|
||||
bool useWorker = PlatformCompat.System.RenderInWorker;
|
||||
var worker = useWorker ? _scanningContext.WorkerFactory?.Create() : null;
|
||||
var fallback = new ExpFallback(100, 60 * 1000);
|
||||
while (true)
|
||||
@ -174,7 +174,7 @@ public class ThumbnailRenderQueue : IDisposable
|
||||
foreach (var img in listCopy)
|
||||
{
|
||||
var thumb = img.GetThumbnailClone();
|
||||
if (thumb == null || thumb.Width != thumbnailSize || thumb.Height != thumbnailSize)
|
||||
if (thumb == null || thumb.Width != thumbnailSize && thumb.Height != thumbnailSize)
|
||||
{
|
||||
return img;
|
||||
}
|
||||
|
@ -31,6 +31,9 @@
|
||||
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
|
||||
<_Parameter1>NAPS2.Lib.WinForms</_Parameter1>
|
||||
</AssemblyAttribute>
|
||||
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
|
||||
<_Parameter1>NAPS2.Lib.Mac</_Parameter1>
|
||||
</AssemblyAttribute>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(Configuration)' == 'Debug'">
|
||||
|
@ -9,7 +9,7 @@ public static class ThumbnailSizes
|
||||
public static int Validate(int inputSize)
|
||||
{
|
||||
return inputSize.Clamp(MIN_SIZE, MAX_SIZE);
|
||||
}
|
||||
}
|
||||
|
||||
public static double StepNumberToSize(double stepNumber)
|
||||
{
|
||||
@ -45,4 +45,18 @@ public static class ThumbnailSizes
|
||||
}
|
||||
return (size - 832) / 96 + 16;
|
||||
}
|
||||
|
||||
public static int CurveToSize(double value)
|
||||
{
|
||||
value = value.Clamp(0, 1);
|
||||
var curved = (Math.Exp(value) - 1) / (Math.E - 1);
|
||||
return (int) Math.Round(MIN_SIZE + curved * (MAX_SIZE - MIN_SIZE));
|
||||
}
|
||||
|
||||
public static double SizeToCurve(int size)
|
||||
{
|
||||
size = Validate(size);
|
||||
var curved = (size - MIN_SIZE) / (double) (MAX_SIZE - MIN_SIZE);
|
||||
return Math.Log(curved * (Math.E - 1) + 1);
|
||||
}
|
||||
}
|
@ -14,6 +14,8 @@ public interface ISystemCompat
|
||||
|
||||
bool UseSystemTesseract { get; }
|
||||
|
||||
bool RenderInWorker { get; }
|
||||
|
||||
string? TesseractExecutablePath { get; }
|
||||
|
||||
string PdfiumLibraryPath { get; }
|
||||
|
@ -19,6 +19,8 @@ public class LinuxSystemCompat : ISystemCompat
|
||||
|
||||
public bool UseSystemTesseract => true;
|
||||
|
||||
public bool RenderInWorker => false;
|
||||
|
||||
public string? TesseractExecutablePath => null;
|
||||
|
||||
public string PdfiumLibraryPath => "_linux/libpdfium.so";
|
||||
|
@ -20,6 +20,8 @@ public class MacSystemCompat : ISystemCompat
|
||||
|
||||
public bool UseSystemTesseract => true;
|
||||
|
||||
public bool RenderInWorker => false;
|
||||
|
||||
public string? TesseractExecutablePath => null;
|
||||
|
||||
public string PdfiumLibraryPath =>
|
||||
|
@ -17,6 +17,8 @@ public abstract class WindowsSystemCompat : ISystemCompat
|
||||
|
||||
public bool UseSystemTesseract => false;
|
||||
|
||||
public bool RenderInWorker => true;
|
||||
|
||||
public abstract string? TesseractExecutablePath { get; }
|
||||
|
||||
public abstract string PdfiumLibraryPath { get; }
|
||||
|
30
NAPS2.Sdk/Threading/SyncContextInvoker.cs
Normal file
30
NAPS2.Sdk/Threading/SyncContextInvoker.cs
Normal file
@ -0,0 +1,30 @@
|
||||
using System.Threading;
|
||||
|
||||
namespace NAPS2.Threading;
|
||||
|
||||
public class SyncContextInvoker : IInvoker
|
||||
{
|
||||
private readonly SynchronizationContext _current;
|
||||
|
||||
public SyncContextInvoker(SynchronizationContext current)
|
||||
{
|
||||
_current = current;
|
||||
}
|
||||
|
||||
public void Invoke(Action action)
|
||||
{
|
||||
_current.Send(_ => action(), null);
|
||||
}
|
||||
|
||||
public void SafeInvoke(Action action)
|
||||
{
|
||||
_current.Send(_ => action(), null);
|
||||
}
|
||||
|
||||
public T InvokeGet<T>(Func<T> func)
|
||||
{
|
||||
T value = default!;
|
||||
_current.Send(_ => value = func(), null);
|
||||
return value;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user