Skip to content

Popover - A consistent way of enabling a Subview to popup outside of a View #3691

@tig

Description

@tig

(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

Drop-down Combobox

Current Implementation

  • Does not support pop-over; The ComboBox needs to be sized to allow the drop-down to show if HideDropdownListOnClick is true.
  • _listview (of type ComboListView) is a subview of Combobox
  • ComboListView overrides OnMouseEvent (which is internal!) with a bunch of hackery to deal with when it should be displayed

Menus (from MenuBar)

Current Implementation

  • MenuBar is a "special" subview of Application.Top (Toplevel has knowledge of it).
  • When MenuBar is activated (via keyboard/mouse/api) it creates instances of Menu and adds them to Top
  • Both MenuBar and Menu have convoluted logic for sensing when the menus should close (via mouse & keyboard).
  • When an open Menu closes, it is removed from Top

Context Menus

Current Implementation

  • Uses a "fake" MenuBar that is created when Show is called.
  • _menuBar is never actually added to any views; BeginInit/EndInit are 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 Subviews being changed during a foreach iteration). In this design we will ensure there's no need to mess with another View's Subviews.
  • 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 Application.Top named Application.Popover:

  • In the run loop, after Top.Draw() is called, if Popover.Visible we'll call Popover.Draw(). This will ensure the Popover is always drawn over everything else.
  • In keyboard handling, if Popover.Visible && Popover.HasFocus we'll give Popover a chance to handle key events.
  • In mouse handling, if Popover.VIsible and Popover == Applicaiton.MouseGrabView we'll let it have mouse events. If a click happens outside of Popover.Frame we set Popover.Visible = false.
  • In focus/navigation handling, if Popover.HasFocus, if any other View gets focus, we'll set Popover.Visible = false.
  • For layout, whenever Top.LayoutSubviews is called, we'll then call Popover.SetRelativeLayout
  • Two APIs:
    • Applicaton.ShowPopover (View popoverView)
public static bool ShowPopover (View popoverView) 
{
   if (Popover is {})
   {
      Popover.Visible = false;
   }

   if (!popoverView.IsInitialized)
   {
      popoverView.BeginInit();
      popoverView.EndInit();
   }

   Popover = popoverView;
   Popover.Visible = true;
   Popover.SetRelativeLayout(screen);
}
  • Application.HidePopover ()
public static void HidePopover () 
{
   if (Popover is {})
   {
      Popover.Visible = false;
   }
}

View will have no knowledge of the popover concept.

Any View subclass that wants a Popover, will:

  • _myPopover = new MyPopoverView();
  • Note, _myPopover will always have this.SuperView is null.
  • Any time the popover should be displayed:
    • Set _myPopover.X/Y/Width/Height to ??? (NEED TO FIGURE THIS OUT)
    • Call Application.ShowPopover(_myPopover)
  • If the view wants to explicitly hide the popover,
    • Call Application.HidePopover()
       

Footnotes

  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.

Metadata

Metadata

Assignees

Labels

designIssues regarding Terminal.Gui design (bugs, guidelines, debates, etc...)

Type

No type

Projects

Status

✅ Done

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions