textual-paint/menus.py

136 lines
4.5 KiB
Python
Raw Normal View History

2023-04-13 03:49:16 +03:00
import re
from enum import Enum
from typing import List
from textual import events
from textual.message import Message, MessageTarget
from textual.app import ComposeResult
from textual.containers import Container, Horizontal, Vertical
from textual.geometry import Offset, Region, Size
from textual.reactive import var, reactive
from textual.widget import Widget
from textual.widgets import Button, Static
def to_snake_case(name):
name = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', name)
name = re.sub('__([A-Z])', r'_\1', name)
name = re.sub('([a-z0-9])([A-Z])', r'\1_\2', name)
return name.lower()
class Menu(Container):
"""A menu widget."""
items = var([])
focus_index = var(0)
def __init__(self, items: List[str], **kwargs) -> None:
"""Initialize a menu."""
super().__init__(**kwargs)
self.add_class("menu")
self.items = items
2023-04-13 05:07:38 +03:00
self.parent_menu = None
2023-04-13 03:49:16 +03:00
2023-04-13 05:07:38 +03:00
def watch_items(self, old_items, new_items: List[str]) -> None:
"""Update the menu items."""
for item in old_items:
self.unmount(item)
for item in new_items:
self.mount(item)
if item.submenu:
self.app.mount(item.submenu)
item.submenu.close()
2023-04-13 03:49:16 +03:00
def on_key(self, event: events.Key) -> None:
"""Called when the user presses a key."""
if event.key == "up":
self.focus_index -= 1
if self.focus_index < 0:
self.focus_index = len(self.items) - 1
elif event.key == "down":
self.focus_index += 1
if self.focus_index >= len(self.items):
self.focus_index = 0
2023-04-13 05:07:38 +03:00
elif event.key == "escape":
self.close()
if self.parent_menu:
self.parent_menu.focus()
2023-04-13 05:07:38 +03:00
def on_button_pressed(self, event: Button.Pressed) -> None:
"""Called when a button is clicked or activated with the keyboard."""
if isinstance(event.button, MenuItem):
2023-04-13 05:07:38 +03:00
if event.button.action:
event.button.action()
root_menu = self
while root_menu.parent_menu:
root_menu = root_menu.parent_menu
root_menu.close()
elif event.button.submenu:
was_open = event.button.submenu.display
for item in self.items:
if item.submenu:
item.submenu.close()
if not was_open:
event.button.submenu.open(self, event.button)
2023-04-13 05:07:38 +03:00
def open(self, parent_menu, parent_menu_item):
2023-04-13 05:07:38 +03:00
self.display = True
if len(self.items) > 0:
self.items[0].focus()
self.parent_menu = parent_menu
self.parent_menu_item = parent_menu_item
2023-04-13 05:42:48 +03:00
self.add_class("menu_popup")
# I don't see any way to get the position of widgets on screen.
# self.style.offset = (parent_menu.x, parent_menu.y + parent_menu.height)
# So, guess it based on the position in the list of items, works for one level.
menu_bar_height = 3
menu_bar_item_width = 8
self.styles.offset = (menu_bar_item_width * parent_menu.items.index(self.parent_menu_item), menu_bar_height)
2023-04-13 05:07:38 +03:00
def close(self):
for item in self.items:
if item.submenu:
item.submenu.close()
if not isinstance(self, MenuBar):
self.display = False
2023-04-13 03:49:16 +03:00
class MenuBar(Menu):
"""A menu bar widget."""
def __init__(self, items: List[str], **kwargs) -> None:
"""Initialize a menu bar."""
super().__init__(items, **kwargs)
self.add_class("menu_bar")
2023-04-13 05:07:38 +03:00
class MenuItem(Button):
"""A menu item widget."""
2023-04-13 03:49:16 +03:00
def __init__(self, name: str, action = None, id: str = None, submenu = None, grayed = False, **kwargs) -> None:
2023-04-13 03:49:16 +03:00
"""Initialize a menu item."""
2023-04-13 05:07:38 +03:00
super().__init__(name, **kwargs)
self.add_class("menu_item")
self.disabled = grayed
2023-04-13 03:49:16 +03:00
self.action = action
self.submenu = submenu
if isinstance(id, str):
2023-04-13 05:07:38 +03:00
self.id = id
elif id:
self.id = "rc_" + str(id)
2023-04-13 05:07:38 +03:00
else:
self.id = "menu_item_" + to_snake_case(name)
2023-04-13 03:49:16 +03:00
mid_line = "" * 100
2023-04-13 05:07:38 +03:00
class Separator(Static):
"""A menu separator widget."""
2023-04-13 03:49:16 +03:00
2023-04-13 05:07:38 +03:00
def __init__(self, **kwargs) -> None:
"""Initialize a separator."""
super().__init__(mid_line, **kwargs)
self.add_class("separator")
self.disabled = True
self.action = None
self.submenu = None