Closed Xeverous closed 4 years ago
This is not the final API of course. It's just a step towards the direction I had in mind. Feel free to suggest PRs that I can evaluate.
As a matter of fact: before we go to 1.0 is the right time to improve a lot of the APIs, esp. those in the gallery. But keep in mind that the gallery is simply a collection of pre-packaged code that the user can use as-is, or used as basis for their own purposes. That said, I am with you that the gallery should strive to be as generically useful to a broader set of use-cases.
link_tabs
), to let users get the notebook behavior but arrange elements as they want if they do not like 2 premade factories (vnotebook
and hnotebook
)I have updated the example - you should check how it looks with hnotebook
- there is certainly something wrong with tab lengths there but given how consistent are the factories I have made (ideal mirror of v/h and alignment functions) I suspect the issue resides how tab
element occupies space that is given to it.
There is definitely something wrong with the tab
element. When htile
d, it does not occupy all the space that is given to it which causes them to look bad because each has a different length. I suspected that exactly the same problem occurs in reverse situation when they are vtile
d when they have different heights, so I tried to edit the example and discovered 2 more problems:
diff --git a/examples/notebook/main.cpp b/examples/notebook/main.cpp
index 3e8a509..d549cb9 100755
--- a/examples/notebook/main.cpp
+++ b/examples/notebook/main.cpp
@@ -27,7 +27,7 @@ auto make_tabs(view& view_)
)
, tab("Dream")
, tab("Insanity")
- , tab("Imagination")
+ , hmin_size(50, tab("Imagination"))
);
}
tab
can only take std::string
)diff --git a/examples/notebook/main.cpp b/examples/notebook/main.cpp
index 3e8a509..7611658 100755
--- a/examples/notebook/main.cpp
+++ b/examples/notebook/main.cpp
@@ -27,7 +27,7 @@ auto make_tabs(view& view_)
)
, tab("Dream")
, tab("Insanity")
- , tab("Imagination")
+ , tab(hmin_size(50, label("Imagination")))
);
}
I really should start using tests, probably starting with layout issues like this. We can't keep on using the examples as tests. I'll think about it...
That's a good point but while I can instantly find color/sizing/stretching issues in a given UI I can't imagine how would you write all the test conditions given how complex widget composing can get, even if you implemented a sort of virtual mock view/window class.
That's a good point but while I can instantly find color/sizing/stretching issues in a given UI I can't imagine how would you write all the test conditions given how complex widget composing can get, even if you implemented a sort of virtual mock view/window class.
I know it's difficult. If it was easy, I would have done it already :-) But then again, I think I can start with something simple, like what I did in Artist. Anything is better than nothing.
copy-paste mistake:
And for real issues with the tab:
htile
d, they should have equal height; if tabs are vtile
d, they should have equal width.
- Currently it is a wrapper around button or label widget, it should be more generic like all other widgets in their factories
The tab_element_base
class is a gallery element. Of course, you can have different kinds of tabs as long as it follows the basic_choice, or selectable interface.
- Tab should implement / should help implementing sizing behavior that is correct for a typical notebook: if tabs are
htile
d, they should have equal height; if tabs arevtile
d, they should have equal width.
Not sure what you mean. Pictures will help.
The
tab_element_base
class is a gallery element. Of course, you can have different kinds of tabs as long as it follows the basic_choice, or selectable interface.
And I think the problem lies here. Why the tab_element_base
has a std::string
member? It's duplicating button/label state. It should only be a behavior-defining widget, not a state widget - if someone wants a tab with text they should do tab(label("string"))
, if they want a tab with icon they should do tab(icon(...))
and so on. The tab
factory function should only add required code to make tab switching work and apply different background colors from the theme depending whether the tab is currently enabled. I'm trying here to follow your library philosophy of reusable elements and no artificial/accidental API limitations.
[...] if tabs are htiled, they should have equal height; if tabs are vtiled, they should have equal width
Not sure what you mean. Pictures will help.
I have marked in red space that should be occupied by tab's clickable area. I
Another wording: on the left, "Dream" and "Insanity" tabs should be higher, because the third tab has larger height; on the right, the "Dream" and "Insanity" tabs should be wider, because the third tab has larger width.
And I think the problem lies here. Why the
tab_element_base
has astd::string
member? It's duplicating button/label state.
Show me exactly where it is duplicated. When you are claiming things like this, it always helps to be precise.
Show me exactly where it is duplicated
IMO the tab
factory function should accept an arbitrary widget and store that widget and only appy necessary behavior for the notebook to function properly.
Another wording: on the left, "Dream" and "Insanity" tabs should be higher, because the third tab has larger height; on the right, the "Dream" and "Insanity" tabs should be wider, because the third tab has larger width.
This looks like the same issue as you had before with tiles. That is how tiles behave. (see layout docs). I'll see if there's a generic solution, or if it is an entirely different problem altogether.
IMO the
tab
factory function should accept an arbitrary widget and store that widget and only appy necessary behavior for the notebook to function properly.
Still does not make sense. Here's the real base class:
https://github.com/cycfi/elements/blob/notebook/lib/include/elements/element/button.hpp#L215
You want something else that the gallery tab does not give, make one yourself. It is easy, as the tab gallery element shows. The basic_choice
is what you want to base your custom tab from. If you want genericity, use basic_choice.
Look, I think you are misunderstanding the purpose of the gallery. It is supposed to be a collection of useful things, as generic as possible, but not try hard to be TOO generic. The generic clases are the basic elements outside the gallery from which the gallery stuff are made from.
There's a balance of ease of use and genericity. That balance should be maintained in the gallery. The key point here is that you can never please everybody and so you make it easy for them to use the gallery stuff as *exemplars to base their own compositions from.
Ok, I get what you mean but, given current state of the repository, how much code would I need to make a tab that has something different than text?
I think that elements should supply a factory function that allows to put arbitrary widget inside a notebook tab, this way then user could then do something like this:
vnotebook(
view,
pages,
tab_factory(label("foo")),
tab_factory(label("bar")),
tab_factory(image("...")),
tab_factory(htile(icon_gear, label("settings")))
)
Ok, I get what you mean but, given current state of the repository, how much code would I need to make a tab that has something different than text?
I'll leave that up to you as an exercise ;-) Make your own based on tab_element.
Ok, I get what you mean but, given current state of the repository, how much code would I need to make a tab that has something different than text?
I'll leave that up to you as an exercise ;-) Make your own based on tab_element.
Oh, and the button gallery should also have some interesting examples that you can mix and match.
This looks like the same issue as you had before with tiles. That is how tiles behave. (see layout docs). I'll see if there's a generic solution, or if it is an entirely different problem altogether.
I think this time the issue is different.
Previously all widgets were stretchable (buttons, labels, input boxes), but I only wanted input boxes to stretch. It was fixed by adding no_hstretch
around button and label columns, which caused them to not stretch which caused input boxes column to take all of the extra horizontal space.
This time there is just 1 row or column of tabs but nothing stretches.
This looks like the same issue as you had before with tiles. That is how tiles behave. (see layout docs). I'll see if there's a generic solution, or if it is an entirely different problem altogether.
I think this time the issue is different.
Previously all widgets were stretchable (buttons, labels, input boxes), but I only wanted input boxes to stretch. It was fixed by adding
no_hstretch
around button and label columns, which caused them to not stretch which caused input boxes column to take all of the extra horizontal space.This time there is just 1 row or column of tabs but nothing stretches.
I think they are the same at a fundamental level. What you are describing are effects of this, but I am not sure if I can explain completely, easily.
I think they are the same at a fundamental level. What you are describing are effects of this, but I am not sure if I can explain completely, easily.
Here: http://cycfi.github.io/elements/layout#vertical-tiles
Keypoints:
I'll leave that up to you as an exercise ;-) Make your own based on tab_element.
Ok, I will try it and see the code. Maybe then I will turn this into a PR which will express my intent clearer. I understand your points about the balance of ease of use and genericity but I don't get why the simplest "tab with text" is not actually using the label
or button
elements.
I'll leave that up to you as an exercise ;-) Make your own based on tab_element.
Ok, I will try it and see the code. Maybe then I will turn this into a PR which will express my intent clearer. I understand your points about the balance of ease of use and genericity but I don't get why the simplest "tab with text" is not actually using the
label
orbutton
elements.
Very valid points! Let's see how it goes. The gallery can evolve and be more generic as needed. The button gallery is an example.
If a child element’s maximum limits in either dimension is exceeded, the element will be aligned to the top-left.
This is the root cause then. If I have few widgets tiled vertically:
The problem is, how can I make other widgets to resize to that max horizontal size?
On this example there are 2 columns. The column with buttons is intentionally wrapped in no_hstretch
so that all of the extra space is given to input boxes. And this works because for magic reason input boxes expand to infinity. But tabs do not, they always keep their minum size. That's my problem - is there any way to make tabs auto-expand whenever there is any extra space?
If a child element’s maximum limits in either dimension is exceeded, the element will be aligned to the top-left.
This is the root cause then. If I have few widgets tiled vertically:
- the vertical size of the tiling is the sum of their minimum vertical requirements
- the horizontal size of the tiling is the max horizontal size among them
The problem is, how can I make other widgets to resize to that max horizontal size?
Agreed. I'm thinking about it as we speak. I'll need to investigate the effects if I change this behavior to have elements use more space. The tab-element, and labels for that matter, have fixed size: https://github.com/cycfi/elements/blob/notebook/lib/src/element/gallery/tab.cpp#L47
One way is to wrap it in an align element, but then it will have infinite size and I am not sure that is what you want either. Update: Oh, maybe that's what you want ("any way to make tabs auto-expand whenever there is any extra space")?
Update: Oh, maybe that's what you want ("any way to make tabs auto-expand whenever there is any extra space")?
Yes, that's what I want. Input boxes can resize horizontally for obvious reasons, but labels have no point in it as a fixed text has always the same size, so why not add an alignment near them? I wanted to immediately test it with tab(align_center(label("test")))
but then the experiment is blocked by the factory expecting std::string
. Looks like I will need to work on the tab factory PR first.
I'm getting better at it. Now I understand completely how aligning+tiling+stretching combined interact. Expect a PR for the tab interface and a modified notebook example soon.
While implementing the PR, I have identified some bugs in proxy:
Both of these functions take Subject&& subject
argument, but:
Subject
is not deduced. It's already specified by the class template and both the constructor and subject
setter are simply using it. Perfect forwarding is only possible when the function template can apply reference collapsing during type deduction.std::decay
is a no-op. It has no purpose when Subject
is not deduced and at this point (inside the class) Subject
already must be a non-reference type.Subject&&
is not deduced, both std::forward
s are no-op.Subject&&
is not deduced, both the constructor and the setter are expecting rvalues. This hit me during some tab factory works when I supplied an lvalue to the constructor. You can trigger the error by any lvalue object: proxy<element>(empty())
works, but auto e = empty(); proxy<element>(e);
fails on forbidden lvalue-to-rvalue convertion.but labels have no point in it as a fixed text has always the same size
You really should not make strong claims like that without knowing the underlying reasons ;-)
So, are there more reasons why labels always have fixed size?
Both of these functions take
Subject&& subject
argument, but:
- There is no perfect forwarding because
Subject
is not deduced. It's already specified by the class template and both the constructor andsubject
setter are simply using it. Perfect forwarding is only possible when the function template can apply reference collapsing during type deduction.
Correct. Indeed. The ctor should probably take a template to allow convertibility, etc. That is the intent here IIRC.
- Because of how we use proxy (we always store values of non-reference type), the
std::decay
is a no-op. It has no purpose whenSubject
is not deduced and at this point (inside the class)Subject
already must be a non-reference type.
The way we use it today does not mean it will be so in the future. There might be a possibility of using references (although we already have the indirect classes), but I am not ready to stifle the interface just yet.
So, are there more reasons why labels always have fixed size?
Good. It's always good to ask first before assuming something is "wrong" :) I suggest you avoid such assumptions without knowing the underlying reasons. Don't assume anything even if it seems obvious to you initially.
Consider this use-case: Imagine this: I want an icon (text icon) at the left of some text with some exact margin. How do I do that if the icon and text have flexible sizes? Have a look at the icon+text buttons, for example:
Can you imagine now how that code will be messed up with the behavior that you propose?
Moreover... Should icons, or images have fixed sized? If not, should they stretch to fit or align to center?
Whatever your answer is, these are design and conceptual questions and there is really no right or wrong answers. It is a choice. IMO, they should be fixed and there should be consistent behavior. After all, there are always align proxies that you can use, and it is easy to add stretch-to-fit proxies if you want some elements to scale fluidly.
That is the underlying design:
I managed to workaround current broken forwarding in the tab PR (I will not attempt fixing proxy on this branch, is the job for develop), however I have encountered another bug:
lib/include/elements/element/button.hpp:90:18: error: no match for 'operator=' (operand types are 'std::array<std::shared_ptr<cycfi::elements::element>, 2>::value_type' {aka 'std::shared_ptr<cycfi::elements::element>'} and 'std::shared_ptr<cycfi::elements::tab_element<false, cycfi::elements::label_gen<cycfi::elements::basic_label_base<cycfi::elements::default_label> > > >')
90 | (*this)[0] = share(std::forward<W1>(off));
shared pointer has a converting constructor, so I was surprised why it can not upcast the pointed type to base element
type. Everything after this error are failed enable_if
s from different operator=
overloads which don't tell me much apart from the fact that std::is_assignable
said no.
So I went ahead and made a simple experiment:
tab_element<false, label_gen<basic_label_base<default_label>>>* p1 = nullptr;
auto p2 = static_cast<element*>(p1);
main.cpp:20:36: error: 'cycfi::elements::element' is an inaccessible base of 'cycfi::elements::tab_element<false, cycfi::elements::label_gen<cycfi::elements::basic_label_base<cycfi::elements::default_label> > >'
20 | auto p2 = static_cast<element*>(p1);
| ^
somewhere across the hierarchy there is a class
that uses implicit private inheritance - maybe a bug introduced in recent size refactorings (although there are no such types in this instantiation). Now it's just time for some grep
hunting.
Surprise, it was actually may fault because I edited the tab_element
incorrectly:
lib/include/elements/element/gallery/tab.hpp:17: class tab_element : proxy<Subject>
Still, I searched the entire repository just in case - not more such mistakes were found.
Ok, please build 2070c88e7ca7b26df011b1b430c695e6d096fd88 and look at its result.
It's not a PR but it should clearly express what I had in mind.
Few notable things:
std::string
. After this commit the tab
can accept any element.tab_element
is now a proxy element which only adds a background behind the proxied element.draw_tab
has been significantly simplified. It's meant to be just a background that reacts with color changes depending whether tab is enabled (state
) and/or highlighted (hilite
). hilite
does not work (background doesn't react to hovering) but it was broken before this commit (looks like some refresh
call is missing somewhere)Looks good to me! Now I see what you mean. Yes, this is in line with the design goals of Elements, and I am happy :-)
First you educated me how to implement progress bar element correctly to be generic. Now I'm improving the tab because I see a mistake like you pointed me then.
Before I make this change an actual PR, I will try to repair proxy. You can see how the tab currently uses a copy
function to workaround (unwanted) rvalue requirement.
First you educated me how to implement progress bar element correctly to be generic. Now I'm improving the tab because I see a mistake like you pointed me then.
Then you educated me to be consistent with the design philosophies :) I'm glad you get it. Sometimes I forget too :-) Thanks for your patience in reminding me :-)
Since #122 is merged, I think this branch will be ready to be merged onto develop when the copying interface is implemented is removed from the factory.
Can this be merged to develop now? The only thing it still needs is the state duplication refactor, just like some buttons.
See branch: https://github.com/cycfi/elements/tree/use-basic_button
Which I branched off "notebook". Check out the changes. This is what I had in mind with the basic_button vs. layered_button, which might become obsolete because that too can be a specialization of basic_button.
Great, I will check the diff vs notebook branch.
Yes, this is what I had in mind. Go for it on every element that creates duplicate elements in its factory.
I have 2 goals:
tab("a string")
which reacts to hover and selection using theme "hover-highlight" and "selected" colors).receiver<bool>
and sender<bool>
interfaces, I'm just looking if the library can ease creating such widgets even more.And btw, on this branch the notebook example is broken. Tabs can only be switched on once and stay "on" forever.
I'm encouraging you to merge notebook or even use-basic_button branche to develop, as my other work (my own project, bug fixes, theme editor) are depending on more and more bug fixes/elements.
And btw, on this branch the notebook example is broken. Tabs can only be switched on once and stay "on" forever.
Fixed. Simplified traversal code along the way.
I'm encouraging you to merge notebook or even use-basic_button branche to develop, as my other work (my own project, bug fixes, theme editor) are depending on more and more bug fixes/elements.
Merged into develop. Still open to refinements such as aesthetics, API, etc.
Great. There is still some small weirdness in the notebook example - the first tab text is highlighted whenever mouse leaves the window.
Great. There is still some small weirdness in the notebook example - the first tab text is highlighted whenever mouse leaves the window.
I'll look into it.
I'll look into it.
Fixed. I'll close this now.
I would like to implement tab (and some other widgets) but I need some guidance regarding library's code - everything is so composable that I have a feeling I will write duplicated code most of time time.
I have read the post and checked how button is implemented (array composite of 2 elements) but I still don't get many things:
_base
types? That is,proxy
vsproxy_base
,composite
vscomposite_base
etctypename Base
? What's the benefit of doing this over just inheriting fromelement
?Base
must beelement
or a type derived from it)?support
directory is a collection of helpers (implementation details etc) but what is the reliation ofelement/*
toelement/gallery/*
? Both directory trees containbutton
header and source - their reliation/dependency is unclear to me.