ImHUI:使用实际HTML输出的网络Imgui样式库

2021-03-04 00:12:52

我是Dear ImGUI的粉丝(也是赞助商)。我在上面写过几篇文章,包括这篇文章和这篇文章

最近我想,我不知道尝试制作一个遵循类似API风格的HTML库会是什么样子。

注意:这不是在JavaScript中运行的亲爱的ImGUI。为此,请参阅此回购。不同之处在于,大多数ImGUI库都呈现自己的文本和图形。更具体地说,它们为字形以及您的UI生成顶点位置,纹理坐标和顶点颜色的数组。您可以使用任何感觉的方法绘制每个顶点阵列(WebGL,Op​​enGL,Vulkan,DirectX,虚幻,Unity等)。

实际上,此实验实际上是使用< div> <输入类型="文本">,<输入类型="范围">,<按钮>等等...

缺点是它可能不如亲爱的ImGUI(或其他ImGUI)库快,尤其是如果您有一个复杂的UI以60fps更新时呢?

另一方面,对于许多用例,它实际上可能更快。见下文

在Dear ImGUI中,如果UI内容(游戏/应用)发生变化,则必须在每帧重新渲染UI。

大多数ImGUI库的样式功能非常少,但是这里我们将所有CSS样式化。

大多数布局发生在基于CSS的库外部。在Dear ImGUIall中,库的每一帧都会发生布局。在这里,如果我们要并排放置4个元素,我们只需将它们用flex或grid包围起来,然后浏览器即可处理布局。

大多数ImGUI库仅处理少量字形,它们可能会或可能不会处理彩色表情符号或日语,日语,韩语,中文。我不认为任何人都可以处理从右到左的语言,例如阿拉伯语(عربي)。

这可能不公平,我确定亲爱的ImGUI可以使用多个字体。问题是它可能很麻烦,尤其是多种样式的多种尺寸,这是浏览器最擅长的。

许多ImGUI库在语言输入方面存在问题。因为它们呈现自己的文本,所以它们具有第二类支持复杂的输入和输入法编辑器。

大多数ImGUI库对可访问性不是很友好。因为它们只是最终渲染像素,所以没有太多可访问性功能需要关注。使用HTML,浏览器和OS可以更轻松地查看元素的内容。

浏览器将考虑用户设备的HD-DPI功能来呈现文本和大多数其他小部件。

通常,如果不每帧更新1000个值,则执行的代码应少于亲爱的ImGUI。

考虑一下,亲爱的ImGUI主要是无状态AFAIK。这意味着可能需要在每次渲染时都进行诸如自动包装字符串或计算列大小之类的事情。在ImHUI的情况下,这是由浏览器处理的,并且如果元素的内容没有更改,则将大部分内容缓存。

无状态UI的ImGUI样式的优点在于,您不必设置事件处理程序,也不必将数据编组进出UI小部件。

考虑标准的JavaScript。如果您有< input type =" text">你可能有这样的代码

当然,上面的虚假反应代码是行不通的。您需要使用状态或其他一些解决方案,当您更改someObject.someProperty时,它们会做出反应以重新呈现。

函数MyTextInput(){const [value,setValue] = useState(someObject.someProperty); return {< input value = {value} onChange = {function(e){setValue(this.value); }> }}

因此,现在,React将对状态变化做出反应,但不会对someObject.someProperty变化做出反应,例如说是否选择了不同的对象。因此,您必须添加更多代码。上面的代码也没有提供将数据返回到someObject.someProperty的方法,因此您必须添加更多代码。

JavaScript不支持通过引用传递,因此我们不能采用第二种样式,否则,我们可以传递一些getter / setter对以使代码更改值。

//传入值,获得新值(在JS中仍然有效)someObject.someProperty = textInput(someObject.someProperty); //使用getter / setter生成器textInput(gs(someObject,' someProperty'));

函数gs(obj,propertyName){return {get(){return obj [propertyName]; },set(v){obj [propertyName] = v; },};}

无论如何,它绝对比普通JS或React更简单。没有其他代码可以将新值从UI返回到您的数据存储。它只是发生。

我不确定如何描述这一点。基本上,我注意到我使用ImGUI放弃了所有HTML / CSS功能,因为我知道在下面创建了哪些HTML元素。

考虑文本功能。它只需要一个字符串并在当前UI中添加一行

无法设置className。无法选择span而不是div或sub或sup或h1等。

通过查看ImGUI代码,我看到很多有状态的函数可以帮助解决这个问题(构成示例),一个解决方案是一些函数,该函数设置要创建的类型

ImGUI :: TextElement(' div')ImGUI :: text('此文本放在div'); ImGUI :: text('此文本放在一个div也是'); ImGUI :: TextElement(' span')ImGUI :: text('此文本放在一个span')中;

对于使用哪个类名或添加样式等也是如此。在整个ImGUI示例中,我看到的都是乱七八糟的东西。再举一个例子

我注意到的一件事是,至少对于Dear ImGUI来说,还有更多事情要为您决定;或者也许这是另一种说法,Dear ImGUI是比React或Vanilla JS / HTML更高级的库。

那么,ImGUI实际上比HTML更简单吗?还是它具有更高级别的组件?

换句话说,要使用原始HTML做到这一点,需要创建4个元素,将前3个元素子化为其中一个,响应输入事件,并在输入事件到达时更新数字显示。更新数字显示和< input>元素的值(如果该值从UI小部件外部更改)。

但是,如果我已经处理了现有的更高级别的UI组件,是否足以使事情变得更轻松?意味着Dear ImGUI的易用性有多少来自其范例,而大量的高级小部件库又有多少呢?

这有点像比较编程语言。 对于给定的语言,感知到的收益中有多少来自语言本身,以及来自运行于其中的标准库或公共环境。 ImGUI使用C ++功能通过引用传递。 JavaScript无法通过引用传递。换句话说,在C ++中我可以做到这一点 void multBy2(int& v){v * = 2;} int foo = 123; multBy2(foo); cout<< foo; //打印246 遵循Dear ImGUI API之后,我首先尝试通过要求您传递这样的getter-setter方法来解决此问题 var foo = 123; var fooGetterSetter = {get(){return foo; } set(v){foo = v; }}; 当然,如果使用这些库之一的要点是易于使用,那么thenit很糟糕,必须使用getter-setter方法。 我以为我可以像上面显示的那样创建getter setter生成器,这意味着为了最简单的用法,您需要使用对象,而不是光秃秃的fooyou来做类似的事情

const data = {foo:123,}; ... //滑块从0变为200ImHUI.sliderFloat(" Some Value&#34 ;, gs(data,' foo'),0 ,200);

那有两个问题。一种是无法进行类型检查,因为您必须将字符串传递给gs(对象:对象,属性名称:字符串)。

另一个是它可以在每次调用时有效地生成一个新的getter-setter方法。换句话说,虽然易于键入的代码看起来像上面的linejust一样,但是执行代码需要像这样在初始化时创建一个getter-setter方法

const data = {foo:123,}; const fooGetterSetter = gs(data,' foo'); ... //滑块从0到200ImHUI.sliderFloat(" Some Value&#34 ;,fooGetterSetter,0,200);

我可能可以做一些函数来为所有属性生成getters / setter方法,但这听起来也很糟糕,因为它将您从数据中删除了。

const data = {foo:123,}; const dataGetterSetters = generateGetterSetters(data)... //滑块从0到200ImHUI.sliderFloat(" Some Value&#34 ;, dataGetterSetter.foo,0,200) ;

另一个解决方案是要求使用一个对象,然后使所有ImHUI函数采用一个对象和一个属性名称,如下所示:

但这有一个相同的问题,因为您是通过字符串传递属性名称的,因此容易出错,并且无法检查类型。

因此,至少目前为止,我最终对其进行了更改,以便您传入该值,并将其传回一个新值。

//从0到200的滑块foo = ImHUI.sliderFloat(" Some Value&#34 ;, foo,0,200); //或//从0到200的滑块data.foo = ImHUI.sliderFloat( " Some Value&#34 ;, data.foo,0,200);

它比使用getter-setter的性能要好得多,而且比生成getter-setter的性能更高。此外,它是安全的类型。 Eslint或TypeScript都可以警告您不存在的属性,并且可能键入不匹配的内容。

我创建的第三个小部件是sliderFloat,正如我前面指出的那样,它由4个元素组成,一个div用于标签,一个div用于显示值,一个input [type = range],用于滑动器,以及一个用于排列它们的容器。我首先实现它,然后制作了一个类,它管理所有4个元素。但是后来我意识到这4个元素中的每一个都是有用的,因此当前的实现只是嵌套的ImHUI调用。一个sliderFloat是

函数slideFloat(label:字符串,value:数字,min:数字= 0,max:数字= 1){beginWrapper(' slider-float');值= slideFloatNode(值,最小值,最大值);文本(value.toFixed(2));文字(提示); endWrapper();返回值;}

例如,当前可拖动的窗口被手动编码为多个部分的组合。在外部div上,它是可伸缩的。窗口有标题栏,有标题文本,并且可以拖动以移动窗口。我可以将它们分开,以便从这些较低级别的部分构建一个窗口吗? ; s探索的东西。

您可以在当前的实时示例中看到我放入了ImGUI :: plotLines版本,该版本接受值列表并将其绘制为2D线。当前的实现使用canvasNode创建一个2D canvas,canvas返回一个Canvas2DRenderingContext,换句话说,如果您想实时绘制一些东西,可以像这样构建自己的小部件

功能circleGraph(zeroToOne:数字){const ctx = canvasNode(); const {width,height} = ctx.canvas; const radius = Math.min(width,height); ctx.beginPath(); ctx.arc(宽度/ 2,高度/ 2,半径,0,Math.PI * 2 * zeroToOne); ctx.fill();}

画布将自动调整大小以适合其容器,因此您只需在其上绘制东西即可。

事实是,canvas 2D api并不是那么快。我应该在什么时候尝试使用WebGL或让您使用WebGL。如果我使用WebGL,则存在上下文限制问题。只是要考虑一下。根据ImGUI的工作方式,如果您要绘制1000条线,那么每次UI更新时,您都必须绘制全部1000条线。在C ++ ImGUI中,只是将一些数据插入正在生成的顶点缓冲区中,而在JavaScript中,使用Canvas 2D,它需要做更多的工作来调用Canvas2D API。

我不知道这是怎么回事。我目前没有任何需要像这样的GUI的项目,但是也许如果我能将它变成某种东西,我认为这是一种稳定的考虑,可以考虑在诸如dat.gui之类的东西上使用它,这可能是遥不可及的WebGL可视化最常用的UI库。