Use String as ID, working visible toggle

This commit is contained in:
Simon Lübeß
2024-11-19 22:23:40 +01:00
parent d259ce5dfa
commit 7fd36de77a
11 changed files with 272 additions and 41 deletions
+92 -4
View File
@@ -8,6 +8,7 @@
"name": "glitchy-editor-ui",
"version": "0.0.0",
"dependencies": {
"@rollup/plugin-typescript": "^12.1.1",
"dockview-react": "^1.17.2",
"eslint-plugin-react": "^7.37.2",
"react": "^18.3.1",
@@ -16,6 +17,7 @@
},
"devDependencies": {
"@eslint/js": "^9.11.1",
"@types/node": "^22.9.0",
"@types/react": "^18.3.10",
"@types/react-dom": "^18.3.0",
"@vitejs/plugin-react": "^4.3.2",
@@ -942,6 +944,79 @@
"node": ">= 8"
}
},
"node_modules/@rollup/plugin-typescript": {
"version": "12.1.1",
"resolved": "https://registry.npmjs.org/@rollup/plugin-typescript/-/plugin-typescript-12.1.1.tgz",
"integrity": "sha512-t7O653DpfB5MbFrqPe/VcKFFkvRuFNp9qId3xq4Eth5xlyymzxNpye2z8Hrl0RIMuXTSr5GGcFpkdlMeacUiFQ==",
"dependencies": {
"@rollup/pluginutils": "^5.1.0",
"resolve": "^1.22.1"
},
"engines": {
"node": ">=14.0.0"
},
"peerDependencies": {
"rollup": "^2.14.0||^3.0.0||^4.0.0",
"tslib": "*",
"typescript": ">=3.7.0"
},
"peerDependenciesMeta": {
"rollup": {
"optional": true
},
"tslib": {
"optional": true
}
}
},
"node_modules/@rollup/plugin-typescript/node_modules/@rollup/pluginutils": {
"version": "5.1.3",
"resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.3.tgz",
"integrity": "sha512-Pnsb6f32CD2W3uCaLZIzDmeFyQ2b8UWMFI7xtwUezpcGBDVDW6y9XgAWIlARiGAo6eNF5FK5aQTr0LFyNyqq5A==",
"dependencies": {
"@types/estree": "^1.0.0",
"estree-walker": "^2.0.2",
"picomatch": "^4.0.2"
},
"engines": {
"node": ">=14.0.0"
},
"peerDependencies": {
"rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0"
},
"peerDependenciesMeta": {
"rollup": {
"optional": true
}
}
},
"node_modules/@rollup/plugin-typescript/node_modules/picomatch": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz",
"integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/@rollup/plugin-typescript/node_modules/resolve": {
"version": "1.22.8",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz",
"integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==",
"dependencies": {
"is-core-module": "^2.13.0",
"path-parse": "^1.0.7",
"supports-preserve-symlinks-flag": "^1.0.0"
},
"bin": {
"resolve": "bin/resolve"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/@rollup/pluginutils": {
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-4.2.1.tgz",
@@ -1224,6 +1299,15 @@
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
"integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="
},
"node_modules/@types/node": {
"version": "22.9.0",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.9.0.tgz",
"integrity": "sha512-vuyHg81vvWA1Z1ELfvLko2c8f34gyA0zaic0+Rllc5lbCnbSyuvb2Oxpm6TAUAC/2xZN3QGqxBNggD1nNR2AfQ==",
"dev": true,
"dependencies": {
"undici-types": "~6.19.8"
}
},
"node_modules/@types/prop-types": {
"version": "15.7.13",
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.13.tgz",
@@ -2455,8 +2539,7 @@
"node_modules/estree-walker": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
"dev": true
"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="
},
"node_modules/esutils": {
"version": "2.0.3",
@@ -3769,7 +3852,7 @@
"version": "4.24.0",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.24.0.tgz",
"integrity": "sha512-DOmrlGSXNk1DM0ljiQA+i+o0rSLhtii1je5wgk60j49d1jHT5YYttBv1iWOnYSTG+fZZESUOSNiAl89SIet+Cg==",
"dev": true,
"devOptional": true,
"dependencies": {
"@types/estree": "1.0.6"
},
@@ -4183,7 +4266,6 @@
"version": "5.6.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz",
"integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==",
"dev": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
@@ -4229,6 +4311,12 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/undici-types": {
"version": "6.19.8",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz",
"integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==",
"dev": true
},
"node_modules/update-browserslist-db": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz",
+2
View File
@@ -11,6 +11,7 @@
"preview": "vite preview"
},
"dependencies": {
"@rollup/plugin-typescript": "^12.1.1",
"dockview-react": "^1.17.2",
"eslint-plugin-react": "^7.37.2",
"react": "^18.3.1",
@@ -19,6 +20,7 @@
},
"devDependencies": {
"@eslint/js": "^9.11.1",
"@types/node": "^22.9.0",
"@types/react": "^18.3.10",
"@types/react-dom": "^18.3.0",
"@vitejs/plugin-react": "^4.3.2",
@@ -0,0 +1,10 @@
import {MouseEventHandler, ReactElement} from "react";
export function IconCheckButton({isChecked, iconChecked, iconUnchecked, onClick} : {isChecked: boolean, iconChecked: string, iconUnchecked: string, onClick: MouseEventHandler }) : ReactElement
{
return (
<div onClick={onClick}>
<img src={isChecked ? iconChecked : iconUnchecked} alt="" />
</div>
);
}
+7 -1
View File
@@ -1,4 +1,4 @@
import { Entity } from "./Entity.ts";
import {Entity, EntityId} from "./Entity.ts";
export interface IEngineGlue
{
@@ -12,6 +12,7 @@ export interface IEngineGlue
handleHoverNonClientArea(hover: boolean): void;
onClickCreateEmptyEntity(): void;
setEntityVisibility(entityId: EntityId, isVisible: boolean): void;
requestEntityHierarchyUpdate(): void;
onUpdateEntities?: (entities: Entity[]) => void;
@@ -60,6 +61,11 @@ class DevEngineGlue implements IEngineGlue {
{
logNotImplemented("onClickCreateEmptyEntity");
}
setEntityVisibility(entityId: EntityId, isVisible: boolean): void
{
logNotImplemented("onClickCreateEmptyEntity");
console.log(`entityId ${entityId} isVisible ${isVisible}`)
}
requestEntityHierarchyUpdate(): void
{
+3 -3
View File
@@ -1,4 +1,4 @@
export type EntityId = number;
export type EntityId = string;
export class Entity
{
@@ -7,10 +7,10 @@ export class Entity
visible: boolean = true;
children: EntityId[];
constructor(name: string, key: number, children: EntityId[] = [])
constructor(name: string, id: EntityId, children: EntityId[] = [])
{
this.name = name;
this.id = key;
this.id = id;
this.children = children;
}
}
@@ -1,8 +1,8 @@
import {IDockviewPanelProps} from "dockview";
import {IDockviewPanelProps} from "dockview";
import {MenuBar, MenuItem} from "../Components/Menu.tsx";
import "./EntityHierarchyWindow.css";
import {MouseEvent, MouseEventHandler, ReactElement, useEffect, useState} from "react";
import React, {MouseEvent, MouseEventHandler, ReactElement, useEffect, useState} from "react";
import IconVisible from "../assets/Icons/AntDesign/eye-visible.svg";
import IconInvisible from "../assets/Icons/AntDesign/eye-invisible.svg";
@@ -12,7 +12,7 @@ import {EngineGlue} from "../EngineGlue";
import {enableMapSet} from "immer"
import {Entity, EntityId} from "../Entity.ts";
import * as sea from "node:sea";
import {IconCheckButton} from "../Components/IconCheckButton.tsx";
enableMapSet()
@@ -25,10 +25,10 @@ type EntityMap = {
const entityHierarchy: EntityMap = {
selectedIds: new Set<EntityId>(),
entities: new Map<EntityId, Entity>([
[0, new Entity("Root", 0, [1, 2])],
[1, new Entity("Entity 1", 1, [])],
[2, new Entity("Entity 2", 2, [3])],
[3, new Entity("Entity 3 in 2", 3, [])]
["0", new Entity("Root", "0", ["1", "2"])],
["1", new Entity("Entity 1", "1", [])],
["2", new Entity("Entity 2", "2", ["3"])],
["3", new Entity("Entity 3 in 2", "3", [])]
])
// // 0 is hardcoded to be the root. The editor must provide this "pseudo"-entity.
// 0: new Entity("Root", 0, [1, 2]),
@@ -160,27 +160,24 @@ function VisibilityToggle({item, onClick, updateEntities} : {item: Entity, onCli
if (entity !== undefined)
{
entity.visible = !item.visible;
entity.visible = !entity.visible;
EngineGlue.setEntityVisibility(entity.id, entity.visible);
}
});
console.log("Clickidy" + item.visible);
onClick(event);
event.stopPropagation();
}
return (
<div onClick={clickHandler}>
<img src={item.visible ? IconVisible : IconInvisible} />
</div>
);
return <IconCheckButton isChecked={item.visible} onClick={clickHandler} iconChecked={IconVisible} iconUnchecked={IconInvisible}/>
}
function EntityTree({items, updateItems, entityFilter}: { items: EntityMap, updateItems: Updater<EntityMap>, entityFilter: string })
{
const rootEntity = items.entities.get(0);
const rootEntity = items.entities.get("0");
if (entityFilter.length == 0)
{
@@ -4,6 +4,7 @@ import {MenuBar, MenuDivider, MenuItem} from "../Components/Menu.tsx";
import {ReactElement, useEffect, useRef} from "react";
import '../EngineGlue.ts'
import {EngineGlue} from "../EngineGlue.ts";
import {StartupCard} from "../Components/StartupCard.tsx";
//something = 'testing';
@@ -21,6 +22,7 @@ export default function MainWindow() {
function TitleBar({ projectName, fileName } : {projectName: string, fileName: string}): ReactElement
{
// {false ? "🗗" : "🗖"}
return (
<div className="title-bar">
<div className="title-bar__icon"/>
@@ -56,7 +58,7 @@ function MainMenuBar(): ReactElement
<MenuItem text="File" onClick={() => {
console.log("Test")
}}>
<MenuItem text="New Scene..." hotkey="Ctrl + N"/>
<MenuItem text="New Scene..." hotkey="Ctrl + N" onClick={() => window.open("file:///index.html")}/>
<MenuItem text="Open Scene..." hotkey="Ctrl + O"/>
<MenuItem text="Open recent Scene">
<MenuItem text="1. Bli"/>
+53
View File
@@ -0,0 +1,53 @@
using Ultralight.CAPI;
using System;
namespace GlitchyEditor.Ultralight;
class JSObject
{
private JSContextRef _jsContext;
private JSObjectRef _jsObject;
public JSObjectRef ObjectRef => _jsObject;
private this(JSContextRef context)
{
_jsContext = context;
}
public static JSObject CreateArray(JSContextRef context)
{
JSObject object = new JSObject(context);
object._jsObject = JSObjectMakeArray(context, 0, null, null);
return object;
}
public JSObjectRef this[int index]
{
get => JSObjectGetPropertyAtIndex(_jsContext, _jsObject, (uint32)index, null);
set => JSObjectSetPropertyAtIndex(_jsContext, _jsObject, (uint32)index, value, null);
}
}
class JSString
{
private JSContextRef _context;
private JSStringRef _string;
public this(JSContextRef context, JSValueRef jsValue)
{
_context = context;
_string = JSValueToStringCopy(context, jsValue, null);
}
public ~this()
{
//JSStringRelease(_string);
}
public void GetUTF8String(String buffer)
{
buffer.Append(Span<char16>((char16*)JSStringGetCharactersPtr(_string), JSStringGetLength(_string)));
}
}
@@ -49,4 +49,22 @@ static class UltralightHelper
{
return JSObjectMake(context, NativeFunctionClass(), Internal.UnsafeCastToPtr(ownCallback));
}
public static JSObjectRef CreateObjectFromString(JSContextRef context, StringView string)
{
JSStringRef entityName = JSStringCreateWithUTF8CString(string.ToScopeCStr!());
JSValueRef nameObject = JSValueMakeString(context, entityName);
JSStringRelease(entityName);
return nameObject;
}
public static JSObjectRef CreateObjectFromString(JSContextRef context, String string)
{
JSStringRef entityName = JSStringCreateWithUTF8CString(string.CStr());
JSValueRef nameObject = JSValueMakeString(context, entityName);
JSStringRelease(entityName);
return nameObject;
}
}
@@ -5,6 +5,8 @@ using GlitchyEngine.Collections;
using GlitchyEngine.World;
using System.Collections;
using GlitchyEngine.Core;
using System.Reflection;
using GlitchyEngine.World.Components;
using static GlitchyEngine.UI.Window;
namespace GlitchyEditor.Ultralight;
@@ -198,8 +200,10 @@ class UltralightMainWindow : UltralightWindow
defer JSStringRelease(childrenProperty);
JSObjectRef jsArray = JSObjectMakeArray(context, 0, null, null);
uint32 index = 0;
String id = scope .(18);
for (let (entityId, operation) in updates)
{
if (operation == .None)
@@ -208,25 +212,17 @@ class UltralightMainWindow : UltralightWindow
}
JSObjectRef jsEntity = JSObjectMake(context, null, null);
/*
name: string;
id: EntityId;
visible: boolean = true;
children: EntityId[];
*/
JSValueRef idObject = JSValueMakeNumber(context, (double)(uint64)entityId);
entityId.ToString(id..Clear());
JSValueRef idObject = UltralightHelper.CreateObjectFromString(context, id);
JSObjectSetProperty(context, jsEntity, idProperty, idObject, 0, null);
if (operation != .Delete)
{
EntityEntry entry = _entities[entityId];
JSStringRef entityName = JSStringCreateWithUTF8CString(entry.Name);
JSValueRef nameObject = JSValueMakeString(context, entityName);
JSStringRelease(entityName);
JSValueRef nameObject = UltralightHelper.CreateObjectFromString(context, entry.Name);
JSObjectSetProperty(context, jsEntity, nameProperty, nameObject, 0, null);
JSValueRef visibleObject = JSValueMakeBoolean(context, true);
@@ -236,7 +232,8 @@ class UltralightMainWindow : UltralightWindow
for (let childId in entry.Children)
{
JSValueRef childIdObject = JSValueMakeNumber(context, (double)(uint64)childId);
childId.ToString(id..Clear());
JSValueRef childIdObject = UltralightHelper.CreateObjectFromString(context, id);
JSObjectSetPropertyAtIndex(context, childrenArray, (uint32)@childId.Index, childIdObject, null);
}
@@ -351,7 +348,52 @@ class UltralightMainWindow : UltralightWindow
{
_window.Close();
}
[BindToJsFunction("setEntityVisibility")]
void HandleSetEntityVisibility(JSContextRef context, JSObjectRef thisObject, Span<JSValueRef> arguments, JSValueRef* exception = null)
{
if (arguments.Length != 2)
{
Log.EngineLogger.Error("EngineGlue.setEntityVisibility: called with wrong number of arguments.");
return;
}
/*if (!JSValueIsNumber(context, arguments[0]))
{
Log.EngineLogger.Error($"EngineGlue.setEntityVisibility: expected number as argument 0, but received {JSValueGetType(context, arguments[0])} instead.");
return;
}*/
if (!JSValueIsBoolean(context, arguments[1]))
{
Log.EngineLogger.Error($"EngineGlue.setEntityVisibility: expected boolean as argument 1, but received {JSValueGetType(context, arguments[1])} instead.");
return;
}
JSString jsId = scope .(context, arguments[0]);
String idString = scope .();
jsId.GetUTF8String(idString);
if (uint64.Parse(idString) case .Ok(let id))
{
bool visible = JSValueToBoolean(context, arguments[1]);
UUID entityId = .((uint64)id);
if (Editor.Instance.CurrentScene.GetEntityByID(entityId) case .Ok(let entity))
{
if (entity.TryGetComponent<EditorFlagsComponent>(let flags))
{
Enum.SetFlagConditionally(ref flags.Flags, .HideInScene, !visible);
}
}
else
{
Log.ClientLogger.Error($"Entity with ID {entityId} doesn't exist.");
}
}
}
[BindToJsFunction("callFromEngine_updateEntities")]
private JsFunctionCall uiCallUpdateEntities ~ delete _;
@@ -11,7 +11,7 @@ using GlitchyEngine.UI;
namespace GlitchyEditor.Ultralight;
abstract class UltralightWindow
class UltralightWindow
{
protected internal Window _window ~ delete _;
@@ -21,11 +21,13 @@ abstract class UltralightWindow
private Texture2D _texture ~ _.ReleaseRef();
protected this(StringView title = "GlitchyEngine ♥ Ultralight", StringView startUrl = "file:///app.html")
protected this(StringView title = "GlitchyEngine ♥ Ultralight", StringView startUrl = "file:///app.html", uint2 size = .Zero)
{
WindowDescription windowDesc = .Default;
windowDesc.Title = title;
windowDesc.WindowStyle = .CustomTitle;
windowDesc.Width = size.X;
windowDesc.Height = size.Y;
_window = new Window(windowDesc);
_window.EventCallback = new => EventHandler;
@@ -68,6 +70,8 @@ abstract class UltralightWindow
ulViewSetAddConsoleMessageCallback(_view, => OnAddConsoleMessage, userData);
ulViewSetChangeCursorCallback(_view, => OnChangeCursor, userData);
ulViewSetCreateChildViewCallback(_view, => OnCreateChildView, userData);
}
#endregion Init
@@ -493,5 +497,14 @@ abstract class UltralightWindow
Log.ClientLogger.Log(logLevel, prettyMessage);
}
private static ULView OnCreateChildView(void* user_data, ULView caller, ULString opener_url, ULString target_url, bool is_popup, ULIntRect popup_rect)
{
StringView target = StringView(ulStringGetData(target_url), ulStringGetLength(target_url));
UltralightWindow newWindow = new UltralightWindow(startUrl: target, size: .((uint32)(popup_rect.right - popup_rect.left), (uint32)(popup_rect.bottom - popup_rect.top)));
UltralightLayer.Instance.[Friend]_windows.Add(newWindow);
return newWindow._view;
}
#endregion Ultralight Callbacks
}