Allow to access tools via a context menu - #14228
Conversation
7197eb6 to
a5c8270
Compare
|
It is possible to configure the p.context_menu = Menu(
items=[
MenuItem(label="Do something", action=CustomJS(code="alert('did something')")),
None,
MenuItem(label="Tools", menu=ToolMenu(toolbar=p.toolbar)),
],
) |
a5c8270 to
0ded33b
Compare
|
|
76cd99a to
b31e214
Compare
| of an item. Unchecked item is represented with an empty space. | ||
|
|
||
| The menu will allocate a column for check marks for all its items if | ||
| at least one item has set a boolean value for ``checked`` property. |
There was a problem hiding this comment.
This seems a little awkward. If I want a list of checkable, but all initially unchecked items, I need to either:
- pick one at random to set
checked=False(arbitrary, inconsistent) - explicitly set all
checked=False(a bit tedious, False is a reasonable default to assume for checked)
?
There was a problem hiding this comment.
Alternatives are:
- Leave
CheckableItemundeprecated and overridecheckeddefault value withFalse. - Add
Menu.checkable: bool | "auto", where"auto"is the default current behavior.Falseseems not particlarly useful though.
There was a problem hiding this comment.
I'm not sure I like any of these. It just seems to me that "checkability" is an explicit up-front decision, so there's no need for all the magic on the base class (i.e. ActionItem should just not have checked at all). But if you don't like that route for some reason I suppose the way things is preferable over the two options above.
There was a problem hiding this comment.
Given that non of these properties are mutually exclusive, I figured that only having MenuItem will be preferable, instead of more complex complex model hierarchy, and easier for the user if they change their mind (no need to change the model and imports). Other frameworks that provide context menus (e.g. Qt, WinForms) do the same.
There was a problem hiding this comment.
I also don't like this when I put myself in the shoes of someone implementing a menu with this library. In my view, having a nullable boolean variable named checked creates cognitive overhead. From what I could tell, the other frameworks you cited, Qt and Windows Forms, do not try to stuff the semantics of "is checkable" (in your code, maps to null / not null) and "is checked" (true / false) into a single variable. Same goes for HTML forms: <input type="checkbox" checked />.
I guess I don't understand why Bryan doesn't like option 2, Menu.checkable, nor do I understand why it should have a type other than a simple boolean whose default value is false. That way, at the end of the day, you have two separate booleans to express three possible states (i.e.: not checkable; checkable and checked; checkable and unchecked) rather than one nullable boolean to express those same three states.
There was a problem hiding this comment.
Continuation:
48d7edd to
dfdafcc
Compare
620edb9 to
c257e2d
Compare
c257e2d to
db5254b
Compare
There was a problem hiding this comment.
I really like the direction this code is taking. I am quite a bit late to the party but I spent a lot of time reading and poking at this code, so I thought I should share my review even though the pull request has already been merged. A lot of my remarks are just questions to help me better understand the codebase.
Most of my comments are inline, but I have two UX remarks that I will leave here:
- I don't think that native context menus take an action or close when you click on a menu item that has a sub-menu. That means the current behavior in Bokeh may be an anti-pattern. I don't think this PR introduced that behavior. But I thought it was still worth noting.
- I also noticed that for sub-menus of certain tools when you select one of the sub-tools, the icon for that tool is not updated in the context menu, which I think is a bit confusing. For example, the pan tool has three states: X, Y, and both X and Y. If you choose the Y state, the icon in the context menu does not update to reflect that it is now in the Y state. Again, I don't think this is something that this pull request changed, but putting it down so it's recorded somewhere.
|
|
||
| const {view} = await display(p) | ||
|
|
||
| // can't simply dispatchEvent() because if browser security |
There was a problem hiding this comment.
| // can't simply dispatchEvent() because if browser security | |
| // can't simply dispatchEvent() because of browser security |
| @@ -0,0 +1,49 @@ | |||
| """ Example showing how to configure the context menu of a plot, including | |||
There was a problem hiding this comment.
I don't think so. You may have a tooltip for an element of a menu. In this particular case, the top level component should be the one showed last.
| target: HTMLElement | ||
| orientation?: Orientation | ||
| reversed?: boolean | ||
| labels?: boolean |
There was a problem hiding this comment.
The variable name labels makes me think it is an array of strings, not a boolean.
There was a problem hiding this comment.
Sure, show_labels would have been better, but this will be removed soon-ish anyway.
| }), | ||
| new MenuItem({ | ||
| icon: `.${icons.tool_icon_auto_box_zoom}`, | ||
| label: "Auto mode", |
There was a problem hiding this comment.
Out of curiosity, how does auto mode work? How exactly does it depend on the mouse gesture? For me it seems to behave exactly like XY mode, and neither of the following docs pages clarifies it for me:
There was a problem hiding this comment.
Update: I figured it out. It behaves like X-only mode if the user drags the mouse along a horizontal straight line in the x direction, like Y-only mode if the user drags only in the y direction (i.e., along a vertical line), and like XY mode if the user drags the mouse in the both the x and y directions (neither a strictly horizontal nor strictly vertical line). Making a note to see if this is clearly conveyed in the docs somewhere.
There was a problem hiding this comment.
It may not be in the case of this particular tool, but that kind of behavior should be documented for other tools that implement it as well. Still it should be documented regardless.
| const menus = this.model.items | ||
| .map((item) => item instanceof ActionItem ? item.menu : null) | ||
| const menus = this.menu_items | ||
| .map((item) => item instanceof MenuItem ? item.menu : null) |
There was a problem hiding this comment.
The way this was written is peculiar... wouldn't you agree that it's a bit more natural to filter first then map?
const menus = this.menu_items
.filter((item) => item instanceof MenuItem)
.map((item) => item.menu)There was a problem hiding this comment.
Peculiar indeed. Maybe a side effect of some earlier code.
There was a problem hiding this comment.
Actually, you need another filter because item.menu may be null. Also, I'm still not used to TypeScript properly narrowing the return type in filter() with instanceof checks (fairly recent addition to the type system).
| of an item. Unchecked item is represented with an empty space. | ||
|
|
||
| The menu will allocate a column for check marks for all its items if | ||
| at least one item has set a boolean value for ``checked`` property. |
There was a problem hiding this comment.
I also don't like this when I put myself in the shoes of someone implementing a menu with this library. In my view, having a nullable boolean variable named checked creates cognitive overhead. From what I could tell, the other frameworks you cited, Qt and Windows Forms, do not try to stuff the semantics of "is checkable" (in your code, maps to null / not null) and "is checked" (true / false) into a single variable. Same goes for HTML forms: <input type="checkbox" checked />.
I guess I don't understand why Bryan doesn't like option 2, Menu.checkable, nor do I understand why it should have a type other than a simple boolean whose default value is false. That way, at the end of the day, you have two separate booleans to express three possible states (i.e.: not checkable; checkable and checked; checkable and unchecked) rather than one nullable boolean to express those same three states.
There was a problem hiding this comment.
I understand most of the changes you made to this test file, but I actually don't understand the test file itself. First of all, the name is confusing to me. If I understand the test code correctly, it would make more sense if the file were named annotation_context_menu.ts. Secondly, it looks like there's all of this code that creates a context menu, but then doesn't actually take the necessary steps to open that context menu before taking a snapshot, so essentially it's just snapshotting a kind of scatter plot, doesn't seem to have anything to do with annotations or context menus, since neither of those are depicted in the snapshot:
There was a problem hiding this comment.
This is an example more than it is a test, so although image is being captured, in this and some other cases it has secondary importance if any. These examples are a part of integration tests for convenience (at least for now). I need to think about this, because we could set them up like tests, or maybe introduce a modal setup and allow both (under some command like/URL argument option).
|
@gabalafou, can you start a discussion for checkable item behavior and API? We can still refine this before 3.7. |
* Remove unnecessary leftover code * Fix spelling * Use less convoluted logic * Add unit tests
|
This pull request has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs. |


This PR adds support for automatically generated menu that mimics plot's toolbar. This way tools are accessible even when
Toolbarisn't visible.fixes #14225