Skip to main content
Version: 0.9.0

GuiPanelControl class

The session's GUI panels: shared 2D surfaces in the room showing a web page or a video stream. Reached through Session.GuiPanels.

Namespace: Quaternar.Qaros · Assembly: Quaternar.Qaros.dll · Source: GuiPanelControl.cs

public sealed class GuiPanelControl

Remarks​

Panel state is shared session state and eventually consistent. A change method returns success once it has published the change; the change reaches the shared state, and every peer's snapshots, later, and may still be refused there. A read right after a write can return the old value: observe the result through GuiPanelControl.SubscribeUpdates instead of reading back once. A change to a panel this session does not know yet fails rather than waiting for it.

Every method throws ObjectDisposedException once the session is disposed.

Methods​

MemberDescription
GetOrCreate(GuiPanelOptions)Returns the panel identified by GuiPanelOptions.CommonName, creating it from options if it does not exist yet.
SubscribeGestureEvents(GuiPanelId, GuiPanelGestureKind, EventHandler<GuiPanelGestureEventArgs>, CancellationToken)Subscribe to one kind of gesture the rendering peers report for one panel. Several producers publish, one per user: key lifecycles on (source peer, gesture id). Disposing the result stops delivery.
SubscribeAllGestureEvents(GuiPanelId, EventHandler<GuiPanelGestureEventArgs>, CancellationToken)Subscribe to every gesture kind on one panel through one handler. Prefer this when taking more than one kind: everything arrives in the order the producer published it.
UpdatePose(GuiPanelId, Pose)Moves a panel. pose is in room space.
ChangeSize(GuiPanelId, GuiPanelSize)Changes the panel's size in metres and its content scale. A fast-path change that this session's GuiPanelControl.GetAll shows at once.
SetState(GuiPanelId, GuiPanelState)Shows, minimizes or hides the panel for every peer. This, not the visible-to list, is how a panel is hidden. GuiPanelState.Closed is the same as GuiPanelControl.Close.
Close(GuiPanelId)Closes the panel for every peer: its GuiPanel.State becomes GuiPanelState.Closed. GuiPanelControl.GetOrCreate with the same common name reopens it.
NavigateToUri(GuiPanelId, string)Points a web panel at another URI. A stream panel fails with StatusCode.GuiPanelChangeNotPermitted: content type is fixed at creation.
SetStream(GuiPanelId, StreamId)Switches a stream panel to another video stream, which must be a video sender owned by this session. A web panel fails with StatusCode.GuiPanelChangeNotPermitted: content type is fixed at creation.
UpdateVisibleTo(GuiPanelId, PeerId[], PeerId[])Adds and removes peers from the panel's visible-to list. An empty list means the panel is visible to every peer: removing the last peer shows it to everyone again, it never hides it. Hide a panel through its state instead.
GetAll()Snapshot of every panel the session's shared storage holds, with this session's own pending pose and size changes applied. Blocks for a network round trip of about 2 s on every call.
SubscribeUpdates()Starts delivering updates of every panel through the GuiPanelControl.Updated event until the result is disposed.
SubscribeUpdates(CancellationToken)Starts delivering updates of every panel through the GuiPanelControl.Updated event until the result is disposed.
SubscribeUpdates(EventHandler<GuiPanelUpdatedEventArgs>)Calls handler once for every panel already stored, then again for every change to any panel, until the result is disposed.
SubscribeUpdates(EventHandler<GuiPanelUpdatedEventArgs>, CancellationToken)Calls handler once for every panel already stored, then again for every change to any panel, until the result is disposed.
WatchUpdatesAsync(CancellationToken)The snapshots of GuiPanelControl.SubscribeUpdates as an async stream: every stored panel first, then every change.
SubscribeUpdates(GuiPanelId)Starts delivering one panel's updates through the GuiPanelControl.Updated event until the result is disposed.
SubscribeUpdates(GuiPanelId, CancellationToken)Starts delivering one panel's updates through the GuiPanelControl.Updated event until the result is disposed.
SubscribeUpdates(GuiPanelId, EventHandler<GuiPanelUpdatedEventArgs>)Calls handler for one panel: first with its stored state, when it already exists, then for every change to it, until the result is disposed.
SubscribeUpdates(GuiPanelId, EventHandler<GuiPanelUpdatedEventArgs>, CancellationToken)Calls handler for one panel: first with its stored state, when it already exists, then for every change to it, until the result is disposed.
WatchUpdatesAsync(GuiPanelId, CancellationToken)The snapshots of GuiPanelControl.SubscribeUpdates as an async stream.

GetOrCreate​

Returns the panel identified by GuiPanelOptions.CommonName, creating it from options if it does not exist yet.

public Result<GuiPanelId> GetOrCreate(GuiPanelOptions options)

Parameters

NameTypeDescription
optionsGuiPanelOptions

Returns: Result<GuiPanelId>

Exceptions

ExceptionCondition
ArgumentNullExceptionoptions is null.
ArgumentExceptionGuiPanelOptions.CommonName is null, or both a URI and a stream id are set.

Remarks

The id is derived from the common name alone, so the same common name gives the same panel in every session, from every peer and after a restart: call this on every start instead of persisting the id. The common name is the identity key and must be ASCII; put user-facing text, which may be any UTF-8, in GuiPanelOptions.DisplayName.

When an open panel of that name exists, the rest of options is ignored. A closed panel is reopened, visible, with options.

The content is a web page (GuiPanelOptions.ContentUri) or a video stream (GuiPanelOptions.ContentStreamId, a video sender owned by this session) and cannot change type later; with neither the panel shows the default web page. A panel meant only for some peers is best created with them in GuiPanelOptions.VisibleToPeers rather than narrowed afterwards.

SubscribeGestureEvents​

Subscribe to one kind of gesture the rendering peers report for one panel. Several producers publish, one per user: key lifecycles on (source peer, gesture id). Disposing the result stops delivery.

public Result<IDisposable> SubscribeGestureEvents(GuiPanelId id, GuiPanelGestureKind gestureKind, EventHandler<GuiPanelGestureEventArgs> handler, CancellationToken cancellationToken = default)

Parameters

NameTypeDescription
idGuiPanelId
gestureKindGuiPanelGestureKind
handlerEventHandler<GuiPanelGestureEventArgs>
cancellationTokenCancellationToken

Returns: Result<IDisposable>

Exceptions

ExceptionCondition
ArgumentNullExceptionhandler is null.

Remarks

The peer that draws the panel recognises hover, tap, long press and drag from its user's hands; positions arrive in panel-content-space pixels. Every panel reports gestures, web panels included.

The gesture id is unique only within a source peer: two users' first drags both carry id 1. Order holds within one producer, not across producers, and a source peer never seen before is a new user. A live lifecycle is republished every 250 ms even when nothing changed; treat one silent for three intervals as cancelled, which is what recovers from a crashed producer. A missed started phase is recoverable because every update carries its start position.

Separate per-kind subscriptions have no order relative to each other; to take more than one kind, prefer GuiPanelControl.SubscribeAllGestureEvents. The handler runs on a library thread and an exception it throws is dropped. Disposal waits for a running handler call as described for GuiPanelControl.SubscribeUpdates. The default all-zero id fails with StatusCode.GuiPanelInvalidId.

SubscribeAllGestureEvents​

Subscribe to every gesture kind on one panel through one handler. Prefer this when taking more than one kind: everything arrives in the order the producer published it.

public Result<IDisposable> SubscribeAllGestureEvents(GuiPanelId id, EventHandler<GuiPanelGestureEventArgs> handler, CancellationToken cancellationToken = default)

Parameters

NameTypeDescription
idGuiPanelId
handlerEventHandler<GuiPanelGestureEventArgs>
cancellationTokenCancellationToken

Returns: Result<IDisposable>

Exceptions

ExceptionCondition
ArgumentNullExceptionhandler is null.

Remarks

A hover and the drag that grows out of the same contact keep their order here. Dispatch on GuiPanelGestureEvent.Kind. Everything else - several producers, lifecycle keys, threading and disposal - is as for GuiPanelControl.SubscribeGestureEvents.

UpdatePose​

Moves a panel. pose is in room space.

public Result<Unit> UpdatePose(GuiPanelId id, Pose pose)

Parameters

NameTypeDescription
idGuiPanelId
posePose

Returns: Result<Unit>

Remarks

The pose is the midpoint of the panel's top edge, not its centre, with the front normal along -Z. A fast-path change that this session's GuiPanelControl.GetAll shows at once.

ChangeSize​

Changes the panel's size in metres and its content scale. A fast-path change that this session's GuiPanelControl.GetAll shows at once.

public Result<Unit> ChangeSize(GuiPanelId id, GuiPanelSize size)

Parameters

NameTypeDescription
idGuiPanelId
sizeGuiPanelSize

Returns: Result<Unit>

SetState​

Shows, minimizes or hides the panel for every peer. This, not the visible-to list, is how a panel is hidden. GuiPanelState.Closed is the same as GuiPanelControl.Close.

public Result<Unit> SetState(GuiPanelId id, GuiPanelState state)

Parameters

NameTypeDescription
idGuiPanelId
stateGuiPanelState

Returns: Result<Unit>

Close​

Closes the panel for every peer: its GuiPanel.State becomes GuiPanelState.Closed. GuiPanelControl.GetOrCreate with the same common name reopens it.

public Result<Unit> Close(GuiPanelId id)

Parameters

NameTypeDescription
idGuiPanelId

Returns: Result<Unit>

Points a web panel at another URI. A stream panel fails with StatusCode.GuiPanelChangeNotPermitted: content type is fixed at creation.

public Result<Unit> NavigateToUri(GuiPanelId id, string uri)

Parameters

NameTypeDescription
idGuiPanelId
uristring

Returns: Result<Unit>

SetStream​

Switches a stream panel to another video stream, which must be a video sender owned by this session. A web panel fails with StatusCode.GuiPanelChangeNotPermitted: content type is fixed at creation.

public Result<Unit> SetStream(GuiPanelId id, StreamId streamId)

Parameters

NameTypeDescription
idGuiPanelId
streamIdStreamId

Returns: Result<Unit>

UpdateVisibleTo​

Adds and removes peers from the panel's visible-to list. An empty list means the panel is visible to every peer: removing the last peer shows it to everyone again, it never hides it. Hide a panel through its state instead.

public Result<Unit> UpdateVisibleTo(GuiPanelId id, PeerId[] additions, PeerId[] removals)

Parameters

NameTypeDescription
idGuiPanelId
additionsPeerId[]
removalsPeerId[]

Returns: Result<Unit>

GetAll​

Snapshot of every panel the session's shared storage holds, with this session's own pending pose and size changes applied. Blocks for a network round trip of about 2 s on every call.

public Result<GuiPanel[]> GetAll()

Returns: Result<GuiPanel[]>

Remarks

Use it for a one-off listing. To track panels, subscribe with GuiPanelControl.SubscribeUpdates, which delivers the same snapshot first.

SubscribeUpdates()​

Starts delivering updates of every panel through the GuiPanelControl.Updated event until the result is disposed.

public Result<IDisposable> SubscribeUpdates()

Returns: Result<IDisposable>

Remarks

Same delivery and disposal rules as GuiPanelControl.SubscribeUpdates.

SubscribeUpdates(CancellationToken)​

Starts delivering updates of every panel through the GuiPanelControl.Updated event until the result is disposed.

public Result<IDisposable> SubscribeUpdates(CancellationToken cancellationToken)

Parameters

NameTypeDescription
cancellationTokenCancellationToken

Returns: Result<IDisposable>

Remarks

Same delivery and disposal rules as GuiPanelControl.SubscribeUpdates.

SubscribeUpdates(EventHandler<GuiPanelUpdatedEventArgs>)​

Calls handler once for every panel already stored, then again for every change to any panel, until the result is disposed.

public Result<IDisposable> SubscribeUpdates(EventHandler<GuiPanelUpdatedEventArgs> handler)

Parameters

NameTypeDescription
handlerEventHandler<GuiPanelUpdatedEventArgs>

Returns: Result<IDisposable>

Exceptions

ExceptionCondition
ArgumentNullExceptionhandler is null.

Remarks

Each call carries an immutable GuiPanel snapshot that is safe to keep. Its pose is in the publishing peer's room space. Changes come from any peer or app, including a user grabbing the panel in the room.

The handler runs on a library thread: do not block in it, do not call blocking QAROS methods from it, and marshal to your own thread before touching single-threaded state. An exception it throws is caught and dropped.

Disposing the result ends delivery: when Dispose returns, a handler call running on another thread has finished and no further one starts. Called from inside the handler, Dispose returns at once and no call starts after the current one. Never dispose while holding a lock the handler takes: it deadlocks. Cancelling cancellationToken ends delivery the same way, and CancellationTokenSource.Cancel waits the same way.

SubscribeUpdates(EventHandler<GuiPanelUpdatedEventArgs>, CancellationToken)​

Calls handler once for every panel already stored, then again for every change to any panel, until the result is disposed.

public Result<IDisposable> SubscribeUpdates(EventHandler<GuiPanelUpdatedEventArgs> handler, CancellationToken cancellationToken)

Parameters

NameTypeDescription
handlerEventHandler<GuiPanelUpdatedEventArgs>
cancellationTokenCancellationToken

Returns: Result<IDisposable>

Exceptions

ExceptionCondition
ArgumentNullExceptionhandler is null.

Remarks

Each call carries an immutable GuiPanel snapshot that is safe to keep. Its pose is in the publishing peer's room space. Changes come from any peer or app, including a user grabbing the panel in the room.

The handler runs on a library thread: do not block in it, do not call blocking QAROS methods from it, and marshal to your own thread before touching single-threaded state. An exception it throws is caught and dropped.

Disposing the result ends delivery: when Dispose returns, a handler call running on another thread has finished and no further one starts. Called from inside the handler, Dispose returns at once and no call starts after the current one. Never dispose while holding a lock the handler takes: it deadlocks. Cancelling cancellationToken ends delivery the same way, and CancellationTokenSource.Cancel waits the same way.

WatchUpdatesAsync(CancellationToken)​

The snapshots of GuiPanelControl.SubscribeUpdates as an async stream: every stored panel first, then every change.

public IAsyncEnumerable<GuiPanel> WatchUpdatesAsync(CancellationToken cancellationToken = default)

Parameters

NameTypeDescription
cancellationTokenCancellationToken

Returns: IAsyncEnumerable<GuiPanel>

Exceptions

ExceptionCondition
NativeCallExceptionThrown on the first MoveNextAsync when the subscription cannot be started.

Remarks

The subscription starts with the enumeration and ends when the enumerator is disposed. Updates are queued without bound until read.

SubscribeUpdates(GuiPanelId)​

Starts delivering one panel's updates through the GuiPanelControl.Updated event until the result is disposed.

public Result<IDisposable> SubscribeUpdates(GuiPanelId id)

Parameters

NameTypeDescription
idGuiPanelId

Returns: Result<IDisposable>

Remarks

Same delivery and disposal rules as GuiPanelControl.SubscribeUpdates.

SubscribeUpdates(GuiPanelId, CancellationToken)​

Starts delivering one panel's updates through the GuiPanelControl.Updated event until the result is disposed.

public Result<IDisposable> SubscribeUpdates(GuiPanelId id, CancellationToken cancellationToken)

Parameters

NameTypeDescription
idGuiPanelId
cancellationTokenCancellationToken

Returns: Result<IDisposable>

Remarks

Same delivery and disposal rules as GuiPanelControl.SubscribeUpdates.

SubscribeUpdates(GuiPanelId, EventHandler<GuiPanelUpdatedEventArgs>)​

Calls handler for one panel: first with its stored state, when it already exists, then for every change to it, until the result is disposed.

public Result<IDisposable> SubscribeUpdates(GuiPanelId id, EventHandler<GuiPanelUpdatedEventArgs> handler)

Parameters

NameTypeDescription
idGuiPanelId
handlerEventHandler<GuiPanelUpdatedEventArgs>

Returns: Result<IDisposable>

Exceptions

ExceptionCondition
ArgumentNullExceptionhandler is null.

Remarks

Snapshots, threading, exceptions and disposal are as for GuiPanelControl.SubscribeUpdates.

SubscribeUpdates(GuiPanelId, EventHandler<GuiPanelUpdatedEventArgs>, CancellationToken)​

Calls handler for one panel: first with its stored state, when it already exists, then for every change to it, until the result is disposed.

public Result<IDisposable> SubscribeUpdates(GuiPanelId id, EventHandler<GuiPanelUpdatedEventArgs> handler, CancellationToken cancellationToken)

Parameters

NameTypeDescription
idGuiPanelId
handlerEventHandler<GuiPanelUpdatedEventArgs>
cancellationTokenCancellationToken

Returns: Result<IDisposable>

Exceptions

ExceptionCondition
ArgumentNullExceptionhandler is null.

Remarks

Snapshots, threading, exceptions and disposal are as for GuiPanelControl.SubscribeUpdates.

WatchUpdatesAsync(GuiPanelId, CancellationToken)​

The snapshots of GuiPanelControl.SubscribeUpdates as an async stream.

public IAsyncEnumerable<GuiPanel> WatchUpdatesAsync(GuiPanelId id, CancellationToken cancellationToken = default)

Parameters

NameTypeDescription
idGuiPanelId
cancellationTokenCancellationToken

Returns: IAsyncEnumerable<GuiPanel>

Exceptions

ExceptionCondition
NativeCallExceptionThrown on the first MoveNextAsync when the subscription cannot be started.

Remarks

The subscription starts with the enumeration and ends when the enumerator is disposed. Updates are queued without bound until read.

Events​

Updated​

Raised for every panel update, but only while a subscription started by one of the SubscribeUpdates overloads without a handler is alive.

public event EventHandler<GuiPanelUpdatedEventArgs>? Updated

Event type: EventHandler<GuiPanelUpdatedEventArgs>

Remarks

Raised on a library thread with this control as the sender; see GuiPanelControl.SubscribeUpdates for what is delivered and the threading rules. Each such subscription raises the event once per update it delivers, so overlapping subscriptions raise it more than once.