Node refactor (#1406)

This commit is contained in:
Mikulas Florek 2022-02-17 22:37:49 +01:00 committed by GitHub
parent 75adea2d01
commit c39de5a66a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 543 additions and 3641 deletions

View file

@ -6,4 +6,3 @@
#include "imgui_draw.cpp"
#include "imgui_widgets.cpp"
#include "imgui_freetype.cpp"
#include "imnodes.cpp"

View file

@ -24,6 +24,11 @@ enum class CurveEditorFlags
RESET = 1 << 2
};
enum class PinShape {
CIRCLE,
TRIANGLE
};
IMGUI_API int CurveEditor(const char* label
, float* values
, int points_count
@ -37,6 +42,16 @@ IMGUI_API void HSplitter(const char* str_id, ImVec2* size);
IMGUI_API void VSplitter(const char* str_id, ImVec2* size);
IMGUI_API void Rect(float w, float h, ImU32 color);
IMGUI_API void BeginNodeEditor(const char* title, ImVec2* offset);
IMGUI_API void EndNodeEditor();
IMGUI_API ImVec2 GetNodeEditorOffset();
IMGUI_API void BeginNode(ImGuiID id, ImVec2& screen_pos);
IMGUI_API void EndNode();
IMGUI_API void Pin(ImGuiID id, bool is_input, PinShape shape = PinShape::CIRCLE);
IMGUI_API bool GetNewLink(ImGuiID* from, ImGuiID* to);
IMGUI_API void NodeLink(ImGuiID from, ImGuiID to);
IMGUI_API bool IsLinkHovered();
IMGUI_API bool InputRotation(const char* label, float* euler);
IMGUI_API void Label(const char* label);
IMGUI_API void TextClipped(const char* text, float size);

View file

@ -16,9 +16,193 @@ ImVec2::operator Lumix::Vec2() const {
return {x, y};
}
namespace ImGuiEx {
constexpr float NODE_PIN_RADIUS = 5.f;
struct NodeEditorState {
ImVec2* node_pos;
float node_w = 120;
ImGuiID last_node_id;
ImVec2 node_editor_pos;
ImGuiID new_link_from = 0;
ImGuiID new_link_to = 0;
bool new_link_from_input;
bool link_hovered = false;
ImDrawList* draw_list = nullptr;
bool is_pin_hovered = false;
ImVec2* canvas_offset = nullptr;
} g_node_editor;
ImVec2 GetNodeEditorOffset() {
return *g_node_editor.canvas_offset;
}
void BeginNodeEditor(const char* title, ImVec2* offset) {
g_node_editor.canvas_offset = offset;
BeginChild(title, ImVec2(0, 0), false, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse);
g_node_editor.node_editor_pos = GetCursorScreenPos() + *g_node_editor.canvas_offset;
g_node_editor.link_hovered = false;
g_node_editor.draw_list = GetWindowDrawList();
g_node_editor.draw_list->ChannelsSplit(2);
}
void EndNodeEditor() {
g_node_editor.draw_list->ChannelsMerge();
if (g_node_editor.new_link_from != 0) {
ImGuiStorage* storage = GetStateStorage();
PushID(g_node_editor.new_link_from);
const ImVec2 from(storage->GetFloat(GetID("pin-x"), 0), storage->GetFloat(GetID("pin-y"), 0));
PopID();
ImDrawList* dl = g_node_editor.draw_list;
const ImVec2 to = GetMousePos();
if (g_node_editor.new_link_from_input) {
dl->AddBezierCubic(from, from - ImVec2(20, 0), to + ImVec2(20, 0), to, GetColorU32(ImGuiCol_Tab), 3.f);
}
else {
dl->AddBezierCubic(from, from + ImVec2(20, 0), to - ImVec2(20, 0), to, GetColorU32(ImGuiCol_Tab), 3.f);
}
}
EndChild();
if (IsMouseReleased(0)) {
g_node_editor.new_link_from = 0;
g_node_editor.new_link_to = 0;
}
if (IsMouseDragging(ImGuiMouseButton_Middle) && IsItemHovered()) {
const ImVec2 delta = GetIO().MouseDelta;
*g_node_editor.canvas_offset += delta;
}
}
bool GetNewLink(ImGuiID* from, ImGuiID* to) {
if (g_node_editor.new_link_to) {
*from = g_node_editor.new_link_from;
*to = g_node_editor.new_link_to;
return true;
}
return false;
}
void Pin(ImGuiID id, bool is_input, PinShape shape) {
PopID(); // pop node id, we want pin id to not include node id
ImDrawList* draw_list = GetWindowDrawList();
ImVec2 screen_pos = ImGui::GetCursorScreenPos();
const ImVec2 center = [&](){
if (is_input) return screen_pos + ImVec2(-GetStyle().WindowPadding.x, GetTextLineHeightWithSpacing() * 0.5f);
return ImVec2(g_node_editor.node_pos->x + g_node_editor.node_w + 2 * GetStyle().WindowPadding.x, screen_pos.y + GetTextLineHeightWithSpacing() * 0.5f);
}();
const ImVec2 half_extents(NODE_PIN_RADIUS, NODE_PIN_RADIUS);
ItemAdd(ImRect(center - half_extents, center + half_extents), id);
const bool hovered = IsItemHovered();
ImGuiStyle& style = ImGui::GetStyle();
const ImU32 color = GetColorU32(hovered ? ImGuiCol_TabHovered : ImGuiCol_Tab);
switch(shape) {
case PinShape::TRIANGLE:
draw_list->AddTriangleFilled(center - ImVec2(NODE_PIN_RADIUS, -NODE_PIN_RADIUS), center - half_extents, center + ImVec2(NODE_PIN_RADIUS, 0), GetColorU32(ImGuiCol_Text));
break;
default:
draw_list->AddCircleFilled(center, NODE_PIN_RADIUS, color);
break;
}
g_node_editor.is_pin_hovered = g_node_editor.is_pin_hovered || hovered;
ImGuiStorage* storage = GetStateStorage();
PushID(id);
storage->SetFloat(GetID("pin-x"), center.x);
storage->SetFloat(GetID("pin-y"), center.y);
PopID();
if (hovered && ImGui::IsMouseClicked(0)) {
g_node_editor.new_link_from = id;
g_node_editor.new_link_from_input = is_input;
}
if (hovered && ImGui::IsMouseReleased(0) && g_node_editor.new_link_from != 0) {
g_node_editor.new_link_to = id;
if (!is_input) {
ImSwap(g_node_editor.new_link_to, g_node_editor.new_link_from);
}
}
PushID(g_node_editor.last_node_id);
}
bool IsLinkHovered() {
return g_node_editor.link_hovered;
}
void NodeLink(ImGuiID from_id, ImGuiID to_id) {
ImGuiStorage* storage = GetStateStorage();
PushID(from_id);
const ImVec2 from(storage->GetFloat(GetID("pin-x"), 0), storage->GetFloat(GetID("pin-y"), 0));
PopID();
PushID(to_id);
const ImVec2 to(storage->GetFloat(GetID("pin-x"), 0), storage->GetFloat(GetID("pin-y"), 0));
PopID();
ImVec2 p1 = from;
float d = ImMax(20.f, ImAbs(from.x - to.x)) * 0.75f;
ImVec2 t1 = ImVec2(d, 0.0f);
ImVec2 p2 = to;
ImVec2 t2 = ImVec2(d, 0.0f);
const int STEPS = 12;
ImDrawList* draw_list = GetWindowDrawList();
const ImGuiStyle& style = ImGui::GetStyle();
const ImVec2 closest_point = ImBezierCubicClosestPointCasteljau(p1, p1 + t1, p2 - t2, p2, ImGui::GetMousePos(), style.CurveTessellationTol);
const float dist_squared = ImFabs(ImLengthSqr(ImGui::GetMousePos() - closest_point));
g_node_editor.link_hovered = dist_squared < 3 * 3 + 1;
draw_list->AddBezierCubic(p1, p1 + t1, p2 - t2, p2, GetColorU32(g_node_editor.link_hovered ? ImGuiCol_TabActive : ImGuiCol_Tab), 3.f);
}
void BeginNode(ImGuiID id, ImVec2& pos) {
g_node_editor.last_node_id = id;
pos += g_node_editor.node_editor_pos;
g_node_editor.node_pos = &pos;
SetCursorScreenPos(pos + GetStyle().WindowPadding);
g_node_editor.draw_list->ChannelsSetCurrent(1);
BeginGroup();
PushID(id);
g_node_editor.node_w = GetStateStorage()->GetFloat(GetID("node-width"), 120);
PushItemWidth(80);
g_node_editor.is_pin_hovered = false;
}
void EndNode()
{
PopItemWidth();
EndGroup();
const ImGuiStyle& style = GetStyle();
const ImRect rect(GetItemRectMin() - style.WindowPadding, GetItemRectMax() + style.WindowPadding);
const ImVec2 size = rect.GetSize();
GetStateStorage()->SetFloat(GetID("node-width"), size.x - style.WindowPadding.x * 2);
const ImGuiID dragger_id = GetID("##_node_dragger");
ItemAdd(rect, dragger_id);
const bool is_hovered = IsItemHovered();
if (is_hovered && IsMouseClicked(0) && !g_node_editor.is_pin_hovered) {
SetActiveID(dragger_id, GetCurrentWindow());
}
if (IsItemActive() && IsMouseReleased(0)) {
ResetActiveID();
}
if (IsItemActive() && IsMouseDragging(0)) {
*g_node_editor.node_pos += GetIO().MouseDelta;
}
g_node_editor.draw_list->ChannelsSetCurrent(0);
ImVec2 np = *g_node_editor.node_pos;
g_node_editor.draw_list->AddRectFilled(np, np + size, ImColor(style.Colors[ImGuiCol_WindowBg]), 4.0f);
g_node_editor.draw_list->AddRect(np, np + size, GetColorU32(is_hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Border), 4.0f);
PopID();
*g_node_editor.node_pos -= g_node_editor.node_editor_pos;
}
bool ToolbarButton(ImFont* font, const char* font_icon, const ImVec4& bg_color, const char* tooltip)
{
auto frame_padding = GetStyle().FramePadding;

File diff suppressed because it is too large Load diff

View file

@ -1,335 +0,0 @@
#pragma once
#include <stddef.h>
struct ImGuiContext;
struct ImVec2;
namespace imnodes
{
enum ColorStyle
{
ColorStyle_NodeBackground = 0,
ColorStyle_NodeBackgroundHovered,
ColorStyle_NodeBackgroundSelected,
ColorStyle_NodeOutline,
ColorStyle_TitleBar,
ColorStyle_TitleBarHovered,
ColorStyle_TitleBarSelected,
ColorStyle_Link,
ColorStyle_LinkHovered,
ColorStyle_LinkSelected,
ColorStyle_Pin,
ColorStyle_PinHovered,
ColorStyle_BoxSelector,
ColorStyle_BoxSelectorOutline,
ColorStyle_GridBackground,
ColorStyle_GridLine,
ColorStyle_Count
};
enum StyleVar
{
StyleVar_GridSpacing = 0,
StyleVar_NodeCornerRounding,
StyleVar_NodePaddingHorizontal,
StyleVar_NodePaddingVertical,
StyleVar_NodeBorderThickness,
StyleVar_LinkThickness,
StyleVar_LinkLineSegmentsPerLength,
StyleVar_LinkHoverDistance,
StyleVar_PinCircleRadius,
StyleVar_PinQuadSideLength,
StyleVar_PinTriangleSideLength,
StyleVar_PinLineThickness,
StyleVar_PinHoverRadius,
StyleVar_PinOffset
};
enum StyleFlags
{
StyleFlags_None = 0,
StyleFlags_NodeOutline = 1 << 0,
StyleFlags_GridLines = 1 << 2
};
// This enum controls the way attribute pins look.
enum PinShape
{
PinShape_Circle,
PinShape_CircleFilled,
PinShape_Triangle,
PinShape_TriangleFilled,
PinShape_Quad,
PinShape_QuadFilled
};
// This enum controls the way the attribute pins behave.
enum AttributeFlags
{
AttributeFlags_None = 0,
// Allow detaching a link by left-clicking and dragging the link at a pin it is connected to.
// NOTE: the user has to actually delete the link for this to work. A deleted link can be
// detected by calling IsLinkDestroyed() after EndNodeEditor().
AttributeFlags_EnableLinkDetachWithDragClick = 1 << 0,
// Visual snapping of an in progress link will trigger IsLink Created/Destroyed events. Allows
// for previewing the creation of a link while dragging it across attributes. See here for demo:
// https://github.com/Nelarius/imnodes/issues/41#issuecomment-647132113 NOTE: the user has to
// actually delete the link for this to work. A deleted link can be detected by calling
// IsLinkDestroyed() after EndNodeEditor().
AttributeFlags_EnableLinkCreationOnSnap = 1 << 1
};
struct IO
{
struct EmulateThreeButtonMouse
{
EmulateThreeButtonMouse();
// The keyboard modifier to use in combination with mouse left click to pan the editor view.
// Set to NULL by default. To enable this feature, set the modifier to point to a boolean
// indicating the state of a modifier. For example,
//
// imnodes::GetIO().emulate_three_button_mouse.modifier = &ImGui::GetIO().KeyAlt;
const bool* modifier;
} emulate_three_button_mouse;
struct LinkDetachWithModifierClick
{
LinkDetachWithModifierClick();
// Pointer to a boolean value indicating when the desired modifier is pressed. Set to NULL
// by default. To enable the feature, set the modifier to point to a boolean indicating the
// state of a modifier. For example,
//
// imnodes::GetIO().link_detach_with_modifier_click.modifier = &ImGui::GetIO().KeyCtrl;
//
// Left-clicking a link with this modifier pressed will detach that link. NOTE: the user has
// to actually delete the link for this to work. A deleted link can be detected by calling
// IsLinkDestroyed() after EndNodeEditor().
const bool* modifier;
} link_detach_with_modifier_click;
IO();
};
struct Style
{
float grid_spacing;
float node_corner_rounding;
float node_padding_horizontal;
float node_padding_vertical;
float node_border_thickness;
float link_thickness;
float link_line_segments_per_length;
float link_hover_distance;
// The following variables control the look and behavior of the pins. The default size of each
// pin shape is balanced to occupy approximately the same surface area on the screen.
// The circle radius used when the pin shape is either PinShape_Circle or PinShape_CircleFilled.
float pin_circle_radius;
// The quad side length used when the shape is either PinShape_Quad or PinShape_QuadFilled.
float pin_quad_side_length;
// The equilateral triangle side length used when the pin shape is either PinShape_Triangle or
// PinShape_TriangleFilled.
float pin_triangle_side_length;
// The thickness of the line used when the pin shape is not filled.
float pin_line_thickness;
// The radius from the pin's center position inside of which it is detected as being hovered
// over.
float pin_hover_radius;
// Offsets the pins' positions from the edge of the node to the outside of the node.
float pin_offset;
// By default, StyleFlags_NodeOutline and StyleFlags_Gridlines are enabled.
StyleFlags flags;
// Set these mid-frame using Push/PopColorStyle. You can index this color array with with a
// ColorStyle enum value.
unsigned int colors[ColorStyle_Count];
Style();
};
struct Context;
// Call this function if you are compiling imnodes in to a dll, separate from ImGui. Calling this
// function sets the GImGui global variable, which is not shared across dll boundaries.
void SetImGuiContext(ImGuiContext* ctx);
Context* CreateContext();
void DestroyContext(Context* ctx = NULL); // NULL = destroy current context
Context* GetCurrentContext();
void SetCurrentContext(Context* ctx);
// An editor context corresponds to a set of nodes in a single workspace (created with a single
// Begin/EndNodeEditor pair)
//
// By default, the library creates an editor context behind the scenes, so using any of the imnodes
// functions doesn't require you to explicitly create a context.
struct EditorContext;
EditorContext* EditorContextCreate();
void EditorContextFree(EditorContext*);
void EditorContextSet(EditorContext*);
ImVec2 EditorContextGetPanning();
void EditorContextResetPanning(const ImVec2& pos);
void EditorContextMoveToNode(const int node_id);
IO& GetIO();
// Returns the global style struct. See the struct declaration for default values.
Style& GetStyle();
// Style presets matching the dear imgui styles of the same name.
void StyleColorsDark(); // on by default
void StyleColorsClassic();
void StyleColorsLight();
// The top-level function call. Call this before calling BeginNode/EndNode. Calling this function
// will result the node editor grid workspace being rendered.
void BeginNodeEditor();
void EndNodeEditor();
// Use PushColorStyle and PopColorStyle to modify Style::colors mid-frame.
void PushColorStyle(ColorStyle item, unsigned int color);
void PopColorStyle();
void PushStyleVar(StyleVar style_item, float value);
void PopStyleVar();
// id can be any positive or negative integer, but INT_MIN is currently reserved for internal use.
void BeginNode(int id);
void EndNode();
ImVec2 GetNodeDimensions(int id);
// Place your node title bar content (such as the node title, using ImGui::Text) between the
// following function calls. These functions have to be called before adding any attributes, or the
// layout of the node will be incorrect.
void BeginNodeTitleBar();
void EndNodeTitleBar();
// Attributes are ImGui UI elements embedded within the node. Attributes can have pin shapes
// rendered next to them. Links are created between pins.
//
// The activity status of an attribute can be checked via the IsAttributeActive() and
// IsAnyAttributeActive() function calls. This is one easy way of checking for any changes made to
// an attribute's drag float UI, for instance.
//
// Each attribute id must be unique.
// Create an input attribute block. The pin is rendered on left side.
void BeginInputAttribute(int id, PinShape shape = PinShape_CircleFilled);
void EndInputAttribute();
// Create an output attribute block. The pin is rendered on the right side.
void BeginOutputAttribute(int id, PinShape shape = PinShape_CircleFilled);
void EndOutputAttribute();
// Create a static attribute block. A static attribute has no pin, and therefore can't be linked to
// anything. However, you can still use IsAttributeActive() and IsAnyAttributeActive() to check for
// attribute activity.
void BeginStaticAttribute(int id);
void EndStaticAttribute();
// Push a single AttributeFlags value. By default, only AttributeFlags_None is set.
void PushAttributeFlag(AttributeFlags flag);
void PopAttributeFlag();
// Render a link between attributes.
// The attributes ids used here must match the ids used in Begin(Input|Output)Attribute function
// calls. The order of start_attr and end_attr doesn't make a difference for rendering the link.
void Link(int id, int start_attribute_id, int end_attribute_id);
// Enable or disable the ability to click and drag a specific node.
void SetNodeDraggable(int node_id, const bool draggable);
// The node's position can be expressed in three coordinate systems:
// * screen space coordinates, -- the origin is the upper left corner of the window.
// * editor space coordinates -- the origin is the upper left corner of the node editor window
// * grid space coordinates, -- the origin is the upper left corner of the node editor window,
// translated by the current editor panning vector (see EditorContextGetPanning() and
// EditorContextResetPanning())
// Use the following functions to get and set the node's coordinates in these coordinate systems.
void SetNodeScreenSpacePos(int node_id, const ImVec2& screen_space_pos);
void SetNodeEditorSpacePos(int node_id, const ImVec2& editor_space_pos);
void SetNodeGridSpacePos(int node_id, const ImVec2& grid_pos);
ImVec2 GetNodeScreenSpacePos(const int node_id);
ImVec2 GetNodeEditorSpacePos(const int node_id);
ImVec2 GetNodeGridSpacePos(const int node_id);
// Returns true if the current node editor canvas is being hovered over by the mouse, and is not
// blocked by any other windows.
bool IsEditorHovered();
// The following functions return true if a UI element is being hovered over by the mouse cursor.
// Assigns the id of the UI element being hovered over to the function argument. Use these functions
// after EndNodeEditor() has been called.
bool IsNodeHovered(int* node_id);
bool IsLinkHovered(int* link_id);
bool IsPinHovered(int* attribute_id);
// Use The following two functions to query the number of selected nodes or links in the current
// editor. Use after calling EndNodeEditor().
int NumSelectedNodes();
int NumSelectedLinks();
// Get the selected node/link ids. The pointer argument should point to an integer array with at
// least as many elements as the respective NumSelectedNodes/NumSelectedLinks function call
// returned.
void GetSelectedNodes(int* node_ids);
void GetSelectedLinks(int* link_ids);
// Clears the list of selected nodes/links. Useful if you want to delete a selected node or link.
void ClearNodeSelection();
void ClearLinkSelection();
// Was the previous attribute active? This will continuously return true while the left mouse button
// is being pressed over the UI content of the attribute.
bool IsAttributeActive();
// Was any attribute active? If so, sets the active attribute id to the output function argument.
bool IsAnyAttributeActive(int* attribute_id = NULL);
// Use the following functions to query a change of state for an existing link, or new link. Call
// these after EndNodeEditor().
// Did the user start dragging a new link from a pin?
bool IsLinkStarted(int* started_at_attribute_id);
// Did the user drop the dragged link before attaching it to a pin?
// There are two different kinds of situations to consider when handling this event:
// 1) a link which is created at a pin and then dropped
// 2) an existing link which is detached from a pin and then dropped
// Use the including_detached_links flag to control whether this function triggers when the user
// detaches a link and drops it.
bool IsLinkDropped(int* started_at_attribute_id = NULL, bool including_detached_links = true);
// Did the user finish creating a new link?
bool IsLinkCreated(
int* started_at_attribute_id,
int* ended_at_attribute_id,
bool* created_from_snap = NULL);
bool IsLinkCreated(
int* started_at_node_id,
int* started_at_attribute_id,
int* ended_at_node_id,
int* ended_at_attribute_id,
bool* created_from_snap = NULL);
// Was an existing link detached from a pin by the user? The detached link's id is assigned to the
// output argument link_id.
bool IsLinkDestroyed(int* link_id);
// Use the following functions to write the editor context's state to a string, or directly to a
// file. The editor context is serialized in the INI file format.
const char* SaveCurrentEditorStateToIniString(size_t* data_size = NULL);
const char* SaveEditorStateToIniString(const EditorContext* editor, size_t* data_size = NULL);
void LoadCurrentEditorStateFromIniString(const char* data, size_t data_size);
void LoadEditorStateFromIniString(EditorContext* editor, const char* data, size_t data_size);
void SaveCurrentEditorStateToIniFile(const char* file_name);
void SaveEditorStateToIniFile(const EditorContext* editor, const char* file_name);
void LoadCurrentEditorStateFromIniFile(const char* file_name);
void LoadEditorStateFromIniFile(EditorContext* editor, const char* file_name);
} // namespace imnodes

View file

@ -1,6 +1,5 @@
#include <imgui/imgui.h>
#include <imgui/imgui_internal.h>
#include <imgui/imnodes.h>
#include "audio/audio_scene.h"
#include "editor/asset_browser.h"
@ -441,7 +440,6 @@ struct StudioAppImpl final : StudioApp
ImGui::SetAllocatorFunctions(imguiAlloc, imguiFree, this);
ImGui::CreateContext();
imnodes::CreateContext();
loadSettings();
initIMGUI();

View file

@ -136,6 +136,7 @@ LUMIX_FORCE_INLINE static bool trigger(Signal* signal)
}
else {
--signal->counter;
ASSERT(signal->counter >= 0);
if (signal->counter > 0) return false;
}

View file

@ -12,11 +12,12 @@
#include "engine/os.h"
#include "engine/string.h"
#include "engine/universe.h"
#include "render_plugins.h"
#include "renderer/material.h"
#include "renderer/particle_system.h"
#include "renderer/render_scene.h"
#include "renderer/renderer.h"
#include <imgui/imgui.h>
#include <imgui/imnodes.h>
namespace Lumix {
@ -94,28 +95,24 @@ struct ParticleEditorResource {
return {};
}
void beginInput() {
imnodes::BeginInputAttribute(m_id | (u32(m_input_counter) << 16));
void inputSlot(ImGuiEx::PinShape shape = ImGuiEx::PinShape::CIRCLE) {
ImGuiEx::Pin(m_id | (u32(m_input_counter) << 16), true, shape);
++m_input_counter;
}
static void endInput() { imnodes::EndInputAttribute(); }
void beginOutput() {
imnodes::BeginOutputAttribute(m_id | (u32(m_output_counter) << 16) | OUTPUT_FLAG);
void outputSlot(ImGuiEx::PinShape shape = ImGuiEx::PinShape::CIRCLE) {
ImGuiEx::Pin(m_id | (u32(m_output_counter) << 16) | OUTPUT_FLAG, false, shape);
++m_output_counter;
}
static void endOutput() { imnodes::EndOutputAttribute(); }
bool onNodeGUI() {
m_input_counter = 0;
m_output_counter = 0;
imnodes::SetNodeEditorSpacePos(m_id, m_pos);
imnodes::BeginNode(m_id);
const ImVec2 old_pos = m_pos;
ImGuiEx::BeginNode(m_id, m_pos);
bool res = onGUI();
imnodes::EndNode();
return res;
ImGuiEx::EndNode();
return res || old_pos.x != m_pos.x || old_pos.y != m_pos.y;
}
u16 m_id;
@ -157,12 +154,11 @@ struct ParticleEditorResource {
}
bool onGUI() override {
beginInput();
endInput();
beginOutput();
inputSlot();
ImGui::SetNextItemWidth(60);
ImGui::Combo("##fn", (int*)&func, "cos\0sin\0");
endOutput();
ImGui::SameLine();
outputSlot();
return false;
}
@ -209,13 +205,11 @@ struct ParticleEditorResource {
void deserialize(InputMemoryStream& blob) override { blob.read(count); blob.read(keys); blob.read(values); }
bool onGUI() override {
beginOutput();
endOutput();
beginInput();
ImGui::SetNextItemWidth(120);
inputSlot();
bool changed = ImGuiEx::Gradient4("test", lengthOf(keys), (int*)&count, keys, &values[0].x);
endInput();
ImGui::SameLine();
outputSlot();
return changed;
}
@ -256,11 +250,10 @@ struct ParticleEditorResource {
void deserialize(InputMemoryStream& blob) override { blob.read(count); blob.read(keys); blob.read(values); }
bool onGUI() override {
beginOutput();
endOutput();
beginInput();
ImGui::TextUnformatted("Gradient");
ImGui::BeginGroup();
inputSlot();
ImGui::PushItemWidth(60);
bool changed = false;
for (u32 i = 0; i < count; ++i) {
@ -272,7 +265,6 @@ struct ParticleEditorResource {
keys[i] = clamp(keys[i], 0.f, 1.f);
}
ImGui::PopItemWidth();
endInput();
if (ImGui::Button("Add")) {
ASSERT(count < lengthOf(values));
keys[count] = 0;
@ -280,6 +272,10 @@ struct ParticleEditorResource {
++count;
changed = true;
}
ImGui::EndGroup();
ImGui::SameLine();
outputSlot();
return changed ;
}
@ -304,9 +300,7 @@ struct ParticleEditorResource {
}
bool onGUI() override {
beginOutput();
ImGui::TextUnformatted(m_resource.m_consts[idx].name);
endOutput();
outputSlot(); ImGui::TextUnformatted(m_resource.m_consts[idx].name);
return false;
}
@ -339,15 +333,20 @@ struct ParticleEditorResource {
}
bool onGUI() override {
imnodes::BeginNodeTitleBar();
ImGui::Text("Random");
imnodes::EndNodeTitleBar();
beginOutput();
//imnodes::BeginNodeTitleBar();
ImGui::Text(ICON_FA_DICE " Random");
//imnodes::EndNodeTitleBar();
ImGui::BeginGroup();
ImGui::PushItemWidth(60);
ImGui::DragFloat("From", &from);
ImGui::DragFloat("To", &to);
ImGui::DragFloat("##from", &from);
ImGui::SameLine(); ImGui::DragFloat("##to", &to);
ImGui::PopItemWidth();
endOutput();
ImGui::EndGroup();
ImGui::SameLine();
outputSlot();
return false;
}
@ -371,10 +370,9 @@ struct ParticleEditorResource {
void deserialize(InputMemoryStream& blob) override { blob.read(value); }
bool onGUI() override {
beginOutput();
outputSlot();
ImGui::SetNextItemWidth(120);
bool changed = ImGui::DragFloat("##v", &value);
endOutput();
return changed;
}
@ -402,39 +400,41 @@ struct ParticleEditorResource {
void deserialize(InputMemoryStream& blob) override { blob.read(value); }
bool onGUI() override {
beginOutput();
endOutput();
ImGui::PushItemWidth(60);
bool changed = false;
beginInput();
ImGui::BeginGroup();
inputSlot();
if (getInput(0).node) {
ImGui::TextUnformatted("X");
}
else {
changed = ImGui::DragFloat("X", &value.x);
}
endInput();
beginInput();
inputSlot();
if (getInput(1).node) {
ImGui::TextUnformatted("Y");
}
else {
changed = ImGui::DragFloat("Y", &value.y) || changed;
}
endInput();
beginInput();
inputSlot();
if (getInput(2).node) {
ImGui::TextUnformatted("Z");
}
else {
changed = ImGui::DragFloat("Z", &value.z) || changed;
}
endInput();
ImGui::EndGroup();
ImGui::PopItemWidth();
ImGui::SameLine();
outputSlot();
return changed;
}
@ -457,14 +457,13 @@ struct ParticleEditorResource {
void deserialize(InputMemoryStream& blob) override { blob.read(idx); }
bool onGUI() override {
beginOutput();
outputSlot();
if (idx < m_resource.m_streams.size()) {
ImGui::TextUnformatted(m_resource.m_streams[idx].name);
}
else {
ImGui::TextUnformatted(ICON_FA_EXCLAMATION "Deleted input");
}
endOutput();
return false;
}
@ -477,13 +476,11 @@ struct ParticleEditorResource {
Type getType() const override { return Type::EMIT; }
bool onGUI() override {
imnodes::BeginNodeTitleBar();
//imnodes::BeginNodeTitleBar();
ImGui::TextUnformatted(ICON_FA_PLUS " Emit");
imnodes::EndNodeTitleBar();
//imnodes::EndNodeTitleBar();
for (const Stream& stream : m_resource.m_streams) {
beginInput();
ImGui::TextUnformatted(stream.name);
endInput();
inputSlot(); ImGui::TextUnformatted(stream.name);
}
return false;
}
@ -558,18 +555,15 @@ struct ParticleEditorResource {
Type getType() const override { return Type::UPDATE; }
bool onGUI() override {
imnodes::BeginNodeTitleBar();
//imnodes::BeginNodeTitleBar();
ImGui::TextUnformatted(ICON_FA_CLOCK " Update");
imnodes::EndNodeTitleBar();
//imnodes::EndNodeTitleBar();
beginInput();
ImGui::TextUnformatted("Kill");
endInput();
inputSlot(ImGuiEx::PinShape::TRIANGLE); ImGui::TextUnformatted("Kill");
for (const Stream& stream : m_resource.m_streams) {
beginInput();
ImGui::TextUnformatted(stream.name);
endInput();
inputSlot(); ImGui::TextUnformatted(stream.name);
}
return false;
}
@ -614,25 +608,24 @@ struct ParticleEditorResource {
Type getType() const override { return Type::CMP; }
bool onGUI() override {
beginInput();
ImGui::TextUnformatted("A");
endInput();
ImGui::BeginGroup();
inputSlot(); ImGui::NewLine();
beginOutput();
ImGui::SetNextItemWidth(60);
bool changed = ImGui::Combo("##op", (int*)&op, "A < B\0A > B\0");
endOutput();
ImGui::SetNextItemWidth(45);
bool changed = ImGui::Combo("##op", (int*)&op, "<\0>\0");
inputSlot();
beginInput();
if (getInput(1).node) {
ImGui::TextUnformatted("B");
ImGui::NewLine();
}
else {
ImGui::SetNextItemWidth(60);
changed = ImGui::DragFloat("B", &value) || changed;
changed = ImGui::DragFloat("##b", &value) || changed;
}
endInput();
ImGui::EndGroup();
ImGui::SameLine();
outputSlot(ImGuiEx::PinShape::TRIANGLE);
return changed;
}
@ -681,13 +674,11 @@ struct ParticleEditorResource {
Type getType() const override { return Type::OUTPUT; }
bool onGUI() override {
imnodes::BeginNodeTitleBar();
//imnodes::BeginNodeTitleBar();
ImGui::TextUnformatted(ICON_FA_EYE " Output");
imnodes::EndNodeTitleBar();
//imnodes::EndNodeTitleBar();
for (const Output& stream : m_resource.m_outputs) {
beginInput();
ImGui::TextUnformatted(stream.name);
endInput();
inputSlot(); ImGui::TextUnformatted(stream.name);
}
return false;
}
@ -767,15 +758,14 @@ struct ParticleEditorResource {
void deserialize(InputMemoryStream& blob) override { blob.read(color0); blob.read(color1); }
bool onGUI() override {
beginInput();
ImGui::TextUnformatted("Weight");
endInput();
ImGui::BeginGroup();
inputSlot(); ImGui::TextUnformatted("Weight");
bool changed = ImGui::ColorEdit4("Color A", &color0.x, ImGuiColorEditFlags_NoInputs);
changed = ImGui::ColorEdit4("Color B", &color1.x, ImGuiColorEditFlags_NoInputs) || changed;
ImGui::EndGroup();
beginOutput();
ImGui::TextUnformatted("RGBA");
endOutput();
ImGui::SameLine();
outputSlot();
return changed;
}
@ -831,35 +821,34 @@ struct ParticleEditorResource {
}
bool onGUI() override {
imnodes::BeginNodeTitleBar();
beginOutput();
ImGui::TextUnformatted("A * B + C");
endOutput();
imnodes::EndNodeTitleBar();
beginInput();
ImGui::TextUnformatted("A");
endInput();
beginInput();
ImGui::BeginGroup();
inputSlot(); ImGui::NewLine();
ImGui::TextUnformatted("X");
inputSlot();
if (getInput(1).node) {
ImGui::TextUnformatted("B");
ImGui::NewLine();
}
else {
ImGui::SetNextItemWidth(60);
ImGui::DragFloat("B", &value1);
}
endInput();
beginInput();
ImGui::TextUnformatted(ICON_FA_PLUS);
inputSlot();
if (getInput(2).node) {
ImGui::TextUnformatted("C");
ImGui::NewLine();
}
else {
ImGui::SetNextItemWidth(60);
ImGui::DragFloat("C", &value2);
}
endInput();
ImGui::EndGroup();
ImGui::SameLine();
outputSlot();
return false;
}
@ -913,30 +902,30 @@ struct ParticleEditorResource {
}
bool onGUI() override {
imnodes::BeginNodeTitleBar();
beginOutput();
switch(OP_TYPE) {
case InstructionType::DIV: ImGui::TextUnformatted("Divide"); break;
case InstructionType::MUL: ImGui::TextUnformatted("Multiply"); break;
case InstructionType::ADD: ImGui::TextUnformatted("Add"); break;
default: ASSERT(false); break;
}
endOutput();
imnodes::EndNodeTitleBar();
ImGui::BeginGroup();
inputSlot(); ImGui::NewLine();
beginInput();
ImGui::TextUnformatted("A");
endInput();
beginInput();
inputSlot();
if (getInput(1).node) {
ImGui::TextUnformatted("B");
ImGui::NewLine();
}
else {
ImGui::SetNextItemWidth(60);
ImGui::DragFloat("B", &value);
ImGui::DragFloat("##b", &value);
}
endInput();
ImGui::EndGroup();
ImGui::SameLine();
switch(OP_TYPE) {
case InstructionType::DIV: ImGui::TextUnformatted(ICON_FA_DIVIDE); break;
case InstructionType::MUL: ImGui::TextUnformatted("X"); break;
case InstructionType::ADD: ImGui::TextUnformatted(ICON_FA_PLUS); break;
default: ASSERT(false); break;
}
ImGui::SameLine();
outputSlot();
return false;
}
@ -1110,7 +1099,9 @@ struct ParticleEditorResource {
m_nodes.push(UniquePtr<UpdateNode>::create(m_allocator, *this));
m_nodes.push(UniquePtr<OutputNode>::create(m_allocator, *this));
m_nodes.back()->m_pos = ImVec2(200, 100);
m_nodes.push(UniquePtr<EmitNode>::create(m_allocator, *this));
m_nodes.back()->m_pos = ImVec2(300, 100);
}
void generate() {
@ -1169,10 +1160,11 @@ struct ParticleEditorResource {
};
struct ParticleEditorImpl : ParticleEditor {
ParticleEditorImpl(StudioApp& app, IAllocator& allocator)
ParticleEditorImpl(StudioApp& app, IImGuiRenderer& imgui_renderer, IAllocator& allocator)
: m_allocator(allocator)
, m_app(app)
, m_undo_stack(allocator)
, m_imgui_renderer(imgui_renderer)
{
m_toggle_ui.init("Particle editor", "Toggle particle editor", "particle_editor", "", true);
m_toggle_ui.func.bind<&ParticleEditorImpl::toggleOpen>(this);
@ -1198,6 +1190,7 @@ struct ParticleEditorImpl : ParticleEditor {
}
~ParticleEditorImpl() {
if (m_canvas_ctx) ImGui::DestroyContext(m_canvas_ctx);
m_app.removeAction(&m_toggle_ui);
m_app.removeAction(&m_undo_action);
m_app.removeAction(&m_redo_action);
@ -1400,22 +1393,86 @@ struct ParticleEditorImpl : ParticleEditor {
ImGui::Columns(2);
leftColumnGUI();
ImGui::NextColumn();
bool context_open = false;
imnodes::BeginNodeEditor();
if (imnodes::IsEditorHovered() && ImGui::IsMouseClicked(1)) {
ImGui::NextColumn();
ImVec2 canvas_size = ImGui::GetContentRegionAvail();
const ImVec2 canvas_origin = ImGui::GetCursorScreenPos();
ImGuiContext* mainctx = ImGui::GetCurrentContext();
if (!m_canvas_ctx) m_canvas_ctx = ImGui::CreateContext(ImGui::GetIO().Fonts);
ImGui::SetCurrentContext(m_canvas_ctx);
const os::Event* events = m_app.getEvents();
ImGuiIO& io = ImGui::GetIO();
for (i32 i = 0, c = m_app.getEventsCount(); i < c; ++i) {
switch(events[i].type) {
case os::Event::Type::MOUSE_BUTTON: {
// TODO check if we should handle input, see studioapp how it's done there
io.AddMouseButtonEvent((int)events[i].mouse_button.button, events[i].mouse_button.down);
break;
}
case os::Event::Type::MOUSE_MOVE: {
const os::Point cp = os::getMouseScreenPos();
io.AddMousePosEvent((cp.x - canvas_origin.x) / m_canvas_scale.x, (cp.y - canvas_origin.y) / m_canvas_scale.y);
break;
}
}
}
ImGui::GetIO().DisplaySize = canvas_size / m_canvas_scale;
ImGui::NewFrame();
ImGui::SetNextWindowPos(ImVec2(0, 0));
ImGui::SetNextWindowSize(canvas_size / m_canvas_scale);
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0));
ImGui::Begin("particle_editor_canvas", nullptr, ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoInputs);
ImGui::PopStyleVar();
ImGuiEx::BeginNodeEditor("particle_editor", &m_canvas_offset);
i32 hovered_node = -1;
i32 hovered_link = -1;
for (UniquePtr<ParticleEditorResource::Node>& n : m_resource->m_nodes) {
if (n->onNodeGUI()) {
pushUndo(n->m_id);
}
if (ImGui::IsItemHovered()) {
hovered_node = n->m_id;
}
}
for (const ParticleEditorResource::Link& link : m_resource->m_links) {
ImGuiEx::NodeLink(link.from, link.to);
if (ImGuiEx::IsLinkHovered()) {
hovered_link = link.id;
}
}
ImGuiID nlf, nlt;
if (ImGuiEx::GetNewLink(&nlf, &nlt)) {
ParticleEditorResource::Link& link = m_resource->m_links.emplace();
link.from = nlf;
link.to = nlt;
pushUndo(0xffFFffFF);
}
ImGuiEx::EndNodeEditor();
const ImVec2 editor_pos = ImGui::GetItemRectMin();
bool context_open = false;
if (ImGui::IsItemHovered() && ImGui::IsMouseClicked(1)) {
ImGui::OpenPopup("context_menu");
context_open = true;
}
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(8.f, 8.f));
if (ImGui::BeginPopup("context_menu")) {
ImVec2 cp = ImGui::GetItemRectMin();
if (ImGui::BeginMenu("Add")) {
if (ImGui::Selectable("Add")) addNode(ParticleEditorResource::Node::ADD);
if (ImGui::Selectable("Color mix")) addNode(ParticleEditorResource::Node::COLOR_MIX);
if (ImGui::Selectable("Compare")) addNode(ParticleEditorResource::Node::CMP);
ParticleEditorResource::Node* n = nullptr;
if (ImGui::Selectable("Add")) n = addNode(ParticleEditorResource::Node::ADD);
if (ImGui::Selectable("Color mix")) n = addNode(ParticleEditorResource::Node::COLOR_MIX);
if (ImGui::Selectable("Compare")) n = addNode(ParticleEditorResource::Node::CMP);
if (ImGui::BeginMenu("Constant")) {
for (u8 i = 0; i < m_resource->m_consts.size(); ++i) {
if (ImGui::Selectable(m_resource->m_consts[i].name)) {
@ -1428,11 +1485,11 @@ struct ParticleEditorImpl : ParticleEditor {
ImGui::EndMenu();
}
if (ImGui::Selectable("Cos")) {
ParticleEditorResource::Node* n = addNode(ParticleEditorResource::Node::UNARY_FUNCTION);
n = addNode(ParticleEditorResource::Node::UNARY_FUNCTION);
((ParticleEditorResource::UnaryFunctionNode*)n)->func = ParticleEditorResource::UnaryFunctionNode::COS;
}
if (ImGui::Selectable("Gradient")) addNode(ParticleEditorResource::Node::GRADIENT);
if (ImGui::Selectable("Gradient color")) addNode(ParticleEditorResource::Node::GRADIENT_COLOR);
if (ImGui::Selectable("Gradient")) n = addNode(ParticleEditorResource::Node::GRADIENT);
if (ImGui::Selectable("Gradient color")) n = addNode(ParticleEditorResource::Node::GRADIENT_COLOR);
if (ImGui::BeginMenu("Input")) {
for (u8 i = 0; i < m_resource->m_streams.size(); ++i) {
if (ImGui::Selectable(m_resource->m_streams[i].name)) {
@ -1444,16 +1501,19 @@ struct ParticleEditorImpl : ParticleEditor {
}
ImGui::EndMenu();
}
if (ImGui::Selectable("Literal")) addNode(ParticleEditorResource::Node::LITERAL);
if (ImGui::Selectable("Divide")) addNode(ParticleEditorResource::Node::DIV);
if (ImGui::Selectable("Multiply")) addNode(ParticleEditorResource::Node::MUL);
if (ImGui::Selectable("Multiply add")) addNode(ParticleEditorResource::Node::MADD);
if (ImGui::Selectable("Random")) addNode(ParticleEditorResource::Node::RANDOM);
if (ImGui::Selectable("Literal")) n = addNode(ParticleEditorResource::Node::LITERAL);
if (ImGui::Selectable("Divide")) n = addNode(ParticleEditorResource::Node::DIV);
if (ImGui::Selectable("Multiply")) n = addNode(ParticleEditorResource::Node::MUL);
if (ImGui::Selectable("Multiply add")) n = addNode(ParticleEditorResource::Node::MADD);
if (ImGui::Selectable("Random")) n = addNode(ParticleEditorResource::Node::RANDOM);
if (ImGui::Selectable("Sin")) {
ParticleEditorResource::Node* n = addNode(ParticleEditorResource::Node::UNARY_FUNCTION);
n = addNode(ParticleEditorResource::Node::UNARY_FUNCTION);
((ParticleEditorResource::UnaryFunctionNode*)n)->func = ParticleEditorResource::UnaryFunctionNode::SIN;
}
if (ImGui::Selectable("Vec3")) addNode(ParticleEditorResource::Node::VEC3);
if (ImGui::Selectable("Vec3")) n = addNode(ParticleEditorResource::Node::VEC3);
if (n) {
n->m_pos = cp - editor_pos - ImGuiEx::GetNodeEditorOffset();
}
ImGui::EndMenu();
}
@ -1478,43 +1538,38 @@ struct ParticleEditorImpl : ParticleEditor {
}
ImGui::PopStyleVar();
for (UniquePtr<ParticleEditorResource::Node>& n : m_resource->m_nodes) {
if (n->onNodeGUI()) {
pushUndo(n->m_id);
}
}
for (const ParticleEditorResource::Link& link : m_resource->m_links) {
imnodes::Link(link.id, link.from, link.to);
}
imnodes::EndNodeEditor();
for (UniquePtr<ParticleEditorResource::Node>& n : m_resource->m_nodes) {
ImVec2 p = imnodes::GetNodeEditorSpacePos(n->m_id);
if (p.x != n->m_pos.x || p.y != n->m_pos.y) {
n->m_pos = p;
pushUndo(n->m_id);
}
}
if (context_open) {
m_context_link = -1;
imnodes::IsLinkHovered(&m_context_link);
m_context_node = -1;
imnodes::IsNodeHovered(&m_context_node);
m_context_link = hovered_link;
m_context_node = hovered_node;
}
{
int from, to;
if (imnodes::IsLinkCreated(&from, &to)) {
ParticleEditorResource::Link& link = m_resource->m_links.emplace();
link.id = m_resource->genID();
link.from = from;
link.to = to;
pushUndo(0xffFFffFF);
}
ImGui::End();
ImGui::Render();
if (!m_canvas_rt || m_canvas_size.x != canvas_size.x || m_canvas_size.y != canvas_size.y) {
Renderer* renderer = (Renderer*)m_app.getEngine().getPluginManager().getPlugin("renderer");
Renderer::MemRef mem;
if (m_canvas_rt) renderer->destroy(m_canvas_rt);
m_canvas_size = canvas_size;
m_canvas_rt = renderer->createTexture((u32)canvas_size.x, (u32)canvas_size.y, 1, gpu::TextureFormat::RGBA8, gpu::TextureFlags::RENDER_TARGET | gpu::TextureFlags::SRGB, mem, "particle_editor");
}
m_imgui_renderer.render(m_canvas_rt, Vec2(canvas_size.x, canvas_size.y), ImGui::GetDrawData(), m_canvas_scale);
ImGui::SetCurrentContext(mainctx);
ImGui::SetCursorScreenPos(canvas_origin);
if (gpu::isOriginBottomLeft()) {
ImGui::Image(m_canvas_rt, canvas_size, ImVec2(0, 1), ImVec2(1, 0));
}
else {
ImGui::Image(m_canvas_rt, canvas_size);
}
if (ImGui::IsItemHovered() && ImGui::GetIO().MouseWheel) {
m_canvas_scale.x += ImGui::GetIO().MouseWheel / 20;
m_canvas_scale.x = clamp(m_canvas_scale.x, 0.1f, 10.f);
m_canvas_scale.y = m_canvas_scale.x;
}
ImGui::Columns();
ImGui::End();
@ -1711,19 +1766,21 @@ struct ParticleEditorImpl : ParticleEditor {
Action m_redo_action;
Action m_apply_action;
bool m_has_focus = false;
IImGuiRenderer& m_imgui_renderer;
ImVec2 m_canvas_size = ImVec2(0, 0);
gpu::TextureHandle m_canvas_rt = gpu::INVALID_TEXTURE;
ImVec2 m_canvas_scale = ImVec2(1, 1);
ImVec2 m_canvas_offset = ImVec2(0, 0);
ImGuiContext* m_canvas_ctx = nullptr;
};
DataStream ParticleEditorResource::NodeInput::generate(OutputMemoryStream& instructions, DataStream output, u8 subindex) const {
return node ? node->generate(instructions, output_idx, output, subindex) : DataStream();
}
UniquePtr<StudioApp::GUIPlugin> createParticleEditor(StudioApp& app) {
return UniquePtr<ParticleEditorImpl>::create(app.getAllocator(), app, app.getAllocator());
}
UniquePtr<ParticleEditor> ParticleEditor::create(StudioApp& app) {
UniquePtr<ParticleEditor> ParticleEditor::create(StudioApp& app, IImGuiRenderer& imgui_renderer) {
IAllocator& allocator = app.getAllocator();
return UniquePtr<ParticleEditorImpl>::create(allocator, app, allocator);
return UniquePtr<ParticleEditorImpl>::create(allocator, app, imgui_renderer, allocator);
}

View file

@ -7,7 +7,7 @@ namespace Lumix {
template <typename T> struct UniquePtr;
struct ParticleEditor : StudioApp::GUIPlugin {
static UniquePtr<ParticleEditor> create(StudioApp& app);
static UniquePtr<ParticleEditor> create(StudioApp& app, struct IImGuiRenderer& imgui_renderer);
virtual void open(const char* path) = 0;
virtual bool compile(struct InputMemoryStream& input, struct OutputMemoryStream& output, const char* path) = 0;
};

View file

@ -4,7 +4,6 @@
#endif
#include <imgui/imgui_freetype.h>
#include <imgui/imnodes.h>
#include "animation/animation.h"
#include "editor/asset_browser.h"
@ -37,6 +36,7 @@
#include "engine/universe.h"
#include "fbx_importer.h"
#include "game_view.h"
#include "render_plugins.h"
#include "renderer/culling_system.h"
#include "renderer/editor/composite_texture.h"
#include "renderer/font.h"
@ -4357,7 +4357,7 @@ struct RenderInterfaceImpl final : RenderInterface
};
struct EditorUIRenderPlugin final : StudioApp::GUIPlugin
struct EditorUIRenderPlugin final : StudioApp::GUIPlugin, IImGuiRenderer
{
struct RenderJob : Renderer::RenderJob
{
@ -4380,6 +4380,10 @@ struct EditorUIRenderPlugin final : StudioApp::GUIPlugin
i32 x, y;
bool new_program = false;
Array<CmdList> cmd_lists;
Renderer::TransientSlice ub;
Vec2 scale;
gpu::TextureHandle render_target = gpu::INVALID_TEXTURE;
Vec4 clear_color = Vec4(0.2f, 0.2f, 0.2f, 1.f);
};
RenderJob(LinearAllocator& allocator)
@ -4405,6 +4409,8 @@ struct EditorUIRenderPlugin final : StudioApp::GUIPlugin
PluginManager& plugin_manager = plugin->m_engine.getPluginManager();
renderer = (Renderer*)plugin_manager.getPlugin("renderer");
if (!should_setup) return;
ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO();
window_draw_data.reserve(platform_io.Viewports.size());
for (ImGuiViewport* vp : platform_io.Viewports) {
@ -4414,9 +4420,17 @@ struct EditorUIRenderPlugin final : StudioApp::GUIPlugin
dd.h = u32(vp->Size.y);
dd.x = i32(draw_data->DisplayPos.x);
dd.y = i32(draw_data->DisplayPos.y);
dd.scale = Vec2(1);
dd.window = vp->PlatformHandle;
dd.program = getProgram(dd.window, dd.new_program);
dd.cmd_lists.reserve(draw_data->CmdListsCount);
dd.ub = renderer->allocUniform(sizeof(Vec4) * 2);
const Vec4 canvas_mtx[] = {
Vec4(2.f / dd.w, 0, -1 + (float)-dd.x * 2.f / dd.w, 0),
Vec4(0, -2.f / dd.h, 1 + (float)dd.y * 2.f / dd.h, 0)
};
memcpy(dd.ub.ptr, &canvas_mtx, sizeof(canvas_mtx));
for (int i = 0; i < draw_data->CmdListsCount; ++i) {
ImDrawList* cmd_list = draw_data->CmdLists[i];
@ -4428,9 +4442,14 @@ struct EditorUIRenderPlugin final : StudioApp::GUIPlugin
out_cmd_list.vtx_buffer = renderer->allocTransient(cmd_list->VtxBuffer.size_in_bytes());
memcpy(out_cmd_list.vtx_buffer.ptr, &cmd_list->VtxBuffer[0], cmd_list->VtxBuffer.size_in_bytes());
out_cmd_list.commands.resize(cmd_list->CmdBuffer.size());
for (int i = 0, c = out_cmd_list.commands.size(); i < c; ++i) {
out_cmd_list.commands[i] = cmd_list->CmdBuffer[i];
out_cmd_list.commands.reserve(cmd_list->CmdBuffer.size());
for (int i = 0, c = cmd_list->CmdBuffer.size(); i < c; ++i) {
if (cmd_list->CmdBuffer[i].UserCallback) {
cmd_list->CmdBuffer[i].UserCallback(cmd_list, &cmd_list->CmdBuffer[i]);
}
else {
out_cmd_list.commands.push(cmd_list->CmdBuffer[i]);
}
}
}
}
@ -4463,18 +4482,18 @@ struct EditorUIRenderPlugin final : StudioApp::GUIPlugin
if (!tex) tex = *default_texture;
gpu::bindTextures(&tex, 0, 1);
const u32 h = u32(clamp(pcmd->ClipRect.w - pcmd->ClipRect.y, 0.f, 65535.f));
const u32 h = u32(clamp((pcmd->ClipRect.w - pcmd->ClipRect.y) * wdd.scale.y, 0.f, 65535.f));
if (gpu::isOriginBottomLeft()) {
gpu::scissor(u32(maximum(pcmd->ClipRect.x - wdd.x, 0.0f)),
wdd.h - u32(maximum(pcmd->ClipRect.y - wdd.y, 0.0f)) - h,
u32(clamp(pcmd->ClipRect.z - pcmd->ClipRect.x, 0.f, 65535.f)),
u32(clamp(pcmd->ClipRect.w - pcmd->ClipRect.y, 0.f, 65535.f)));
gpu::scissor(u32(maximum((pcmd->ClipRect.x - wdd.x) * wdd.scale.x, 0.0f)),
wdd.h - u32(maximum((pcmd->ClipRect.y - wdd.y) * wdd.scale.y, 0.0f)) - h,
u32(clamp((pcmd->ClipRect.z - pcmd->ClipRect.x) * wdd.scale.x, 0.f, 65535.f)),
u32(clamp((pcmd->ClipRect.w - pcmd->ClipRect.y) * wdd.scale.y, 0.f, 65535.f)));
} else {
gpu::scissor(u32(maximum(pcmd->ClipRect.x - wdd.x, 0.0f)),
u32(maximum(pcmd->ClipRect.y - wdd.y, 0.0f)),
u32(clamp(pcmd->ClipRect.z - pcmd->ClipRect.x, 0.f, 65535.f)),
u32(clamp(pcmd->ClipRect.w - pcmd->ClipRect.y, 0.f, 65535.f)));
gpu::scissor(u32(maximum((pcmd->ClipRect.x - wdd.x) * wdd.scale.x, 0.0f)),
u32(maximum((pcmd->ClipRect.y - wdd.y) * wdd.scale.y, 0.0f)),
u32(clamp((pcmd->ClipRect.z - pcmd->ClipRect.x) * wdd.scale.x, 0.f, 65535.f)),
u32(clamp((pcmd->ClipRect.w - pcmd->ClipRect.y) * wdd.scale.y, 0.f, 65535.f)));
}
gpu::drawElements(gpu::PrimitiveType::TRIANGLES, elem_offset * sizeof(u32) + cmd_list.idx_buffer.offset, pcmd->ElemCount, gpu::DataType::U32);
@ -4495,19 +4514,18 @@ struct EditorUIRenderPlugin final : StudioApp::GUIPlugin
vb_offset = 0;
ib_offset = 0;
for (WindowDrawData& dd : window_draw_data) {
gpu::setCurrentWindow(dd.window);
gpu::setFramebuffer(nullptr, 0, gpu::INVALID_TEXTURE, gpu::FramebufferFlags::NONE);
if (dd.render_target) {
gpu::setFramebuffer(&dd.render_target, 1, gpu::INVALID_TEXTURE, gpu::FramebufferFlags::NONE);
}
else {
gpu::setCurrentWindow(dd.window);
gpu::setFramebuffer(nullptr, 0, gpu::INVALID_TEXTURE, gpu::FramebufferFlags::NONE);
}
gpu::viewport(0, 0, dd.w, dd.h);
const Vec4 canvas_mtx[] = {
Vec4(2.f / dd.w, 0, -1 + (float)-dd.x * 2.f / dd.w, 0),
Vec4(0, -2.f / dd.h, 1 + (float)dd.y * 2.f / dd.h, 0)
};
gpu::update(ub, &canvas_mtx, sizeof(canvas_mtx));
gpu::bindUniformBuffer(UniformBuffer::DRAWCALL, ub, 0, sizeof(canvas_mtx));
gpu::bindUniformBuffer(UniformBuffer::DRAWCALL, dd.ub.buffer, dd.ub.offset, dd.ub.size);
const float clear_color[] = {0.2f, 0.2f, 0.2f, 1.f};
gpu::clear(gpu::ClearFlags::COLOR | gpu::ClearFlags::DEPTH, clear_color, 1.0);
gpu::clear(gpu::ClearFlags::COLOR | gpu::ClearFlags::DEPTH, &dd.clear_color.x, 1.0);
if (dd.new_program) {
const char* vs =
R"#(
@ -4557,10 +4575,10 @@ struct EditorUIRenderPlugin final : StudioApp::GUIPlugin
Renderer* renderer;
const gpu::TextureHandle* default_texture;
Array<WindowDrawData> window_draw_data;
gpu::BufferHandle ub;
u32 ib_offset;
u32 vb_offset;
EditorUIRenderPlugin* plugin;
bool should_setup = true;
};
@ -4585,10 +4603,6 @@ struct EditorUIRenderPlugin final : StudioApp::GUIPlugin
m_texture = renderer->createTexture(width, height, 1, gpu::TextureFormat::RGBA8, gpu::TextureFlags::NO_MIPS, mem, "editor_font_atlas");
ImGui::GetIO().Fonts->TexID = m_texture;
Renderer::MemRef ub_mem;
ub_mem.size = sizeof(Vec4) * 2;
m_ub = renderer->createBuffer(ub_mem, gpu::BufferFlags::UNIFORM_BUFFER);
m_render_interface.create(app, *renderer);
app.setRenderInterface(m_render_interface.get());
}
@ -4600,13 +4614,64 @@ struct EditorUIRenderPlugin final : StudioApp::GUIPlugin
shutdownImGui();
PluginManager& plugin_manager = m_engine.getPluginManager();
Renderer* renderer = (Renderer*)plugin_manager.getPlugin("renderer");
renderer->destroy(m_ub);
for (gpu::ProgramHandle program : m_programs) {
renderer->destroy(program);
}
if (m_texture) renderer->destroy(m_texture);
}
void render(gpu::TextureHandle rt, Vec2 rt_size, ImDrawData* draw_data, Vec2 scale) override {
Renderer* renderer = static_cast<Renderer*>(m_engine.getPluginManager().getPlugin("renderer"));
RenderJob& cmd = renderer->createJob<RenderJob>(renderer->getCurrentFrameAllocator());
cmd.plugin = this;
LinearAllocator& allocator = renderer->getCurrentFrameAllocator();
RenderJob::WindowDrawData& dd = cmd.window_draw_data.emplace(allocator);
dd.w = u32(rt_size.x);
dd.h = u32(rt_size.y);
dd.x = i32(0);
dd.y = i32(0);
dd.window = nullptr;
dd.program = cmd.getProgram(dd.window, dd.new_program);
dd.cmd_lists.reserve(draw_data->CmdListsCount);
dd.ub = renderer->allocUniform(sizeof(Vec4) * 2);
dd.render_target = rt;
dd.scale = scale;
dd.clear_color = Vec4(0);
const Vec2 offset(0);
const Vec4 canvas_mtx[] = {
Vec4(2.f / dd.w * scale.x, 0, -1 + (float)-offset.x * 2.f / dd.w * scale.x, 0),
Vec4(0, -2.f / dd.h * scale.y, 1 + (float)offset.y * 2.f / dd.h * scale.y, 0)
};
memcpy(dd.ub.ptr, &canvas_mtx, sizeof(canvas_mtx));
for (int i = 0; i < draw_data->CmdListsCount; ++i) {
ImDrawList* cmd_list = draw_data->CmdLists[i];
RenderJob::CmdList& out_cmd_list = dd.cmd_lists.emplace(allocator);
out_cmd_list.idx_buffer = renderer->allocTransient(cmd_list->IdxBuffer.size_in_bytes());
memcpy(out_cmd_list.idx_buffer.ptr, &cmd_list->IdxBuffer[0], cmd_list->IdxBuffer.size_in_bytes());
out_cmd_list.vtx_buffer = renderer->allocTransient(cmd_list->VtxBuffer.size_in_bytes());
memcpy(out_cmd_list.vtx_buffer.ptr, &cmd_list->VtxBuffer[0], cmd_list->VtxBuffer.size_in_bytes());
out_cmd_list.commands.reserve(cmd_list->CmdBuffer.size());
for (int i = 0, c = cmd_list->CmdBuffer.size(); i < c; ++i) {
if (cmd_list->CmdBuffer[i].UserCallback) {
cmd_list->CmdBuffer[i].UserCallback(cmd_list, &cmd_list->CmdBuffer[i]);
}
else {
out_cmd_list.commands.push(cmd_list->CmdBuffer[i]);
}
}
}
cmd.default_texture = &m_texture;
cmd.should_setup = false;
renderer->queue(cmd, 0);
}
void onWindowGUI() override {}
@ -4616,7 +4681,6 @@ struct EditorUIRenderPlugin final : StudioApp::GUIPlugin
void shutdownImGui()
{
imnodes::DestroyContext();
ImGui::DestroyContext();
}
@ -4626,7 +4690,6 @@ struct EditorUIRenderPlugin final : StudioApp::GUIPlugin
Renderer* renderer = static_cast<Renderer*>(m_engine.getPluginManager().getPlugin("renderer"));
RenderJob& cmd = renderer->createJob<RenderJob>(renderer->getCurrentFrameAllocator());
cmd.plugin = this;
cmd.ub = m_ub;
renderer->queue(cmd, 0);
renderer->frame();
@ -4637,7 +4700,6 @@ struct EditorUIRenderPlugin final : StudioApp::GUIPlugin
Engine& m_engine;
HashMap<void*, gpu::ProgramHandle> m_programs;
gpu::TextureHandle m_texture;
gpu::BufferHandle m_ub;
Local<RenderInterfaceImpl> m_render_interface;
};
@ -4935,7 +4997,7 @@ struct StudioAppPlugin : StudioApp::IPlugin
m_env_probe_plugin.init();
m_model_plugin.init();
m_particle_editor = ParticleEditor::create(m_app);
m_particle_editor = ParticleEditor::create(m_app, m_editor_ui_render_plugin);
m_app.addPlugin(*m_particle_editor.get());
m_particle_emitter_plugin.m_particle_editor = m_particle_editor.get();

View file

@ -0,0 +1,10 @@
#pragma once
#include "../gpu/gpu.h"
#include <imgui/imgui.h>
namespace Lumix {
struct IImGuiRenderer {
virtual void render(gpu::TextureHandle rt, Vec2 rt_size, ImDrawData* dd, Vec2 scale) = 0;
};
};