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
| Member | Description |
|---|---|
| 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
| Name | Type | Description |
|---|---|---|
options | GuiPanelOptions |
Returns: Result<GuiPanelId>
Exceptions
| Exception | Condition |
|---|---|
| ArgumentNullException | options is null. |
| ArgumentException | GuiPanelOptions.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
| Name | Type | Description |
|---|---|---|
id | GuiPanelId | |
gestureKind | GuiPanelGestureKind | |
handler | EventHandler<GuiPanelGestureEventArgs> | |
cancellationToken | CancellationToken |
Returns: Result<IDisposable>
Exceptions
| Exception | Condition |
|---|---|
| ArgumentNullException | handler 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
| Name | Type | Description |
|---|---|---|
id | GuiPanelId | |
handler | EventHandler<GuiPanelGestureEventArgs> | |
cancellationToken | CancellationToken |
Returns: Result<IDisposable>
Exceptions
| Exception | Condition |
|---|---|
| ArgumentNullException | handler 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
| Name | Type | Description |
|---|---|---|
id | GuiPanelId | |
pose | Pose |
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
| Name | Type | Description |
|---|---|---|
id | GuiPanelId | |
size | GuiPanelSize |
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
| Name | Type | Description |
|---|---|---|
id | GuiPanelId | |
state | GuiPanelState |
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
| Name | Type | Description |
|---|---|---|
id | GuiPanelId |
NavigateToUri
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
| Name | Type | Description |
|---|---|---|
id | GuiPanelId | |
uri | string |
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
| Name | Type | Description |
|---|---|---|
id | GuiPanelId | |
streamId | StreamId |
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
| Name | Type | Description |
|---|---|---|
id | GuiPanelId | |
additions | PeerId[] | |
removals | PeerId[] |
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()
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
| Name | Type | Description |
|---|---|---|
cancellationToken | CancellationToken |
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
| Name | Type | Description |
|---|---|---|
handler | EventHandler<GuiPanelUpdatedEventArgs> |
Returns: Result<IDisposable>
Exceptions
| Exception | Condition |
|---|---|
| ArgumentNullException | handler 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
| Name | Type | Description |
|---|---|---|
handler | EventHandler<GuiPanelUpdatedEventArgs> | |
cancellationToken | CancellationToken |
Returns: Result<IDisposable>
Exceptions
| Exception | Condition |
|---|---|
| ArgumentNullException | handler 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
| Name | Type | Description |
|---|---|---|
cancellationToken | CancellationToken |
Returns: IAsyncEnumerable<GuiPanel>
Exceptions
| Exception | Condition |
|---|---|
| NativeCallException | Thrown 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
| Name | Type | Description |
|---|---|---|
id | GuiPanelId |
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
| Name | Type | Description |
|---|---|---|
id | GuiPanelId | |
cancellationToken | CancellationToken |
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
| Name | Type | Description |
|---|---|---|
id | GuiPanelId | |
handler | EventHandler<GuiPanelUpdatedEventArgs> |
Returns: Result<IDisposable>
Exceptions
| Exception | Condition |
|---|---|
| ArgumentNullException | handler 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
| Name | Type | Description |
|---|---|---|
id | GuiPanelId | |
handler | EventHandler<GuiPanelUpdatedEventArgs> | |
cancellationToken | CancellationToken |
Returns: Result<IDisposable>
Exceptions
| Exception | Condition |
|---|---|
| ArgumentNullException | handler 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
| Name | Type | Description |
|---|---|---|
id | GuiPanelId | |
cancellationToken | CancellationToken |
Returns: IAsyncEnumerable<GuiPanel>
Exceptions
| Exception | Condition |
|---|---|
| NativeCallException | Thrown 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.