Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Virtual UI panels #525

Merged
merged 14 commits into from
Jan 29, 2023
1 change: 1 addition & 0 deletions cmake/Root.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ add_subdirectory(${SHARDS_DIR}/src/core src/core)
add_subdirectory(${SHARDS_DIR}/src/mal src/mal)
add_subdirectory(${SHARDS_DIR}/src/input src/input)
add_subdirectory(${SHARDS_DIR}/src/gfx src/gfx)
add_subdirectory(${SHARDS_DIR}/src/spatial src/spatial)

if(SHARDS_WITH_EXTRA_SHARDS)
if(SHARDS_WITH_RUST_SHARDS)
Expand Down
10 changes: 10 additions & 0 deletions docs/details/shards/Spatial/Panel.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
This shards defines a single world space UI panel. This shard needs to be placed within a [Spatial.UI](../UI)'s Contents block.

This size of the panels defined using virtual UI points/units/pixels. How these virtual points map to world coordinates is controlled by the UI's Scale parameter. For example if the UI's scale is set to 200; 200 virtual points will be equal to 1 unit in world space.

A panel's position and orientation in the world is controlled by the `Transform` parameter, which is a standard 4x4 transformation matrix.

!!! danger
Applying scaling on this transform is not recommended and may result in undefined behavior.

The Panels' contents should be placed in the `Contents` parameter, similar to the screen space [UI](../../General/UI)
9 changes: 9 additions & 0 deletions docs/details/shards/Spatial/UI.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
This shard defines the starting point for creating interactive world space UI elements.

Every instance of [Spatial.Panel](../Panel) should be placed within this shards's Contents parameter.

The draw commands for the UI elements are added to the Queue that is passed in. In contrast to the screen space [UI](../../General/UI) shard, they do not need to be rendered using a [GFX.UIPass](../../GFX/UIPass)

!!! note
When rendering the draw commands, the sorting mode should be set to [SortMode.Queue](../../../enums/SortMode) so they are drawn in the correct order.
The [BuiltinFeatureId.Transform](../../GFX/BuiltinFeature/#transform) feature should be used
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
55 changes: 55 additions & 0 deletions docs/samples/shards/Spatial/UI/1.edn
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
(Setup
(GFX.DrawQueue) >= .queue

; Create render steps
(GFX.BuiltinFeature :Id BuiltinFeatureId.Transform) >> .features
{:Features .features :Queue .queue :Sort SortMode.Queue} (GFX.DrawablePass) >> .render-steps

; Panel transforms
15.0 (Math.DegreesToRadians) (Math.AxisAngleY) (Math.Rotation) >= .tmp
(Float3 -1.0 0.0 0.0) (Math.Translation) (Math.MatMul .tmp) >= .panel-t-0
-15.0 (Math.DegreesToRadians) (Math.AxisAngleY) (Math.Rotation) >= .tmp
(Float3 1.0 0.0 0.0) (Math.Translation) (Math.MatMul .tmp) >= .panel-t-1
5.0 (Math.DegreesToRadians) (Math.AxisAngleX) (Math.Rotation) >= .tmp
(Float3 0.0 1.2 0.0) (Math.Translation) (Math.MatMul .tmp) >= .panel-t-2

; Camera
{:Position (Float3 1 2 8) :Target (Float3 0 0 0)} (Math.LookAt) >= .view-transform
(GFX.View :View .view-transform) >= .view)

(GFX.MainWindow
:Title "World space UI" :Width 1280 :Height 720
:Contents
(->
.queue (GFX.ClearQueue)
(Spatial.UI
:Queue .queue :View .view :Scale 100.0 :Contents
(->
(Spatial.Panel
:Transform .panel-t-0 :Size (Float2 100 100) :Contents
(->
(UI.CentralPanel
(->
"Left panel" (UI.Label)
(UI.Button :Label "Button")))))
(Spatial.Panel
:Transform .panel-t-1 :Size (Float2 100 100) :Contents
(->
(UI.CentralPanel
(->
"Right panel" (UI.Label)
(UI.Button :Label "Button")))))
(Spatial.Panel
:Transform .panel-t-2 :Size (Float2 300 60) :Contents
(->
(UI.CentralPanel
(->
"Wide panel" (UI.Label)
(Setup
(LoadImage "../../assets/ShardsLogo.png") (GFX.Texture) >= .button-texture)
(UI.Horizontal (->
.button-texture (UI.ImageButton :Scale (Float2 0.01))
.button-texture (UI.ImageButton :Scale (Float2 0.01))
.button-texture (UI.ImageButton :Scale (Float2 0.01))))))))))

(GFX.Render :Steps .render-steps :View .view)))
1 change: 1 addition & 0 deletions docs/samples/shards/Spatial/UI/1.out.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
![Image](../assets/render_output.png)
2 changes: 2 additions & 0 deletions include/linalg_shim.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@

#include <linalg.h>
#include <vector>
#include <shards.h>

namespace shards {
struct alignas(16) Mat4 : public linalg::aliases::float4x4 {
using linalg::aliases::float4x4::mat;
Expand Down
142 changes: 28 additions & 114 deletions rust/src/shards/gui/context.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
/* SPDX-License-Identifier: BSD-3-Clause */
/* Copyright © 2022 Fragcolor Pte. Ltd. */

use egui_gfx::gfx_EguiRenderer_renderNoTransform;

use super::egui_host::EguiHost;
use super::util;
use super::EguiContext;
use super::CONTEXTS_NAME;
use super::EGUI_CTX_TYPE;
use super::EGUI_UI_SEQ_TYPE;
use super::GFX_CONTEXT_TYPE;
use super::INPUT_CONTEXT_TYPE;
use super::GFX_QUEUE_VAR_TYPES;
use super::HELP_OUTPUT_EQUAL_INPUT;
use super::INPUT_CONTEXT_TYPE;
use super::PARENTS_UI_NAME;
use crate::shard::Shard;
use crate::shardsc;
Expand Down Expand Up @@ -56,8 +59,7 @@ impl Default for EguiContext {
parents.set_name(PARENTS_UI_NAME);

Self {
context: None,
instance: ctx,
host: EguiHost::default(),
requiring: Vec::new(),
queue: ParamVar::default(),
contents: ShardsVar::default(),
Expand All @@ -73,7 +75,6 @@ impl Default for EguiContext {
var.set_name(CStr::from_ptr(name).to_str().unwrap());
var
},
parents,
renderer: egui_gfx::Renderer::new(),
input_translator: egui_gfx::InputTranslator::new(),
}
Expand Down Expand Up @@ -168,30 +169,12 @@ impl Shard for EguiContext {
let mut data = *data;
// clone shared into a new vector we can append things to
let mut shared: ExposedTypes = data.shared.into();

// expose UI context
let ctx_info = ExposedInfo {
exposedType: EGUI_CTX_TYPE,
name: self.instance.get_name(),
help: cstr!("The UI context.").into(),
isMutable: false,
isProtected: true, // don't allow to be used in code/wires
isTableEntry: false,
global: false,
isPushTable: false,
};
shared.push(ctx_info);
// expose UI parents seq
let ui_info = ExposedInfo {
exposedType: EGUI_UI_SEQ_TYPE,
name: self.parents.get_name(),
help: cstr!("The parent UI objects.").into(),
isMutable: false,
isProtected: true, // don't allow to be used in code/wires
isTableEntry: false,
global: false,
isPushTable: false,
};
shared.push(ui_info);
for exposed in self.host.get_exposed_info() {
shared.push(*exposed);
}

// update shared
data.shared = (&shared).into();

Expand All @@ -205,120 +188,51 @@ impl Shard for EguiContext {
}

fn warmup(&mut self, ctx: &Context) -> Result<(), &str> {
self.context = Some(egui::Context::default());
self.instance.warmup(ctx);
self.host.warmup(ctx)?;
self.queue.warmup(ctx);
self.contents.warmup(ctx)?;
self.graphics_context.warmup(ctx);
self.input_context.warmup(ctx);
self.parents.warmup(ctx);

// Initialize the parents stack in the root UI.
// Every other UI elements will reference it and push or pop UIs to it.
if !self.parents.get().is_seq() {
self.parents.set(Seq::new().as_ref().into());
}

// Context works the same
if !self.instance.get().is_seq() {
self.instance.set(Seq::new().as_ref().into());
}

Ok(())
}

fn cleanup(&mut self) -> Result<(), &str> {
self.parents.cleanup();
self.graphics_context.cleanup();
self.input_context.cleanup();
self.contents.cleanup();
self.queue.cleanup();
self.instance.cleanup();
self.host.cleanup()?;
Ok(())
}

fn activate(&mut self, context: &Context, input: &Var) -> Result<Var, &str> {
let gui_ctx = if let Some(gui_ctx) = &self.context {
gui_ctx
} else {
return Err("No UI context");
};

if self.contents.is_empty() {
return Ok(*input);
}

let raw_input = unsafe {
let inputs = shardsc::gfx_getEguiWindowInputs(
let egui_input = unsafe {
&*(shardsc::gfx_getEguiWindowInputs(
self.input_translator.as_mut_ptr() as *mut shardsc::gfx_EguiInputTranslator,
self.graphics_context.get(),
self.input_context.get(),
1.0,
) as *const egui_gfx::egui_Input;
egui_gfx::translate_raw_input(&*inputs)
) as *const egui_gfx::egui_Input)
};

match raw_input {
Err(_error) => {
shlog_debug!("Input translation error: {:?}", _error);
Err("Input translation error")
}
Ok(raw_input) => {
let draw_scale = raw_input.pixels_per_point.unwrap_or(1.0);

let mut error: Option<&str> = None;
let egui_output = gui_ctx.run(raw_input, |ctx| {
error = (|| -> Result<(), &str> {
// Push empty parent UI in case this context is nested inside another UI
util::update_seq(&mut self.parents, |seq| {
seq.push(Var::default());
})?;

let mut _output = Var::default();
let wire_state =
util::with_object_stack_var(&mut self.instance, ctx, &EGUI_CTX_TYPE, || {
Ok(self.contents.activate(context, input, &mut _output))
})?;

if wire_state == WireState::Error {
return Err("Failed to activate UI contents");
}

// Pop empty parent UI
util::update_seq(&mut self.parents, |seq| {
seq.pop();
})?;

Ok(())
})()
.err();
});

if let Some(e) = error {
return Err(e);
}

#[cfg(not(any(target_arch = "wasm32", target_os = "ios")))]
if let Some(url) = &egui_output.platform_output.open_url {
webbrowser::open(&url.url).map_err(|e| {
shlog_error!("{}", e);
"Failed to open URL."
})?;
}

let queue_var = self.queue.get();
unsafe {
let queue = shardsc::gfx_getDrawQueueFromVar(queue_var);
self.renderer.render(
gui_ctx,
egui_output,
queue as *const egui_gfx::gfx_DrawQueuePtr,
draw_scale,
)?;
}

Ok(*input)
}
self
.host
.activate(&egui_input, &(&self.contents).into(), context, input)?;
let egui_output = self.host.get_egui_output();

let queue_var = self.queue.get();
unsafe {
let queue = shardsc::gfx_getDrawQueueFromVar(queue_var);
self
.renderer
.render_with_native_output(egui_output, queue as *const egui_gfx::gfx_DrawQueuePtr);
}

Ok(*input)
}
}
Loading