Skip to content

Commit

Permalink
Allow the PopupMenu to have a border (#384)
Browse files Browse the repository at this point in the history
The border provides a visual separation of the popup menu from the background.
Making it easier to see against a cluttered background.

Signed-off-by: Erik Zeek <zeekec@gmail.com>
  • Loading branch information
zeekec committed Nov 30, 2023
1 parent a8c34d4 commit 88c76d4
Show file tree
Hide file tree
Showing 3 changed files with 78 additions and 4 deletions.
1 change: 1 addition & 0 deletions CHANGES.rst
Expand Up @@ -3,6 +3,7 @@ CHANGE HISTORY

LATEST
------
- Added border option to PopupMenu

1.15.0
------
Expand Down
14 changes: 10 additions & 4 deletions asciimatics/widgets/popupmenu.py
Expand Up @@ -16,29 +16,35 @@ class PopupMenu(Frame):
palette = defaultdict(lambda: (Screen.COLOUR_WHITE, Screen.A_NORMAL, Screen.COLOUR_CYAN))
palette["focus_button"] = (Screen.COLOUR_CYAN, Screen.A_NORMAL, Screen.COLOUR_WHITE)

def __init__(self, screen, menu_items, x, y):
def __init__(self, screen, menu_items, x, y, has_border=False):
"""
:param screen: The Screen being used for this pop-up.
:param menu_items: a list of items to be displayed in the menu.
:param x: The X coordinate for the desired pop-up.
:param y: The Y coordinate for the desired pop-up.
:param has_border: Whether the menu has a border box. Defaults to False.
The menu_items parameter is a list of 2-tuples, which define the text to be displayed in
the menu and the function to call when that menu item is clicked. For example:
menu_items = [("Open", file_open), ("Save", file_save), ("Close", file_close)]
"""

border_adjustment = 0
if has_border:
border_adjustment = 2 # We add one character to each side for the border

# Sort out location based on width of menu text.
w = max(len(i[0]) for i in menu_items)
h = len(menu_items)
w = max(len(i[0]) for i in menu_items) + border_adjustment
h = len(menu_items) + border_adjustment
if x + w >= screen.width:
x -= w - 1
if y + h >= screen.height:
y -= h - 1

# Construct the Frame
super().__init__(
screen, h, w, x=x, y=y, has_border=False, can_scroll=False, is_modal=True, hover_focus=True)
screen, h, w, x=x, y=y, has_border=has_border, can_scroll=False, is_modal=True, hover_focus=True)

# Build the widget to display the time selection.
layout = Layout([1], fill_frame=True)
Expand Down
67 changes: 67 additions & 0 deletions tests/test_widgets.py
Expand Up @@ -2905,6 +2905,73 @@ def click(x):
self.assertEqual(len(scene.effects), 0)
self.assertEqual(self.clicked, 2)

# Reset menu for border test
self.clicked = 0
popup = PopupMenu(canvas,
[
("First", lambda: click(1)),
("Second", lambda: click(2)),
("Third", lambda: click(4))
],
0, 0, has_border=True)
scene.add_effect(popup)
scene.reset()

# Check that the menu is rendered correctly.
for effect in scene.effects:
effect.update(0)
self.assert_canvas_equals(
canvas,
"+------+ \n" +
"|First | \n" +
"|Second| \n" +
"|Third | \n" +
"+------+ \n" +
" \n" +
" \n" +
" \n" +
" \n" +
" \n")

# Check it handles a selection as expected
self.process_mouse(scene, [(1, 2, MouseEvent.LEFT_CLICK)])
self.assertEqual(len(scene.effects), 0)
self.assertEqual(self.clicked, 2)

# Reset menu for border and title test
self.clicked = 0
popup = PopupMenu(canvas,
[
("First", lambda: click(1)),
("Second Wider", lambda: click(2)),
("Third", lambda: click(4))
],
0, 0, has_border=True)
popup.title = 'Test'
scene.add_effect(popup)
scene.reset()

# Check that the menu is rendered correctly.
for effect in scene.effects:
effect.update(0)
self.assert_canvas_equals(
canvas,
"+--- Test ---+ \n" +
"|First | \n" +
"|Second Wider| \n" +
"|Third | \n" +
"+------------+ \n" +
" \n" +
" \n" +
" \n" +
" \n" +
" \n")

# Check it handles a selection as expected
self.process_mouse(scene, [(1, 2, MouseEvent.LEFT_CLICK)])
self.assertEqual(len(scene.effects), 0)
self.assertEqual(self.clicked, 2)

# Check choice of location at bottom right
self.clicked = 0
canvas.reset()
Expand Down

0 comments on commit 88c76d4

Please sign in to comment.