Compare commits

...

5 Commits

13 changed files with 426 additions and 171 deletions

@ -2,6 +2,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using OpenTK.Windowing.Desktop; using OpenTK.Windowing.Desktop;
using OpenTK.Windowing.GraphicsLibraryFramework; using OpenTK.Windowing.GraphicsLibraryFramework;
using Quik.CommandMachine;
using Quik.Media; using Quik.Media;
using Quik.OpenGL; using Quik.OpenGL;
using Quik.PAL; using Quik.PAL;
@ -24,7 +25,7 @@ namespace Quik.OpenTK
private bool IsGLInitialized = false; private bool IsGLInitialized = false;
public IQuikPort CreatePort() public IQuikPortHandle CreatePort()
{ {
NativeWindow window = new NativeWindow(DefaultSettings); NativeWindow window = new NativeWindow(DefaultSettings);
OpenTKPort port = new OpenTKPort(window); OpenTKPort port = new OpenTKPort(window);
@ -33,7 +34,7 @@ namespace Quik.OpenTK
if (!IsGLInitialized) if (!IsGLInitialized)
{ {
window.Context.MakeCurrent(); window.Context.MakeCurrent();
GL.LoadBindings((string proc) => GLFW.GetProcAddress(proc)); GL.LoadBindings(GLFW.GetProcAddress);
IsGLInitialized = true; IsGLInitialized = true;
} }
@ -60,5 +61,31 @@ namespace Quik.OpenTK
{ {
NativeWindow.ProcessWindowEvents(block); NativeWindow.ProcessWindowEvents(block);
} }
public void DestroyPort(IQuikPortHandle port) => ((OpenTKPort)port).Dispose();
public string PortGetTitle(IQuikPortHandle port) => ((OpenTKPort)port).Title;
public void PortSetTitle(IQuikPortHandle port, string title) => ((OpenTKPort)port).Title = title;
public QVec2 PortGetSize(IQuikPortHandle port) => ((OpenTKPort)port).Size;
public void PortSetSize(IQuikPortHandle port, QVec2 size) => ((OpenTKPort)port).Size = size;
public QVec2 PortGetPosition(IQuikPortHandle port) => ((OpenTKPort)port).Position;
public void PortSetPosition(IQuikPortHandle port, QVec2 position) => ((OpenTKPort)port).Position = position;
public bool PortIsValid(IQuikPortHandle port) => ((OpenTKPort)port).IsValid;
public void PortSubscribeEvent(IQuikPortHandle port, EventHandler handler) => ((OpenTKPort)port).EventRaised += handler;
public void PortUnsubscribeEvent(IQuikPortHandle port, EventHandler handler) => ((OpenTKPort)port).EventRaised -= handler;
public void PortFocus(IQuikPortHandle port) => ((OpenTKPort)port).Focus();
public void PortShow(IQuikPortHandle port, bool shown = true) => ((OpenTKPort)port).Show(shown);
public void PortPaint(IQuikPortHandle port, CommandList commands) => ((OpenTKPort)port).Paint(commands);
} }
} }

@ -8,7 +8,7 @@ using Quik.VertexGenerator;
namespace Quik.OpenTK namespace Quik.OpenTK
{ {
public class OpenTKPort : IQuikPort public class OpenTKPort : IQuikPortHandle
{ {
private readonly NativeWindow _window; private readonly NativeWindow _window;
private readonly GL21Driver _glDriver; private readonly GL21Driver _glDriver;
@ -70,7 +70,7 @@ namespace Quik.OpenTK
QRectangle view = new QRectangle(Size, new QVec2(0, 0)); QRectangle view = new QRectangle(Size, new QVec2(0, 0));
_vertexEngine.Reset(); _vertexEngine.Reset();
_vertexEngine.ProcessCommands(new QRectangle(), queue); _vertexEngine.ProcessCommands(view, queue);
if (!_window.Context.IsCurrent) if (!_window.Context.IsCurrent)
_window.Context.MakeCurrent(); _window.Context.MakeCurrent();

@ -36,7 +36,7 @@ namespace Quik.CommandMachine
public void ProcessCommands(QRectangle bounds, CommandList queue) public void ProcessCommands(QRectangle bounds, CommandList queue)
{ {
CommandEnumerator iterator = queue.GetEnumerator(); CommandQueue iterator = queue.GetEnumerator();
if (!iterator.Peek().IsCommand) if (!iterator.Peek().IsCommand)
throw new ArgumentException("The first element in the iterator must be a command frame."); throw new ArgumentException("The first element in the iterator must be a command frame.");
@ -103,7 +103,7 @@ namespace Quik.CommandMachine
} }
} }
protected virtual void ChildProcessCommand(Command name, CommandEnumerator queue) protected virtual void ChildProcessCommand(Command name, CommandQueue queue)
{ {
} }
@ -119,7 +119,7 @@ namespace Quik.CommandMachine
_matrixStack.Push(QMat4.Identity); _matrixStack.Push(QMat4.Identity);
} }
private void ConditionalHandler(CommandEnumerator iterator) private void ConditionalHandler(CommandQueue iterator)
{ {
Frame frame = iterator.Dequeue(); Frame frame = iterator.Dequeue();

@ -4,5 +4,5 @@ namespace Quik.CommandMachine
/// A delegate for a QUIK command. /// A delegate for a QUIK command.
/// </summary> /// </summary>
/// <param name="stack">The current stack.</param> /// <param name="stack">The current stack.</param>
public delegate void QuikCommandHandler(CommandEngine state, CommandEnumerator queue); public delegate void QuikCommandHandler(CommandEngine state, CommandQueue queue);
} }

@ -2,7 +2,7 @@ using System;
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Reflection.Emit; using System.Runtime.CompilerServices;
namespace Quik.CommandMachine namespace Quik.CommandMachine
{ {
@ -231,12 +231,19 @@ namespace Quik.CommandMachine
Enqueue(uv); Enqueue(uv);
} }
public void Image(QuikTexture texture, QRectangle[] rectangles, bool interleavedUV = false) public void Image(QuikTexture texture, ReadOnlySpan<QRectangle> rectangles, bool interleavedUV = false)
{ {
ImageCommandFlags flags = interleavedUV ? ImageCommandFlags.UVs : ImageCommandFlags.None; int count = rectangles.Length;
ImageCommandFlags flags = ImageCommandFlags.None;
if (interleavedUV)
{
count /= 2;
flags |= ImageCommandFlags.UVs;
}
Enqueue(Command.Image); Enqueue(Command.Image);
Enqueue(new Frame((int)flags, rectangles.Length / 2)); Enqueue(new Frame(count));
Enqueue(new Frame(texture)); Enqueue(new Frame(texture));
foreach (QRectangle rectangle in rectangles) foreach (QRectangle rectangle in rectangles)
@ -245,7 +252,7 @@ namespace Quik.CommandMachine
} }
} }
public void Image(QuikTexture texture, QRectangle[] rectangles, QRectangle[] uvs) public void Image(QuikTexture texture, ReadOnlySpan<QRectangle> rectangles, ReadOnlySpan<QRectangle> uvs)
{ {
int count = Math.Min(rectangles.Length, uvs.Length); int count = Math.Min(rectangles.Length, uvs.Length);
Enqueue(Command.Image); Enqueue(Command.Image);
@ -259,20 +266,44 @@ namespace Quik.CommandMachine
} }
} }
public void Splice(CommandList queue) public void Image3D(QuikTexture texture, in Image3DCall call)
{ {
foreach (Frame frame in queue) Enqueue(Command.Image);
Enqueue(new Frame(ImageCommandFlags.Image3d | ImageCommandFlags.Single));
Enqueue(new Frame(texture));
Enqueue(call.Rectangle);
Enqueue(call.UVs);
Enqueue(new Frame(call.Layer));
}
public void Image3D(QuikTexture texture, ReadOnlySpan<Image3DCall> calls)
{
Enqueue(Command.Image);
Enqueue(new Frame((int)ImageCommandFlags.Image3d, calls.Length));
Enqueue(new Frame(texture));
foreach (Image3DCall call in calls)
{
Enqueue(call.Rectangle);
Enqueue(call.UVs);
Enqueue(new Frame(call.Layer));
}
}
public void Splice(CommandList list)
{
foreach (Frame frame in list)
{ {
Enqueue(frame); Enqueue(frame);
} }
} }
public CommandEnumerator GetEnumerator() => new CommandEnumerator(_frames); public CommandQueue GetEnumerator() => new CommandQueue(_frames);
IEnumerator<Frame> IEnumerable<Frame>.GetEnumerator() => GetEnumerator(); IEnumerator<Frame> IEnumerable<Frame>.GetEnumerator() => GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
} }
public class CommandEnumerator : IEnumerator<Frame> public class CommandQueue : IEnumerator<Frame>
{ {
private readonly IReadOnlyList<Frame> _frames; private readonly IReadOnlyList<Frame> _frames;
private int _current; private int _current;
@ -281,7 +312,7 @@ namespace Quik.CommandMachine
object IEnumerator.Current => Current; object IEnumerator.Current => Current;
public CommandEnumerator(IReadOnlyList<Frame> frames) public CommandQueue(IReadOnlyList<Frame> frames)
{ {
_current = -1; _current = -1;
_frames = frames; _frames = frames;

@ -9,22 +9,22 @@ namespace Quik.CommandMachine
[FieldOffset(0)] [FieldOffset(0)]
private FrameType _type; private FrameType _type;
[FieldOffset(sizeof(FrameType) + 0*sizeof(int))] [FieldOffset(sizeof(FrameType) + 0 * sizeof(int))]
private int _i1; private int _i1;
[FieldOffset(sizeof(FrameType) + 1*sizeof(int))] [FieldOffset(sizeof(FrameType) + 1 * sizeof(int))]
private int _i2; private int _i2;
[FieldOffset(sizeof(FrameType) + 2*sizeof(int))] [FieldOffset(sizeof(FrameType) + 2 * sizeof(int))]
private int _i3; private int _i3;
[FieldOffset(sizeof(FrameType) + 3*sizeof(int))] [FieldOffset(sizeof(FrameType) + 3 * sizeof(int))]
private int _i4; private int _i4;
[FieldOffset(sizeof(FrameType) + 0*sizeof(float))] [FieldOffset(sizeof(FrameType) + 0 * sizeof(float))]
private float _f1; private float _f1;
[FieldOffset(sizeof(FrameType) + 1*sizeof(float))] [FieldOffset(sizeof(FrameType) + 1 * sizeof(float))]
private float _f2; private float _f2;
[FieldOffset(sizeof(FrameType) + 2*sizeof(float))] [FieldOffset(sizeof(FrameType) + 2 * sizeof(float))]
private float _f3; private float _f3;
[FieldOffset(sizeof(FrameType) + 3*sizeof(float))] [FieldOffset(sizeof(FrameType) + 3 * sizeof(float))]
private float _f4; private float _f4;
[FieldOffset(24)] [FieldOffset(24)]
@ -36,7 +36,7 @@ namespace Quik.CommandMachine
_type == FrameType.IVec2 || _type == FrameType.IVec2 ||
_type == FrameType.IVec3 || _type == FrameType.IVec3 ||
_type == FrameType.IVec4; _type == FrameType.IVec4;
public bool IsFloat => public bool IsFloat =>
_type == FrameType.Vec1 || _type == FrameType.Vec1 ||
_type == FrameType.Vec2 || _type == FrameType.Vec2 ||
_type == FrameType.Vec3 || _type == FrameType.Vec3 ||
@ -48,16 +48,16 @@ namespace Quik.CommandMachine
{ {
switch (_type) switch (_type)
{ {
case FrameType.None: case FrameType.None:
return 0; return 0;
default: default:
return 1; return 1;
case FrameType.Vec2: case FrameType.IVec2: case FrameType.Vec2: case FrameType.IVec2:
return 2; return 2;
case FrameType.Vec3: case FrameType.IVec3: case FrameType.Vec3: case FrameType.IVec3:
return 3; return 3;
case FrameType.Vec4: case FrameType.IVec4: case FrameType.Vec4: case FrameType.IVec4:
return 4; return 4;
} }
} }
} }
@ -78,10 +78,11 @@ namespace Quik.CommandMachine
_type = FrameType.None _type = FrameType.None
}; };
#region Constructors
public Frame(Command command) : this() public Frame(Command command) : this()
{ {
_type = FrameType.Command; _type = FrameType.Command;
_i1 = (int)command; _i1 = (int)command;
} }
public Frame(object o) public Frame(object o)
@ -195,6 +196,8 @@ namespace Quik.CommandMachine
_f4 = f4; _f4 = f4;
} }
#endregion
public T As<T>() public T As<T>()
{ {
return (T)_object; return (T)_object;
@ -204,12 +207,12 @@ namespace Quik.CommandMachine
{ {
switch (i) switch (i)
{ {
case 0: return _f1; case 0: return _f1;
case 1: return _f2; case 1: return _f2;
case 2: return _f3; case 2: return _f3;
case 3: return _f4; case 3: return _f4;
default: default:
throw new ArgumentOutOfRangeException(); throw new ArgumentOutOfRangeException();
} }
} }
@ -217,30 +220,118 @@ namespace Quik.CommandMachine
{ {
switch (i) switch (i)
{ {
case 0: return _i1; case 0: return _i1;
case 1: return _i2; case 1: return _i2;
case 2: return _i3; case 2: return _i3;
case 3: return _i4; case 3: return _i4;
default: default:
throw new ArgumentOutOfRangeException(); throw new ArgumentOutOfRangeException();
} }
} }
public static explicit operator int(in Frame frame) => frame._i1; #region Frame->T Conversion
public static explicit operator float(in Frame frame) => frame._f1;
public static explicit operator Command(in Frame frame) => (Command)frame._i1; public static explicit operator int(in Frame frame)
public static explicit operator QVec2(in Frame frame) => {
frame.IsFloat ? new QVec2(frame._f1, frame._f2) : new QVec2(frame._i1, frame._i2); switch (frame.Type)
public static explicit operator QColor(in Frame frame) => {
new QColor((byte)frame._i1, (byte)frame._i2, (byte)frame._i3, (byte)frame._i4); default:
public static explicit operator QRectangle(in Frame frame) => throw new InvalidCastException();
frame.IsFloat ? case FrameType.Command:
new QRectangle(frame._f1, frame._f2, frame._f3, frame._f4) : case FrameType.IVec1:
new QRectangle(frame._i1, frame._i2, frame._i3, frame._i4); case FrameType.IVec2:
public static explicit operator QLine(in Frame frame) => case FrameType.IVec3:
frame.IsFloat ? case FrameType.IVec4:
new QLine(frame._f1, frame._f2, frame._f3, frame._f4) : return frame._i1;
new QLine(frame._i1, frame._i2, frame._i3, frame._i4); case FrameType.Vec1:
case FrameType.Vec2:
case FrameType.Vec3:
case FrameType.Vec4:
return (int)frame._f1;
}
}
public static explicit operator float(in Frame frame)
{
switch (frame.Type)
{
default:
throw new InvalidCastException();
case FrameType.IVec1:
case FrameType.IVec2:
case FrameType.IVec3:
case FrameType.IVec4:
return frame._i1;
case FrameType.Vec1:
case FrameType.Vec2:
case FrameType.Vec3:
case FrameType.Vec4:
return frame._f1;
}
}
public static explicit operator Command(in Frame frame)
{
if (frame.Type != FrameType.Command)
{
throw new InvalidCastException("Not a command frame.");
}
return (Command)frame._i1;
}
public static explicit operator QVec2(in Frame frame)
{
switch (frame.Type)
{
default:
throw new InvalidCastException();
case FrameType.IVec2:
case FrameType.IVec3:
case FrameType.IVec4:
return new QVec2(frame._i1, frame._i2);
case FrameType.Vec2:
case FrameType.Vec3:
case FrameType.Vec4:
return new QVec2(frame._f1, frame._f2);
}
}
public static explicit operator QColor(in Frame frame)
{
if (frame.Type != FrameType.IVec4)
throw new InvalidCastException();
return new QColor((byte)frame._i1, (byte)frame._i2, (byte)frame._i3, (byte)frame._i4);
}
public static explicit operator QRectangle(in Frame frame)
{
switch (frame.Type)
{
default:
throw new InvalidCastException();
case FrameType.IVec4:
return new QRectangle(frame._i1, frame._i2, frame._i3, frame._i4);
case FrameType.Vec4:
return new QRectangle(frame._f1, frame._f2, frame._f3, frame._f4);
}
}
public static explicit operator QLine(in Frame frame)
{
switch (frame.Type)
{
default:
throw new InvalidCastException();
case FrameType.IVec4:
return new QLine(frame._i1, frame._i2, frame._i3, frame._i4);
case FrameType.Vec4:
return new QLine(frame._f1, frame._f2, frame._f3, frame._f4);
}
}
#endregion
public static explicit operator Frame(int i) => new Frame(i); public static explicit operator Frame(int i) => new Frame(i);
public static explicit operator Frame(float f) => new Frame(f); public static explicit operator Frame(float f) => new Frame(f);

@ -0,0 +1,18 @@
namespace Quik.CommandMachine
{
public enum ImageCommandFlags
{
None = 0,
Single = 1 << 0,
UVs = 1 << 1,
Image3d = 1 << 2,
}
public struct Image3DCall
{
public QRectangle Rectangle;
public QRectangle UVs;
public int Layer;
}
}

@ -1,10 +0,0 @@
namespace Quik.CommandMachine
{
public enum ImageCommandFlags
{
None = 0,
Single = 1 << 0,
UVs = 1 << 1
}
}

@ -1,38 +0,0 @@
using System;
using Quik.Media;
namespace Quik.PAL
{
/// <summary>
/// The primary primary platform abstraction interface for Quik hosts.
/// </summary>
public interface IQuikPlatform : IDisposable
{
/// <summary>
/// The title of the application.
/// </summary>
string Title { get; set; }
/// <summary>
/// The default icon for the application.
/// </summary>
QImage Icon { get; set; }
/// <summary>
/// The event raised when an event is received.
/// </summary>
event EventHandler EventRaised;
/// <summary>
/// Create a window.
/// </summary>
/// <returns>The window instance.</returns>
IQuikPort CreatePort();
/// <summary>
/// Raise the events that have been enqueued.
/// </summary>
/// <param name="block">True to block until a new event arrives.</param>
void ProcessEvents(bool block);
}
}

59
Quik/PAL/IQuikPlatform.cs Normal file

@ -0,0 +1,59 @@
using System;
using Quik.CommandMachine;
using Quik.Media;
namespace Quik.PAL
{
/// <summary>
/// An empty interface to statically type Quik port handles.
/// </summary>
public interface IQuikPortHandle
{
}
/// <summary>
/// The primary primary platform abstraction interface for Quik hosts.
/// </summary>
public interface IQuikPlatform : IDisposable
{
/// <summary>
/// The title of the application.
/// </summary>
string Title { get; set; }
/// <summary>
/// The default icon for the application.
/// </summary>
QImage Icon { get; set; }
/// <summary>
/// The event raised when an event is received.
/// </summary>
event EventHandler EventRaised;
/// <summary>
/// Raise the events that have been enqueued.
/// </summary>
/// <param name="block">True to block until a new event arrives.</param>
void ProcessEvents(bool block);
/// <summary>
/// Create a window.
/// </summary>
/// <returns>The window instance.</returns>
IQuikPortHandle CreatePort();
void DestroyPort(IQuikPortHandle port);
string PortGetTitle(IQuikPortHandle port);
void PortSetTitle(IQuikPortHandle port, string title);
QVec2 PortGetSize(IQuikPortHandle port);
void PortSetSize(IQuikPortHandle port, QVec2 size);
QVec2 PortGetPosition(IQuikPortHandle port);
void PortSetPosition(IQuikPortHandle port, QVec2 position);
bool PortIsValid(IQuikPortHandle port);
void PortSubscribeEvent(IQuikPortHandle port, EventHandler handler);
void PortUnsubscribeEvent(IQuikPortHandle port, EventHandler handler);
void PortFocus(IQuikPortHandle port);
void PortShow(IQuikPortHandle port, bool shown = true);
void PortPaint(IQuikPortHandle port, CommandList commands);
}
}

@ -1,49 +0,0 @@
using System;
using Quik.CommandMachine;
namespace Quik.PAL
{
/// <summary>
/// An abstraction over the a window or a rendering context.
/// </summary>
public interface IQuikPort : IDisposable
{
/// <summary>
/// Title of the window, if applicable.
/// </summary>
string Title { get; set; }
/// <summary>
/// Size of the window.
/// </summary>
QVec2 Size { get; set; }
/// <summary>
/// Position of the window in the coordinate system.
/// </summary>
QVec2 Position { get; set; }
bool IsValid { get; }
/// <summary>
/// Called when an event for this port is raised.
/// </summary>
event EventHandler EventRaised;
/// <summary>
/// Focus the window, bringing it to the front.
/// </summary>
void Focus();
/// <summary>
/// Show or hide the window.
/// </summary>
/// <param name="shown">True to show the window, false to hide.</param>
void Show(bool shown = true);
/// <summary>
/// Paint the given command queue onto the port.
/// </summary>
/// <param name="queue">The command queue to paint.</param>
void Paint(CommandList queue);
}
}

92
Quik/PAL/QuikPort.cs Normal file

@ -0,0 +1,92 @@
using Quik.CommandMachine;
using Quik.Controls;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Quik.PAL
{
/// <summary>
/// An abstraction layer over the UI input and output.
/// </summary>
public class QuikPort
{
private readonly IQuikPortHandle handle;
private readonly IQuikPlatform platform;
public string Title
{
get => platform.PortGetTitle(handle);
set => platform.PortSetTitle(handle, value);
}
public QVec2 Size
{
get => platform.PortGetSize(handle);
set => platform.PortSetSize(handle, value);
}
public QVec2 Position
{
get => platform.PortGetPosition(handle);
set => platform.PortSetPosition(handle, value);
}
public UIBase UIElement { get; set; }
public bool IsValid => platform.PortIsValid(handle);
public event EventHandler EventRaised
{
add
{
platform.PortSubscribeEvent(handle, value);
}
remove
{
platform.PortUnsubscribeEvent(handle, value);
}
}
public QuikPort(IQuikPlatform platform)
{
this.platform = platform;
handle = platform.CreatePort();
}
bool isDisposed = false;
public void Dispose()
{
if (isDisposed) return;
platform.DestroyPort(handle);
isDisposed = true;
}
public void Focus()
{
platform.PortFocus(handle);
}
public void Paint(CommandList list = null)
{
if (UIElement == null)
return;
if(list == null)
list = new CommandList();
list.Clear();
UIElement.Bounds = new QRectangle(Size, new QVec2(0,0));
UIElement.Paint(list);
platform.PortPaint(handle, list);
}
public void Show(bool shown = true)
{
platform.PortShow(handle, shown);
}
}
}

@ -4,6 +4,7 @@ using Quik.CommandMachine;
using Quik.Controls; using Quik.Controls;
using Quik.Media; using Quik.Media;
using Quik.PAL; using Quik.PAL;
using Quik.Typography;
namespace Quik namespace Quik
{ {
@ -35,7 +36,9 @@ namespace Quik
set => Platform.Icon = value; set => Platform.Icon = value;
} }
public View MainView { get; private set; } = null; public QuikPort MainPort { get; private set; } = null;
public FontProvider FontProvider { get; }
/// <summary> /// <summary>
/// List of media loaders, drivers that load media such as images and fonts. /// List of media loaders, drivers that load media such as images and fonts.
@ -45,25 +48,56 @@ namespace Quik
public QuikApplication(IQuikPlatform platform) public QuikApplication(IQuikPlatform platform)
{ {
Platform = platform; Platform = platform;
FontProvider = new FontProvider(this);
}
public IDisposable GetMedia(object key, MediaHint hint)
{
IDisposable disposable = null;
foreach (MediaLoader loader in MediaLoaders)
{
disposable = loader.GetMedia(key, hint);
if (disposable != null)
break;
}
return disposable;
}
public IDisposable GetMedia<T>(T key, MediaHint hint)
{
IDisposable disposable = null;
foreach (MediaLoader loader in MediaLoaders)
{
if (loader is MediaLoader<T> typedLoader)
{
disposable = typedLoader.GetMedia(key, hint);
if (disposable != null)
break;
}
}
return disposable;
} }
public void Run(View mainView) public void Run(View mainView)
{ {
IQuikPort port = Platform.CreatePort(); MainPort = new QuikPort(Platform) { UIElement = mainView };
CommandList cmd = new CommandList(); CommandList cmd = new CommandList();
MainView = mainView; MainPort.EventRaised += (sender, ea) => mainView.NotifyEvent(sender, ea);
port.EventRaised += (sender, ea) => mainView.NotifyEvent(sender, ea); while (MainPort.IsValid)
while (port.IsValid)
{ {
Platform.ProcessEvents(false); Platform.ProcessEvents(false);
if (port.IsValid) if (MainPort.IsValid)
{ {
cmd.Clear(); cmd.Clear();
MainView.Paint(cmd); MainPort.Paint(cmd);
port.Paint(cmd);
} }
} }
} }