-
Notifications
You must be signed in to change notification settings - Fork 599
New issue
Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? # to your account
[RFC] Implement wibox.widget.template: An abstract widget that handles a preset of concrete widget. #3421
base: master
Are you sure you want to change the base?
Conversation
Codecov Report
@@ Coverage Diff @@
## master #3421 +/- ##
==========================================
+ Coverage 90.92% 90.96% +0.04%
==========================================
Files 897 906 +9
Lines 56677 57149 +472
==========================================
+ Hits 51531 51984 +453
- Misses 5146 5165 +19
Flags with carried forward coverage won't be shown. Click here to find out more.
|
Sorry for the delay before working again on this PR. Thank you, @sclu1034, for your review 😃 I'm slowly getting back to this PR, I'll try to provide more updates in the coming days. |
a39d528
to
f9d38c9
Compare
lib/wibox/widget/template.lua
Outdated
-- The `:update()` can be called later to update the widget template. Arguments | ||
-- can be provided to the `:update()` method, it will be forwarded to the | ||
-- `update_callback` function. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this should be moved to a separate section, e.g. "how to create a widget/an API that utilizes widget templates".
The common usage that most users will run into will only be about things like "what to write in awful.taglist { widget_template = ... }
?". For those cases, the user will likely just get confused about where they are supposed to call :update()
and with what arguments.
In the examples, the same distinction should be made clear, with comments like
-- this is what the user/consumer will write in their `rc.lua`
-- this is what a widget creator will use in their widget's code, e.g. `lib/awful/taglist.lua`
As it is right now, users will likely assume that they are supposed to call :update()
in their rc.lua
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree this can be confusing since with this widget, you have 2 use case : as the widget author and as the end-user. I'll try to improve this documentation part.
Thank you for your input! 😄
(As a quick note : the taglist/tasklist widget are not targets for this widget template implementation. While it is possible to use it as a base for these list widgets, I think we should have a separate list_template widget to handle all the layouting and iterate-over-a-list stuff that these two need.)
tests/examples/wibox/widget/template/concrete_implementation.lua
Outdated
Show resolved
Hide resolved
a9d05ff
to
1550046
Compare
I had some issue to find the time I needed to finish working on this... I think it's now ready! 😄 All check are green, and I'm not sure how(/whether it's necessary?) to test the remaining lines highlighted by codecov. Thank you, @sclu1034, for your previous reviews. |
(just a note to say I am in vacations, I will review this when I get back. I really appreciate this work) |
e827298
to
4d1b824
Compare
Rebased. Apology for forgetting about this for a year. I also added a commit to bring it to feature parity with the One thing I would be inclined to do is move the file from If you are ok with this change, then I have a few more commits to port every |
lib/wibox/widget/template.lua
Outdated
if recursive and widget.get_children then | ||
apply_property(widget:get_children()) | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I never liked this approach of just blindly assigning a property to the whole tree. And I don't see any merit in it over explicitly setting role id
s where needed.
It's inefficient compared to the by_id
caches, especially without any checks on how big the tree is that you're recursing into.
The fact that some properties will "magically" be available on all widgets while others need an id
will lead to confusion (and already does with things like icon_role
vs awful.widget.clienticon
in the tasklist examples).
With this being a public API assigning arbitrary properties to arbitrary widgets, chances rise that not every :set_foo()
expects the same foo
, i.e. not every widget (especially when taking 3rd-party libraries into account) might expect the same value for the same property name.
E.g. if there was a widget template that used a fg
or bg
property to set a color based on a value, then you wouldn't want to apply that to an entire widget tree where there might be wibox.container.backgrounds
being overwritten.
That wasn't so big of a deal until now, as the only internal implementations of this were tag
for the taglist and client
for the tasklist, where you could reasonably assume that any widget that has a :set_client()
actually wants an awful.client
instance. But now, there is no way to reason about what a template and its usage may be.
It's also weird that when ids
is set, it only recurses into their children. I can't think of a use case where I'd set a role on a widget and then also need that role on all of its child widgets. Given our separation of layout/container/content widgets, widgets that have child widgets don't have content to display of their own.
So while it's just an optional parameter, giving the user a choice, I think it's a choice they should never take. And while we could just reword the method docs to heavily discourage using the option, simply removing it would prevent the confusion and make the API simpler.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for the amazing work @Elv13!
One thing I would be inclined to do is move the file from lib/wibox/widget/template.lua to lib/wibox/template.lua and make it an "official top level concept" rather than a container/widget.
Agreed.
I would also remove the args and document is as foo.widget_template = wibox.template { ..., widget = wibox.layout.fixed }. This would make it a lot less confusing than the current "raw table" we document. It also allows us to print an error is someone wants to pass a widget instance to a template parameter.
This is something I tried to do, but couldn't find a proper way to implement. With the new template.make_from_value
function, I think it's ok.
If you are ok with this change, then I have a few more commits to port every widget_template to (appear to behave like) wibox.template. This should be backward compatible.
I can't wait!
lib/wibox/widget/template.lua
Outdated
return wbase.fit_widget(self, context, w, width, height) | ||
end | ||
|
||
function template:draw() end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ldoc @hidden this method?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's not documented in the first place, so it won't show up either way.
But as it's empty, there is no reason to keep the method itself around.
lib/wibox/widget/template.lua
Outdated
ret:set_update_callback(args.update_callback) | ||
ret:set_update_now(args.update_now) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does the update_callback
still make sense with the new :set_property
and :bind_property
methods?
It seems like these 2 new methods are a more powerful and easier way to achieve what the user would have manually written in the update_callback
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As far as I'm aware, those two things are not interchangeable.
:set_property
is for the developer who creates a widget that uses the template, to assign values at named roles in an unknown template.
update_callback
is for the user who creates the template, to do stuff when values change.
E.g. awful.widget.tasklist
will call :set_property
internally, the user that creates a tasklist will assign an update_callback
to change stuff in the template dynamically.
@@ -0,0 +1,149 @@ | |||
--DOC_GEN_IMAGE --DOC_NO_USAGE |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This example is quite interesting.
It's actually a basic implementation of the taglist/tasklist widgets with the new template. We only need a way to automatically attach a self.widget_template:clone()
to each item on the tag/task list and assign them predefined :set_property
and :bind_property
to make it a perfect replacement.
The previous implementation for these methods was a naive attempt at making the base widget working. We need to make sure computing the widget's sizes is more robust for complex widget_template definition.
The draw method exists only for widgets. When dealing with more complex widget, this method is not necessary implemented (i.e. with layouts). We need to check the method is defined by the child before calling it.
This is a small cleanup to not keep in memory a value we don't need. After the update_callback callback is called, we don't need to remember it happens this callback was queued before. (It also prevents a possible memory leak (of a boolean value) when widgets are destroyed)
Containers and layouts don't allow to build a widget from a callback, so there is no reason to have this feature here.
It also found a bug in an existing test.
…entation. `awful.widget.common` has the ability to set "roles" and apply properties to the entire widget tree. This was missing from the previous commit.
…rectly. It didn't do much and I wanted to make the syntax a drop-in replacement of `wibox.widget {}`.
4d1b824
to
f64c3e2
Compare
Wouldn't it make sense to merge #3521 into this one since other widgets like taglist, tasklist etc got rewritten here too? |
As a reviewer, I'd rather not have that. There is no rush to migrate these things, and the ones Elv already did blew up the PR tremendously. |
lib/wibox/widget/template.lua
Outdated
function template:set_update_now(update_now) | ||
if update_now then | ||
self:update() | ||
end | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The name doesn't make sense. set_[something]
implies a setter for a property, but this just triggers a function call.
And the update_now
argument exists only to call self:update()
in the constructor, so the entire method could just be replaced with an if
in the constructor:
- ret:set_update_now(args.update_now)
+ if args.update_now then
+ ret:update()
+ end
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This method is required to make the declarative pattern work as expected.
When creating a widget with the declarative pattern, the constructor doesn't receive all the properties as parameters. Instead, the properties are updated after the widget instantiation.
local w = wibox.widget {
update_callback = update_fct,
update_now = true,
widget = widget.template,
}
The update_now = true
property is "transformed" to the invocation of the template:set_update_now
method.
Note that this is why the whole method is documented as a "Hack" and is @hidden
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Then it could make sense to just call it update_now()
, which drill
also checks for.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
From a clean code PoV, maybe, but at the same time adding too many if
s to drill()
might not be super scalable. Another of my PR has some code to make meta-extending the DOM easier using some _
prefixed extender "I am special, call this and I will do my special stuff" functions. That might be the solution on the longer term.
However for now I think the hack proposed here is good enough. Unexpected, but not in a way which impact end users.
In the even longer term, which is dubious, there is also that issue which calls to replace how the DOM is created and move it to the hierarchy because in practice right now, you can't merge 2 DOMs and that makes :get_children_by_id()
unreliable when some composed widgets are used... Including this very widget.... This would be an opportunity to revisit some of the bolt-on hacks I made to restore the declarative widget system. Historically, AwesomeWM 3.1-3.4 had it, but 3.5 had a fully rewritten (and much, much better) widget system. It was fully imperative, which annoyed a large number of users who could not port their config. The code was also very heavy for something like rc.lua
, which is supposed to appear like a config file. Back then I mostly did modules rather than contribute to the core, so I wrote the declarative thing as the retrograde module. It eventually got reworked/merged into the core, but was always a bolted on hack rather than something engineered alongside wibox.hierarchy
. So it would benefit from a larger rework to be more flexible/integrated.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
From a clean code PoV, maybe, but at the same time adding too many ifs to drill() might not be super scalable.
I'm not sure to follow what you mean. This if
branch already exists.
I have to agree with @sclu1034. A setter is unneeded here. I have somehow missed this feature of drill
, otherwise I would have used it instead.
lib/wibox/widget/template.lua
Outdated
return wbase.fit_widget(self, context, w, width, height) | ||
end | ||
|
||
function template:draw() end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's not documented in the first place, so it won't show up either way.
But as it's empty, there is no reason to keep the method itself around.
lib/wibox/widget/template.lua
Outdated
gtable.crush(ret, args) | ||
ret:set_template(tmpl) | ||
ret:set_update_callback(tmpl.update_callback) | ||
ret:set_update_now(tmpl.update_now) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
At this point, I think a separate create_callback
would make more sense than using this boolean to treat update_callback
as if it was a create_callback
.
And this would be required for the "drop-in replacement".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For the drop-in part, I was just planning at leaving the old code in place. The issue is the function signature. It has the tag/client as part of it, so it's hard to mutualize into a common API. There are ways around it, but it seems simpler to omit create_callback
given it's not very useful. Something like gears.connection
would be a better fit. I may extract it from the gears.reactive
PR (even if they share some code) since it isn't as affected by the linting issues as the other 2 parts.
The whole callback thing is quite unique in our API when you think about it. We nearly always use signals for this kind of stuff. As you pointed elsewhere, it does make sense to keep it here because it makes the users life easier (I agree). gears.connection
would overlap, but would still have some learning curve issue and has some footguns (like connecting to signals which don't exist).
lib/wibox/template.lua
Outdated
if rawget(widget, "set_"..property) then | ||
widget["set_"..property](widget, value) | ||
else | ||
widget[property] = value |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For the reviewers' sakes, it would be nice to keep commits separated by purpose and to not mix small, unrelated and easy-to-miss changes into a file rename.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, sorry, I messed up a rebase somewhere.
My idea with this Draft PR was to share a little playground where we can preview how the new template API will be used in other widgets. I agree with @sclu1034 that this PR is already very big. Include all the
Yeah, 1k new lines is a big diff. (I haven't reviewed the new commits.) [If] the migration included in this PR only includes migrating the old |
lib/wibox/widget/template.lua
Outdated
function template:set_update_now(update_now) | ||
if update_now then | ||
self:update() | ||
end | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This method is required to make the declarative pattern work as expected.
When creating a widget with the declarative pattern, the constructor doesn't receive all the properties as parameters. Instead, the properties are updated after the widget instantiation.
local w = wibox.widget {
update_callback = update_fct,
update_now = true,
widget = widget.template,
}
The update_now = true
property is "transformed" to the invocation of the template:set_update_now
method.
Note that this is why the whole method is documented as a "Hack" and is @hidden
.
It was previously `wibox.widget.template`, but was in fact a container. Now it is a top level concept. The next few commits will integrate it deeper into AwesomeWM.
The `gears.share.solid_rectangle_shadow` PR was written before the warning was added.
This doesn't actually use `set_property`, but it at least covert the old table to `wibox.template` object. Future cleanup can take care of the code duplication.
Thanks for the reviews!
e1da1d2
to
c8016f0
Compare
Mostly all done. Plus I added a new commit to fix the |
Hello 👋
I think it'll need more work to be ready, I hope to get feedbacks to improve this new widget.
This PR is (what I think to be) the response we need to provide a standard API that address issues raised by initiatives like #2955 and #3208.
The idea behind this new "template widget" is to standardize how we want/try to implement widget template across the
awful.widget
library (and third-party libraries that want to implement user customizable widget). Thanks to this widget, I hope we can build more extendable widgets that allow end-users to easily implement their own customization.In the scope of this PR, I only want to implement the
wibox.widget.template
widget and the tools it needs. If you validate this approach, more PRs will come to implement usage of the template widget across widgets fromawful.widget
.For the record, here is a naive attempt at implementing the widget template for the layoutbox widget : https://github.com/Aire-One/awesome/tree/feature/widget_template/layoutboxSee the next PR #3521Todos : (EDIT: Done 🎉)
RenameChange in the constructor API makes this point irrelevantwidget_template
parameter (?)Intermediate/proxy constructor (to preventUsage is simplified to an argument tablew = layoutbox { widget_template = wibox.widget.template{...} }
)Save arguments from dismissed update call (?)Fixed thanks to @sclu1034 suggestions