-
-
Notifications
You must be signed in to change notification settings - Fork 671
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
Column renderers #1478
base: main
Are you sure you want to change the base?
Column renderers #1478
Conversation
157040d
to
74a7e66
Compare
|
||
|
||
class Column(Widget): | ||
def create(self): |
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.
Note that this is still a no-op. I'll need to become more familiar with android APIs to do anything useful here
62566a7
to
3542f62
Compare
Converted to draft since handling of "missing values" is not yet clear. The current Column API can easily incorporate a placeholder for missing text but it the behaviour for missing icon or checkbox data needs to be better defined. |
e5af018
to
d3a2c42
Compare
I've added hopefully reasonable fallbacks now when no data is available:
|
* Don't test removed accessor attribute * Allow duplicate accessors
d3a2c42
to
45ebace
Compare
@samschott FYI - I've seen this, and I promise I'll take a look soon; I'm currently knee deep in M1, Android, and Py3.11 updates; once I surface from those, this is high on my list. |
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've finally cleared the decks of underlying platform support work, so I've had a chance to dig into this PR. My overall impression is that it's heading in the right direction, with some notable gaps and tweaks required (some of which you've flagged already). I've flagged a couple more inline; but some overall comments:
- We definitely need something in the examples that shows off the checkbox and icon functionality.
- I can see what you've done exposing the Column as a widget on the impl layer so it can be exposed by the factory. However, that means there's a loss of parity between the interface and impl layers. I wonder if it might make more sense to expose an interface on the Table/Tree widget impl that acts as a factory for Column Impls?
- You've flagged the need to provide an API to configure the list of columns at time of construction. I can see how that could enable us to deprecate
headings
andaccessors
; my hesitation is the extent to which that complicates the "simple" case of "show a table based on this list of lists". - The only thing that feels slightly off in the overall design the interface between the Source and the widget. This manifests in 2 ways. The first is the
get/set_data_for_node()
API. While this definitely works, the use of a getter/setter doesn't feel very Pythonic; the obvious alternative would be an object following the descriptor protocol - but then we're getting very close to the API that Source provides. That leads into the second manifestation - the fact that when Source is constructed, it needs to have accessors... that are taken from the Column. I'm not sure I have a concrete counterproposal at this point - but it definitely feels like there's some friction in the way we're handling data access that could be avoided with a slightly different API design.
send_super(__class__, self, "init") | ||
self.imageView = NSImageView.alloc().init() | ||
self.checkbox = NSButton.alloc().init() | ||
self.textField = NSTextField.alloc().init() |
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.
How significant is the overhead of defining the image and checkbox widgets for every cell, and adding them to the layout, even if they're not used?
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.
That's a good question. In my experience, the overhead has been small since in practice only the number of TogaTableCellView instances that are actually visible on screen will be created (maybe a few more than that) and then reused when scrolling.
We might gain a bit by lazily allocating images and checkboxes as needed while slightly complicating the logic.
@@ -75,7 +75,7 @@ def can_have_children(self): | |||
# Property that returns the first column value as (icon, label) | |||
@property | |||
def name(self): | |||
return self._icon, self.path.name |
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.
Comment is now out of date; plus, the demo has now lost the icons.
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.
Indeed. It will regain the icons once the API for that is set.
self.text = text | ||
self.icon = icon | ||
self.checked_state = checked_state | ||
self.editable = editable |
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.
+1 to making these attributes customisable... but are we actually using this capability anywhere? AFAICT, the implementation currently hard-codes all the references to row/node attributes.
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.
We are not, really. New data will be fetched from the source when refreshing the table or when new rows scroll into view. At this point, changes to these accessors will be visible in the widget. We'd probably want setters here which force a reload.
Args: | ||
node (``Node`` or ``Row``): A Node in a TreeSource or a Row in a ListSource | ||
that holds the data for this row. | ||
role (str): The type of data to query. Can be "text", "icon" or "checked_state". |
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.
We need to differentiate between the specific role, and the attribute on the data source that may be providing the data for that role. We should introduce constants or an enum for these defined roles.
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.
Good point.
from toga.widgets.base import Widget | ||
|
||
|
||
class Column(Widget): |
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.
There's an inconsistency between the file name and class name here
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.
src/cocoa/toga_cocoa/widgets/tree.py
Outdated
|
||
return max(heights) | ||
# @objc_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.
We should either preserving the comment around why this is commented out... remove the block entirely
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 is indeed no longer needed since this PR removes support for showing arbitrary widgets in a column.
Thanks for having a look at this! Regarding the constructor API, I'm still searching for a solution that keeps the simplicity while enabling customisation. All while maintaining backward compatibility. Once I have that, I'll update the examples to make use of it. Regarding the Columns in the Impl layer, I like the idea of exposing an interface on the Table/Tree widget to act as a factory. Now to the lager last point. The main goals of what a Column is supposed to do:
In this sense, the Column tries to do something similar to the source: It provides access to data. However, while the data sources are essentially a "list" or a "tree" of data classes with a notification mechanism, the Column does not provide a data structure at all but only a mapping from fields of a data class to "something to display" (including styles, at some point...). With that in mind, I'm not so concerned about the manifestations which you describe:
I agree that there are more elegant and Pythonic ways of solving this. Let me see what I can do here.
The data source already required accessors in the constructor previously. This is just a necessity when "creating" attributes of a Row / Node dynamically. However, why do you say that the accessors are taken from the Column? The opposite is happening: accessors are defined for data source. The Column is then told which accessor it should use to fetch the data from the source for each type of widget that it supports. In the current implementation, this admittedly takes a bit of a roundabout way: Because the constructor only supports |
FWIW: Backwards compatibility is highly desirable, but negotiable at this point. I've been holding off formally cutting v0.3 specifically so we have the flexibility to break compatibility if we need to. A clean forward path is always preferable, but if a good API design exists that isn't backwards compatible, I wouldn't rule it out.
Agreed.
I think we're on the same page here - but to restate to make sure:
Yeah - it's the "roundabout way" part that wasn't sitting right with me. The creation of the column itself (final API notwithstanding) makes sense - Thinking about it some more, I suspect the root of the problem may actually lie in Source, and the way There are essentially 4 use cases that Table/Tree needs to support for passing in data:
Of those, it's only case (1) that needs to know about the columns - and that's only so the source can convert form (1) into something closer to form (2) so that the column can use attribute lookup. It might make sense to do that conversion on the Table/Tree side, rather than pushing it down to the Source. |
Ok, in that case I think we are agreed :) Regarding the types of data that you want to accept as input, the third case of a list of objects does not currently seem to be supported, though I agree that it should be. Passing arbitrary objects to a ListSource currently assigns the entire object to the first accessor. However, it would probably be more useful if the objects would replace the Rows in a List source. In case of a TreeSource, it's a bit more difficult because we need to support the concept of child Nodes, which objects that are passed in might not have. Edit: Maybe replacing Rows with custom objects is not a good idea after all, the current mechanism of notifying the table of changes to the data is baked in to the predefined |
We may not be able to entirely replace Row with custom objects - but it should be possible for Row to use a custom object as its data object (instead of copying the data into a dictionary). |
Fair point. I'd rather defer this to another PR though, it's a bit off-topic for this one and has some subtleties involved. For example: Do we want to proxy all attribute access for those objects? How do we handle type checking when creating the ListSource? In principle, even an integer is an object with attributes such as |
Codecov Report
|
This PR is an initial set of changes to introduce a new
Column
widget which represents a Table or Tree column. Having aColumn
widget has multiple advantages:This PR lays the groundwork by introducing the new Column widget and using it in internally only. The public APIs of
toga.Table
andtoga.Tree
remain unchanged for now, apart from exposing the created columns in a new property. This means that current benefits mostly lie in cleaner implementations of some functionality.Support in different backends for the new column features varies:
Next steps will be:
Columns
instead ofheaders
andaccessors
when instantiating a new Table or Tree. Possibly move towards deprecatingaccessors
andheaders
arguments?This PR supersedes #1085.
PR Checklist: