Skip to content

Commit

Permalink
Merge pull request #525 from fragcolor-xyz/guus/virtual-ui
Browse files Browse the repository at this point in the history
Virtual UI panels
  • Loading branch information
guusw authored Jan 29, 2023
2 parents d7d6c3b + 27c19ed commit 0a0e2b5
Show file tree
Hide file tree
Showing 43 changed files with 1,426 additions and 242 deletions.
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

0 comments on commit 0a0e2b5

Please sign in to comment.