Closed folays closed 3 years ago
@folays Thanks for the proposal. In my mind, this is a advanced trick and could be archived easily by following code.
func buildSomething() g.Layout {
if someCondition {
return g.Layout{}
}
// Build complex layout here
return g.Layout{...}
}
func loop() {
...
g.Child(..).Layout(buildSomething)
...
}
I intend to keep the layout gramma as simple as it could be.
Thanks for you answer. At the first glance, it made me think that maybe I had missed or overlooked on something.
After a second thought, I think have identified the following:
func buildSomething() g.Widget {}
Of course, your buildSomething
function which return a g.Widget
can "run" and condition its code path to some someCondition
user-defined/user-controlled variables.
In your example, those (potentially "build complex layout here") would run only if the code path get to this point.
(*g.Widget).Layout()
having an open/unopened state(Of course, this whole discussion is relevant only for Widget
which are not drawn into their "unopened" state, and the topic is to try to skip "declaring" their children hierarchy in the subs-.Layout()
when they won't be draw anyway)
"Inside" the Layout()
parenthesis characters ((...)
) we are not in the context of a "function".
When this code path is reached, all .Layout()
arguments will be evaluated, no matter if they will be used or not.
Furthermore, we cannot pass (to this .Layout()
function) a buildSomething
argument, we could only pass buildSomething()
argument.
And so again buildSomething
would also run.
By the way, I think that in your example above, you cannot do (it wouldn't compile):
g.Child(..).Layout(buildSomething)
But you would be expected to do:
g.Child(..).Layout(buildSomething()) // <--------- added () there to call the function
And this where my point is, what .Layout()
will do about :
buildSomething
: would be called every loop, but it is not possible (.Layout()
expect []Widgets
, not func() Widget
)buildSomething()
: will only be called once when evaluating .Layout()
arguments (but the resulting Widget
of the call would be called at each loop)It seems that .Layout()
:
Of couse due to the Go language, the only solution an user has to "act" on some variable, is to insert a "function" somewhere.
I guess that buildSomething
is not an answer, because really, the buildSomething
"function" is /"not"/ used as a fundamental function which purpose would be to be called repeatedly, but rather only once to get its immediate result.
In a declarative chain, nothing really condition calling buildSomething()
, which would be called once, and its result and sub-results always being put in the declarative chain.
Besides that, putting in the buildSomething
function, a if
related to the "opened state" of a known parent Widget.Layout()
seems to be nonsensical, because:
Since .Layout()
expect to evaluate all its arguments "now", but to run .Build()
on them ONLY IF they must be draw, it seems rational that one of the arguments would be needed to be a g.Custom
func (really, any function with a .Build()
method, which act as a soft of a "dummy" widget only having sub-Layout), which would serve to break the "declarative chain".
Raising awareness on:
g.Custom
breaking the "declarative chain").Layout()
of "unopened"/"unselected widgetsStill, I agree without you that the grammar should be keep as simple as possible.
Especially, replacing the signature of the .Layout(...Widgets)
API with a .Layout(...func())
would be probably less user-friendly.
Maybe proposing a .LayoutCustom(...func())
? Which could be implemented such as:
(AnyWidget
being e.g. *g.TabItemWidget
or any other widget having an "openable" state) :
func (any *AnyWidget) LayoutCustom(builder func() Widget) {
any.layout = g.Custom(func() {
builder().Build()
})
}
Which could be used to break the declarative chain with:
g.TabItem("tab2").LayoutCustom(func() g.Widget {
return g.Layout {
g.Label("this is tab2 content lazy declared"),
g.TreeNode("and all sub-children will also be lazy declared").Layout(...),
....,
}
})
Or not proposing it, and assuming that users which would need it, would be able to figure how to do it themselves.
Regards,
@folays Basically I think this is a advance trick to optimize. There are many solutions could be done in different scenarios, like but not limited as below.
To provide a lazy load mechanism is not hard, but it still will increase the complicity to understand what's going on behind it.
One example is the giu.Conditional widget. The reason why it was invented is I want to reduce the chance to use giu.Custom. But after more and more practices, I found out in most cases when I need to conditional build something, it is complicated than just simply, if this show button else show label.
As it is an advanced trick, I think a simple system is easier to optimize.
Problem :
Due to the "declarative" nature of
*Widgets.Layout(...)
ingiu
, and the call-chain used to declare them, the whole hierarchy ofgiu
widgets are instantiated at each ui loop. (though only a small struct initialisation for each widget, on the "side" ofgiu
only).Some
Widgets
have an internal "open" status (internal toC++ imgui
, returned byimgui::Begin*V
). For example:TabItem
TreeNode
.WidgetWindow
: but the(*WindowWidget).Layout()
function would need to also be fixed as per your recent https://github.com/AllenDang/giu/commit/7edfa11fc6d751f3fc28a2698453b63d4889340eWith both
C++ imgui
andimgui-go
paragdim, users of those widgets would code some sort of:When you have (possibly multiples)
Widgets
with can thus be "unopened", theif
in the code above would alleviate the CPU. That could be relevant in either:giu
(needed for example when drawing ag.Image(g.ToTexture(imgui.TextureID(renderTexID)))
rendered by ashared-context
drawing inside aFBO
)Solution :
Currently this can be alleviated using:
There above, the
func() {}
contained in theg.Custom()
helps putting a "barrier" between declarative "sections". I use the term "declarative" because allg.*Widget
have mainly the effect of declaring agiu.*Widget
struct. Their.Build()
functions are only invoked indeed when drawing is needed.The "declarative" instructions inside the
func() {}
are thus only invoked IF the(*CustomWidget).Build()
function is invoked.So... What's the problem?
The
giu
paradigm of beingImmediate
and alldeclarative
with not-so-muchif
s could leave users to wonder how they would go about not needlessly executing a long chain ofg.WidgetParent().Layout( g.WidgetChild(...) )
when theWidgetParent
is not opened.Maybe the possibility of doing the example above of using
g.Custom
should be documented.Alternative :
Or maybe even could
giu
provide ag.LazyWidget()
function?Which would be used by an user with:
g.Label("tab3 child lazy")
or whatever other widgets being declared there, this block of code would only be executed if "tab3" is selected.The only thing achieved by the "alternative" construct above is to alleviate the new user of
giu
the difficult phase of needing to figure out they could go "lazy/ondemand" by using ag.Layout{ ... }.Build()
construct inside a parentBuild(...)
function customarily provided byg.Custom()
. (because beginners should probably not be concerned too soon with the.Build()
paradigm ofgiu
, and only take knowledge of it maybe in later advanced stage)