`Popover` - A consistent way of enabling a Subview to popup outside of a View
(Very drafty for now... just some thoughts).
This Issue is a proposal for how to build into TG v2 a consistent way for a View to have a UI element show itself outside of the View's Viewport.
Popover - A View that that is displayed outside of the View that owns it's Viewport.
There are at least these use-cases of Popovers:
Autocomplete Popup
TextView would like a list of autocomplete items to be displayed below the view, at the cursor position as the user types. The user can use mouse/keyboard to select an autocomplete item and the pop over disappears.
Current Implementation
- Adds
_popup(aListView) tothis.SuperView - Uses
Visibleto show/hide - (In #3627 I've had to change the implementation so
_popuponly gets removed/disposed when the host is removed/disposed).
Drop-down Combobox
Current Implementation
- Does not support pop-over; The
ComboBoxneeds to be sized to allow the drop-down to show if HideDropdownListOnClick is true. _listview(of typeComboListView) is a subview ofComboboxComboListViewoverridesOnMouseEvent(which isinternal!) with a bunch of hackery to deal with when it should be displayed
Menus (from MenuBar)
Current Implementation
MenuBaris a "special" subview ofApplication.Top(Toplevel has knowledge of it).- When
MenuBaris activated (via keyboard/mouse/api) it creates instances ofMenuand adds them toTop - Both
MenuBarandMenuhave convoluted logic for sensing when the menus should close (via mouse & keyboard). - When an open
Menucloses, it is removed fromTop
Context Menus
Current Implementation
- Uses a "fake"
MenuBarthat is created whenShowis called. _menuBaris never actually added to any views;BeginInit/EndInitare called manually.
Tooltip
As a user, when I hover the mouse over a View, I'd like a temporary popup to appear providing a "tip" regarding the View's purpose.
Current Implementation
- Not currently implemented
Proposal
Tenets (Unless you know better ones...)
- There can only be one - Just like in Highlander, there can only be one Popover visible and active to the user at a time. None of the use-cases above lead to needing to have more than one, thus this is a simplifier. [^1]
- Its Rude to Mess With Someone Elses' Subviews - Asking View developers to have to code defensively around the fact that some Subview they add, or some other agent, is adding/removing subviews is just bad juju. It leads to bugs in object lifecycles and requires skill to get right (e.g. adding a Subview during draw can result in
Subviewsbeing changed during aforeachiteration). In this design we will ensure there's no need to mess with another View'sSubviews. - Any View can be a Popover - The design should not require a View that "Pops Over" be coded specially for that purpose.
Design
At the Application level we'll add a peer of RunState.Toplevel named RunState.Popover:
- In the run loop, after
RunState.Top(remember,Currentgoes away with #2491)Top.Draw()is called, ifPopover.Visiblewe'll callPopover.Draw(). This will ensure the Popover is always drawn over everything else. - In keyboard handling, if
Popover.Visible && Popover.HasFocuswe'll givePopovera chance to handle key events. - In mouse handling, if
Popover.VIsibleandPopover == Applicaiton.MouseGrabViewwe'll let it have mouse events. If a click happens outside ofPopover.Framewe setPopover.Visible = false. - In focus/navigation handling, if
Popover.HasFocus, if any other View gets focus, we'll setPopover.Visible = false. - For layout, whenever
Top.LayoutSubviewsis called, we'll then callPopover.SetRelativeLayout - Two APIs:
Applicaton.ShowPopover (View popoverView)
public static bool ShowPopover (View popoverView)
{
if (RunState.Popover is {})
{
RunState.Popover.Visible = false;
}
if (!popoverView.IsInitialized)
{
popoverView.BeginInit();
popoverView.EndInit();
}
RunState.Popover = popoverView;
RunState.Popover.Visible = true;
RunState.Popover.SetRelativeLayout(screen);
}
Application.HidePopover ()
public static void HidePopover ()
{
if (RunState.Popover is {})
{
RunState.Popover.Visible = false;
}
}
View will have know knowledge of the popover concept.
Any View subclass that wants a Popover, will:
_myPopover = new MyPopoverView();- Note,
_myPopoverwill always havethis.SuperView is null. - Any time the popover should be displayed:
- Set
_myPopover.X/Y/Width/Heightto ??? (NEED TO FIGURE THIS OUT) - Call
Application.ShowPopover(_myPopover)
- Set
- If the view wants to explicitly hide the popover,
- Call
Application.HidePopover()[^1]: I've considered "As a user, when press a special key, I'd like all visible Views to show temporary popup providing a "tip" regarding the View's purpose." This could be implemented by having the container view showing a Popover who's job it was to show "tips" for each subview.
- Call
- There can only be one - Just like in Highlander, there can only be one Popover visible and active to the user at a time. None of the use-cases above lead to needing to have more than one, thus this is a simplifier. 1
If I remember, only occur to me that ComboBox has an option to let the dropdown list always visible. How you'll deal about that?
I love the Tooltip implementation and all the others stuff.
- There can only be one - Just like in Highlander, there can only be one Popover visible and active to the user at a time. None of the use-cases above lead to needing to have more than one, thus this is a simplifier. 1
If I remember, only occur to me that
ComboBoxhas an option to let the dropdown list always visible. How you'll deal about that?
If the list is always available it is not a "Dropdown Combobox", but just a ComboBox. The current implementation doesn't really provide a "Dropdown Combobox".
Q: What's the difference between a Modal and a Popover?
- The showing of a
Modalis blocking (synchronous) to the caller (viaRun). The showing ofPopoveris not blocking.
Q: Can't I use a Modal for something like the Autocomplete popup?
- No. If you did, then while it was shown, the user couldn't continue to type in the
TextField
Q: But, can Modal be implemented using the Popover support described above?
- Maybe. Worth thinking through.
- In focus/navigation handling, if
Popover.HasFocus, if any other View gets focus, we'll setPopover.Visible = false.
This should say "if any other View than the Popover's owner gets focus".
Otherwise, how would the TextViewAutoComplete popover work?
If the list is always available it is not a "Dropdown Combobox", but just a ComboBox. The current implementation doesn't really provide a "Dropdown Combobox".
I have a suggestion for this. If it's a "Dropdown Combobox" then Popover will be used. With a fixed ComboBox that always has the ListView visible, then ComboBox must have the available dimension to acommodate the TextField and the ListView.
If the list is always available it is not a "Dropdown Combobox", but just a ComboBox. The current implementation doesn't really provide a "Dropdown Combobox".
I have a suggestion for this. If it's a "Dropdown Combobox" then
Popoverwill be used. With a fixedComboBoxthat always has theListViewvisible, thenComboBoxmust have the available dimension to acommodate theTextFieldand theListView.
Yes!