Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP - v0.1.7 #184

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/build_docs_test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@ jobs:

steps:
- uses: actions/checkout@v2
- name: Set up Python 3.7
- name: Set up Python 3.9
uses: actions/setup-python@v1
with:
python-version: 3.7
python-version: 3.9
- name: Build Docs
run: |
cd docs/scripts && bash generateFromDocstrings.sh
4 changes: 2 additions & 2 deletions .github/workflows/build_package_test_linux.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@ jobs:

steps:
- uses: actions/checkout@v2
- name: Set up Python 3.7
- name: Set up Python 3.9
uses: actions/setup-python@v1
with:
python-version: 3.7
python-version: 3.9
- name: Update pip
run: |
python -m pip install --upgrade pip
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/build_package_test_win.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@ jobs:

steps:
- uses: actions/checkout@v2
- name: Set up Python 3.7
- name: Set up Python 3.9
uses: actions/setup-python@v1
with:
python-version: 3.7
python-version: 3.9
- name: Update pip
run: |
python -m pip install --upgrade pip
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/py_cui_test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: [3.6, 3.7, 3.8, 3.9]
python-version: [3.7, 3.8, 3.9]

steps:
- uses: actions/checkout@v2
Expand Down
21 changes: 21 additions & 0 deletions examples/controls/dropdown.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
"""The most basic possible use case for py_cui

@author: Jakub Wlodek
@created: 12-Aug-2019
"""

# Import the lib
import py_cui

# create the CUI object. Will have a 3 by 3 grid with indexes from 0,0 to 2,2
root = py_cui.PyCUI(3, 3)

# Add a label to the center of the CUI in the 1,1 grid position
dropdown = root.add_custom_widget(py_cui.widgets.DropdownMenu, 'Example Dropdown', 1, 1, 1, 1, 1, 0, 10)
textbox = root.add_text_box('Test Text Box', 0, 1, 1, 1, 1, 0, "Hi")

for i in range(15):
dropdown.add_item(f'Test{i}')

# Start/Render the CUI
root.start()
86 changes: 86 additions & 0 deletions examples/expanded_palette.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
"""A simple example demonstrating how to add additional colors to your UI past the defaults.

@author: Jakub Wlodek
@created: 17-Apr-2023
"""


import py_cui

root = py_cui.PyCUI(3, 3)

# The default colors created for you by py_cui are essentially every foreground/background
# combination using the default basic 8 colors: White, Black, Blue, Red, Green, Yellow, Cyan, and Magenta.

# For conveniance, these color pairs have variables that can be used as shortcuts, ex. py_cui.colors.GREEN_ON_BLACK will allow
# for green text on a black background.

# These default colors make up 56 foreground/background combinations. In total, py_cui allows for up to 256 (in supported terminals).
# The remaining 200 foreground/background pairs can be customized with the below function calls.


# The add_color_pair function takes two arguments, a foreground color, and a background color, each being a number from 0-255.
# The default colors have codes: 0 - BLACK, 1- RED, 2 - GREEN, 3 - YELLOW, 4 - BLUE, 5 - MAGENTA, 6 - CYAN, 7 - WHITE.
# 208 represents a shade of orange in my terminal emulator.

ORANGE_ON_BLACK = root.add_color_pair(208, 0)


# Additional color codes can depend on the terminal emulator, however, the below program can help identifying them.
# It will print (on a black background), the code for each of the 256 supported colors - in the respective color.
# The above number (203) was printed orange in my terminal emulator (gnome-terminal). So (208, 0) will be orange on a black background.
"""
import curses

def show_color_codes(stdscr):
key = 0

# Clear and refresh the screen for a blank canvas
stdscr.clear()
stdscr.refresh()

# Start colors in curses
curses.start_color()

for i in range(256):
curses.init_pair(i+1, i, 0)

# Loop where k is the last character pressed
while (k != ord('q')):

# Initialization
stdscr.clear()
height, width = stdscr.getmaxyx()

xpos = 0
ypos = 0
for i in range(256):
temp = int(i / 25)
if temp > ypos:
ypos += 1
xpos = 0
stdscr.addstr(ypos, xpos, " ", curses.color_pair(0))
stdscr.addstr(ypos, xpos + 1, str(i), curses.color_pair(i))
stdscr.addstr(ypos, xpos + 1 + len(str(i)), " ", curses.color_pair(0))
xpos = xpos + len(str(i)) + 2

# Refresh the screen
stdscr.refresh()

# Wait for next input
key = stdscr.getch()

def main():
curses.wrapper(show_color_codes)

if __name__ == "__main__":
main()
"""


# Add a label to the center of the CUI in the 1,1 grid position
label = root.add_label('Hello py_cui in a non-standard color!!!', 1, 1)
label.set_color(ORANGE_ON_BLACK)

# Start/Render the CUI
root.start()
44 changes: 40 additions & 4 deletions py_cui/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,17 @@ def __init__(
height = self._simulated_terminal[0]
width = self._simulated_terminal[1]

# Curses supports up to 256 color pairs. Outside of the default color pairs (56 total combos)
# Allow user to customize remaining 200
self._num_color_pairs = 56
self._available_color_pairs = 200
self._color_map = py_cui.colors._COLOR_MAP

# Data structure mapping color combos to pair code for faster lookup
self._reverse_color_map = {}
for i in range(56):
self._reverse_color_map[self._color_map[i+1]] = i+1

# Add status and title bar
self.title_bar = py_cui.statusbar.StatusBar(
self._title, BLACK_ON_WHITE, root=self, is_title_bar=True
Expand Down Expand Up @@ -168,7 +179,7 @@ def __init__(

# Initialize grid, renderer, and widget dict
self._grid = py_cui.grid.Grid(
num_rows, num_cols, self._height, self._width, self._logger
self, num_rows, num_cols, self._height, self._width, self._logger
)
self._stdscr: Any = None
self._refresh_timeout = -1
Expand Down Expand Up @@ -198,6 +209,20 @@ def __init__(
# Callback to fire when CUI is stopped.
self._on_stop: Optional[Callable[[], Any]] = None

def add_color_pair(self, foreground_color: int, bkgd_color: int):
if self._available_color_pairs == 0:
raise py_cui.errors.PyCUIError("Maximum number of color pairs permitted has been exceeded!")
else:
self._available_color_pairs -= 1
self._num_color_pairs += 1
self._color_map[self._num_color_pairs] = (foreground_color, bkgd_color)
self._reverse_color_map[(foreground_color, bkgd_color)] = self._num_color_pairs
return self._num_color_pairs

def get_color_code(self, foreground_color: int, bkgd_color: int) -> int:

return self._reverse_color_map[(foreground_color, bkgd_color)]

def set_refresh_timeout(self, timeout: int):
"""Sets the CUI auto-refresh timeout to a number of seconds.

Expand Down Expand Up @@ -238,6 +263,14 @@ def set_widget_cycle_key(
self._reverse_cycle_key = reverse_cycle_key

def set_toggle_live_debug_key(self, toggle_debug_key: int) -> None:
"""Sets the keybinding for opening/closing popup debug log.

Parameters
----------
toggle_debug_key : py_cui.keys.KEY
Key code for debug log open/close toggle
"""

self._toggle_live_debug_key = toggle_debug_key

def enable_logging(
Expand Down Expand Up @@ -383,8 +416,8 @@ def _initialize_colors(self) -> None:
# Start colors in curses.
# For each color pair in color map, initialize color combination.
curses.start_color()
for color_pair in py_cui.colors._COLOR_MAP.keys():
fg_color, bg_color = py_cui.colors._COLOR_MAP[color_pair]
for color_pair in self._color_map.keys():
fg_color, bg_color = self._color_map[color_pair]
curses.init_pair(color_pair, fg_color, bg_color)

def _initialize_widget_renderer(self) -> None:
Expand Down Expand Up @@ -1795,7 +1828,10 @@ def _draw(self, stdscr) -> None:
self._logger.info(
f"handling mouse press for elem: {in_element.get_title()}"
)
in_element._handle_mouse_press(x, y, mouse_event)

move_focus = in_element._handle_mouse_press(x, y, mouse_event)
if move_focus:
self.move_focus(in_element)

# Otherwise, if not a popup, select the clicked on widget
elif in_element is not None and not isinstance(
Expand Down
3 changes: 2 additions & 1 deletion py_cui/grid.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ class Grid:
"""


def __init__(self, num_rows: int, num_columns: int, height: int, width: int, logger: 'py_cui.debug.PyCUILogger'):
def __init__(self, parent, num_rows: int, num_columns: int, height: int, width: int, logger: 'py_cui.debug.PyCUILogger'):
"""Constructor for the Grid class

Parameters
Expand All @@ -46,6 +46,7 @@ def __init__(self, num_rows: int, num_columns: int, height: int, width: int, log
The width in characters of the terminal window
"""

self._parent = parent
self._num_rows = num_rows
self._num_columns = num_columns
self._height = height
Expand Down
4 changes: 2 additions & 2 deletions py_cui/renderer.py
Original file line number Diff line number Diff line change
Expand Up @@ -377,7 +377,7 @@ def draw_text(self, ui_element: 'py_cui.ui.UIElement', line: str, y: int, center
self.set_color_mode(ui_element.get_border_color())

if bordered:
self._stdscr.addstr(y, start_x + padx, self._border_characters['VERTICAL'])
self._stdscr.addstr(y, start_x + padx, self._border_characters['VERTICAL'] + ' ')
current_start_x = current_start_x + 2

self.unset_color_mode(ui_element.get_border_color())
Expand Down Expand Up @@ -409,7 +409,7 @@ def draw_text(self, ui_element: 'py_cui.ui.UIElement', line: str, y: int, center
self.set_color_mode(ui_element.get_border_color())

if bordered:
self._stdscr.addstr(y, stop_x - padx - 1, self._border_characters['VERTICAL'])
self._stdscr.addstr(y, stop_x - padx - 2, ' ' + self._border_characters['VERTICAL'])

self.unset_color_mode(ui_element.get_border_color())

Expand Down
44 changes: 44 additions & 0 deletions py_cui/ui.py
Original file line number Diff line number Diff line change
Expand Up @@ -961,6 +961,41 @@ def set_selected_item(self, selected_item: Any):
self._view_items[self._selected_item] = selected_item


def get_item_index(self, index: int):
"""Function that returns reference to item at given index

Paramters
---------
index : int
Index of object

Returns
-------
item : Any
Item at specified index in the list, or None if index is invalid.
"""

try:
return self._view_items[index]
except IndexError:
return None


def set_item_index(self, item: Any, index: int):
"""Function that sets the item at the specified index of the menu

Parameters
----------
item: Any
Item to put in menu at given index
index: int
Index at which to put the specified item.
"""

if item is not None and len(self._view_items) > index and index >= 0:
self._view_items[index] = item


class CheckBoxMenuImplementation(MenuImplementation):
"""Class representing checkbox menu ui implementation

Expand Down Expand Up @@ -1051,6 +1086,15 @@ def mark_item_as_not_checked(self, item) -> None:
self._selected_item_dict[item] = False


class DropdownMenuImplementation(MenuImplementation):

def __init__(self, logger, max_height):

super().__init__(logger)
self.max_height = max_height
self.opened = False


class TextBlockImplementation(UIImplementation):
"""Base class for TextBlockImplementation

Expand Down
2 changes: 1 addition & 1 deletion py_cui/widget_set.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ def __init__(self, num_rows: int, num_cols: int, logger: 'py_cui.debug.PyCUILogg
status_bars_height = self._root.title_bar.get_height() + self._root.status_bar.get_height()
self._height = self._height - status_bars_height - 2

self._grid = py_cui.grid.Grid(num_rows, num_cols, self._height, self._width, logger)
self._grid = py_cui.grid.Grid(root, num_rows, num_cols, self._height, self._width, logger)

self._selected_widget: Optional[int] = None
self._logger = logger
Expand Down
Loading