Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Constructing widgets rather than widget builders #10

Closed
JamesBremner opened this issue Jul 28, 2021 · 14 comments
Closed

Constructing widgets rather than widget builders #10

JamesBremner opened this issue Jul 28, 2021 · 14 comments
Labels
enhancement New feature or request

Comments

@JamesBremner
Copy link

JamesBremner commented Jul 28, 2021

In your sample code you construct ( build ) widgets like this

lecui::widgets::label_builder label(pane_one.get(), "my_label");

This looks like it will call the widget's destructor when it goes out of scope. Usually people want to prevent this happening until the application ends, or when the container is destroyed.

A smaller point: it seems odd to me that you are constructing an instance of a widget builder, rather than an instance of a label.

So something like

lecui::widgets::label * plabel1 = lecui::widgets::label_builder( label(pane_one.get(), "my_label");

( the builder returns a pointer to the widget )

or

lecui::widgets::label& label1 = lecui::widgets::label_builder( label(pane_one.get(), "my_label");

( the builder returns a reference to the widget )

Personally, I prefer the second style.

@alecmus
Copy link
Owner

alecmus commented Jul 28, 2021

Personally, I prefer the second style.

I like the second style. We can do it in the current design by just calling the .specs() method or the () overload as follows:

Either

auto& pane_one = pane_one_builder.get();
auto& label = lecui::widgets::label_builder(pane_one, "my_label").specs();

or just

auto& pane_one = pane_one_builder.get();
auto& label = lecui::widgets::label_builder(pane_one, "my_label")();

What do you think?

This looks like it will call the widget's destructor when it goes out of scope.

The naming in the example code could use some improvements since those are actually builders, and builders can be short-lived since their purpose is just to build the widget and once that's done they're no longer needed. The widget however lives on until either the form or the container is closed, or until the widget is explicitly closed through the widget_manager.

@JamesBremner
Copy link
Author

the example code could use some improvements

The sample code is very important because this is how potential users decide if they want to use your library.

Right now your sample code does not look super useable.

The concept of builders just to build the widget and once that's done they're no longer needed seems a complication that the application coders should not have to deal with. Most gui frameworks I have used simply construct the widget and the library looks after everything else.

I think you should find a way of hiding this complexity from the application coder.

@tmnyoni
Copy link
Contributor

tmnyoni commented Jul 28, 2021

That's an interesting suggestion.

@alecmus
Copy link
Owner

alecmus commented Jul 28, 2021

Thanks for the feedback @JamesBremner. We'll work at simplifying the interface further and updating the sample code as well once that's done.

@alecmus alecmus added the enhancement New feature or request label Jul 28, 2021
@JamesBremner
Copy link
Author

JamesBremner commented Jul 28, 2021

May I suggest the approach I used in windex: a templated factory method to construct all the widgets.

template<class W , class P >
W& factory(P & parent	)	

W is the class of widget to construct
P is the class of the parent widget ( container in your terms, I think )
parent a reference to the parent class
return reference to widget constructed

Every widget can thus be constructed with the same call ( except for the top level window which needs to be special anyway )

For example, constructing a label

auto& label1 = factory< label >( form );

An internal database associates the widgets with their parents so they can destroyed just before the parent is destroyed and can inherit changes to attributes like background color and font.

https://jamesbremner.github.io/windex/classwex_1_1maker.html

@alecmus
Copy link
Owner

alecmus commented Jul 30, 2021

Thank you for the feedback. We are currently working on reverting the interface to something similar to 1674eb2 so that the builders are not public. However, we will not be using templates as this would mean every header is included by default which we have avoided for performance reasons. We prefer that the client code only sees widgets that have been explicitly added to the file, e.g. to see progress indicators one has to explicitly call:

#include <liblec/lecui/widgets/progress_indicator.h>

The solution will address the issue you raised but using a slightly different approach because we wouldn't want client code having to be recompiled every time we add new widgets to the library.

@JamesBremner
Copy link
Author

every header is included by default which we have avoided for performance reasons.

Including a header slows the compilation by a few milliseconds. It has no effect on run time performance.

As an application programmer I find it extremely annoying to have to remember to include the correct headers. I add a feature and compilation fails. Why? Oh yes,I need to add some tiny header file. What was it called again?

After doing this ten or twenty times, I am really annoyed at whoever designed the library like this.

It is much kinder to your library users to tell them to include one header file to get all the core widgets. You can have a few extra includes, for heavy duty features that are often not used. For example to use almost all the feature of windex, include windex.h and you get them all. But if you want a property grid or a plot then you do need extra includes for those.

@alecmus
Copy link
Owner

alecmus commented Jul 30, 2021

Libraries are different because there is no one-size-fits-all. It now appears like all you are really trying to do is draw attention to your own library. I would never talk about my library on your forum.

Using words like "annoying" just because the library is different from yours really doesnt help. It's just a different architecture it doesnt have to annoy you. This sort of library and what it does could never really work in a single header file. Qt, wxWidgets, nana etc arent single header for a reason and just because they use a different architecture doesnt make them terrible. Kindly avoid talking about your own library on other forums.

@alecmus
Copy link
Owner

alecmus commented Jul 30, 2021

There is always a way of making constructive suggestions without talking other people's work down.

Kindly consider how you put your words across. Your suggestions are always welcome but it doesnt help when it comes as an attack.

@alecmus
Copy link
Owner

alecmus commented Aug 1, 2021

This issue was addressed in 156f37b and the example code was updated accordingly in relevant sections. Thank you for the suggestion.

For example, the following code:

auto& home = _page_man.add("home");

auto& label = widgets::label::add(home);
label
    .text("<em>Progress indicator</em>")
    .color_text(color().red(0).green(150).blue(0))
    .rect(rect()
        .left(10.f)
        .right(home.size().get_width() - 10.f)
        .top(10.f)
        .bottom(30.f))
    .center_h(true);

auto& indicator = widgets::progress_indicator::add(home);
indicator
    .percentage(68.f)
    .rect().snap_to(label.rect(), rect::snap_type::bottom, 20.f);

Produces this:

screenshot

The lines of interest here are the following:

auto& label = widgets::label::add(home);
auto& indicator = widgets::progress_indicator::add(home);
...

We simply reverted to the initial interface from back here 1674eb2 while retaining all the improvements and additions that have been to the library since then.

@alecmus alecmus closed this as completed Aug 1, 2021
@JamesBremner
Copy link
Author

auto& label = widgets::label::add(home);

This is nice and clean.

The library user constructs a new widget AND adds it to a pane all in one call to a static method of the widget class. There is no widget class constructor.

The gives clean application code.

It is however a very unusual way of doing things. So I think that you should provide a small example application that does this and which explains the procedure for most people who will be expecting to call a constructor.

@JamesBremner
Copy link
Author

The docs for the label class method add needs to be updated. It gives no clue that this constructs a new instance of the class and should be used rather then a constructor

image

@JamesBremner
Copy link
Author

Question: why do this in a static method when, I think, it could be done just as easily in a constructor?

The application code would be even cleaner

auto& label = widgets::label(home);

Most importantly, this is the 'normal' way of doing things so you would not have to provide any special documentation, Just say, every widget constructor needs a reference to the page that will contain it.

@alecmus
Copy link
Owner

alecmus commented Aug 1, 2021

This is nice and clean.

Thank you.

I think that you should provide a small example application that does this and which explains the procedure for most people who will be expecting to call a constructor

The wiki pages that are available so far have been updated to reflect these changes. For example, the example code here shows this in action.

The docs for the label class method add needs to be updated

The in-code XML documentation had already been updated in e3e2995 and we have just pushed the changes over to the XML documentation in 8670e9b as suggested.

Question: why do this in a static method when, I think, it could be done just as easily in a constructor?

So that the user doesn't have to worry about managing the lifetime of the object. Adding a widget to a container through a constructor would mean the user has to keep the class variable of the widget within scope. By relieving the user from having to manage the widget lifetime themselves this results in less class variables, which is neater. Furthermore, if a widget could be made as a class variable it wouldn't be possible to delete it later using widget_manager.close() as destroying it would require to somehow bring the object out of scope. The alternative would be to use pointers, which we avoided to minimize programming mistakes. As such, the library is responsible for managing the lifetime of the object. The user can get a reference to the object at any time, anywhere within the class by calling the .get() method (or using the helper macros provided for more terse code, e.g. get_label(), and if the widget has been destroyed (either explicitly or because its container was closed), then the .get() method throws an exception, as documented in the respective .get() methods.

Kindly note that this code:

auto& label = widgets::label(home);

Would not be possible since constructors cannot return values. This is another reason why a static method was used.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

3 participants