Coder Social home page Coder Social logo

jwlodek / py_cui Goto Github PK

View Code? Open in Web Editor NEW
749.0 13.0 42.0 20.16 MB

A python library for intuitively creating CUI/TUI interfaces with widgets, inspired by gocui.

Home Page: https://jwlodek.github.io/py_cui-docs

License: BSD 3-Clause "New" or "Revised" License

Python 100.00%
cui tui terminal terminal-based python python-library command-line commandline-interface command

py_cui's Issues

Base class for Container widget

Is your feature request related to a problem? Please describe.
Several widgets and popups are easiest to construct using a combination of other widgets. For example, the form popup or file popup.

Describe the solution you'd like
A set of classes should be made describing a Container widget. This should eliminate boilerplate required for such widget combinations.

Additional context
Form and FileDialog popups likely good candidates to work from

Improve testing infrastructure

Is your feature request related to a problem? Please describe.
The current testing solution creates instances of each object for each test case. This is redundant and can be simplified

Describe the solution you'd like
Use pytest fixtures to create instances of required objects

Cannot move to next column if widget is not on row 0

Getting an issue where I cannot move to the next column if there is not an item on the first row of the next column. The code im using as follows (with offending imports commented out.):

import py_cui
#import pantrack

class Interface():

	def __init__(self):
		#self.db = pantrack.Database()
		self.master = py_cui.PyCUI(6,2)
		self.task_menu = self.master.add_scroll_menu('Tasks', 0, 0, row_span=6)
		self.update_button = self.master.add_button('Update', 1, 1, command=self.update_tasks)

	def start(self):
		self.master.start()

	def update_tasks(self):
		#tasks = [task['short_desc'] for task in self.db.get_tasks()]
		self.task_menu.add_item_list(tasks)

if __name__ == '__main__':
        ui = Interface()
        ui.start()

The error still persisted when i tried:

  • Different types of widgets
  • The first column widget didnt use the max row_span
  • The row of the first column widget was greater than 0.

Block Label's method set_title() doesn't change the title in the widget

Describe the bug
Method set_title() of block label doesn't change the title in the widget, but internally in the variables it does. You can know this because when you get the value of the title, it returns you the new value not the previous one.

To Reproduce
It is shown in the screenshoot of the code editor

Expected behavior
It is expected that when the method set_title() is called, it should change the title of the widget block label as it does with label

Screenshots
imagen
imagen
imagen

Environment:

  • OS: Microsoft Windows [Versión 10.0.18362.900]
  • Terminal: CMD, Windows Terminal (WSL - Ubuntu 20.04)

Change text of item in scroll menu

This feature request is not related with any problem. It's just something that with the use of the library I notice and I think is missing

The feature I'm telling is the possibilty to change the text of a selected item in a Scroll Menu with a method. This method could receive the index of the item you want to change and the new text, or only recieve the new text and take the value of the index that is inside the class.

Finally, I want to tell you that I really like the way you're developing this library, is really easy to use.

BlockLabel: wrong center text

I think I've found a little bug.

The parameter center=True|False is not working because in the method add_block_label in __init__.py when creating the widget LabelBlock I've found the last 2 parameter inverted (logger and center)

fix

new_label   = widgets.BlockLabel(id,  ..., center,  self._logger)

Request: Slider

Hi I'm new to python and I'm very happy to use your library as a learning tool

I'm currently writing a very simple app https://github.com/wdog/mini-radio-player-pycui using your library.

I've implemented a simple Slider and a few others things as a updatable Labels.

I think that would be useful if you implement some of this directly in your library.

I know that my code is awful and very buggy, but there are some good ideas (I hope) 😄

Thanks

AttributeError: 'ScrollMenu' object has no attribute 'add_form'

I created 2 Widgets, scroll menus for each widget.
Python thows AttributeError when add_form() is applied.
I don't know if it's a bug or a misuse.

class mainTUI:

    def __init__(self, master):
        self.master = master
        self.master.set_title("menu example 2")
        self.widget_set_A = self.master.create_new_widget_set(10, 10)
        self.widget_set_B = self.master.create_new_widget_set(10, 10)

        # MENU 1
        self.menu1 = self.widget_set_A.add_scroll_menu('menu_1', 0, 0, row_span=7, column_span=2)
        self.menu1.add_key_command(py_cui.keys.KEY_ENTER, self.toggle_widget_2)
        self.master.apply_widget_set(self.widget_set_A)
        self.menu1.add_form("test")

        # MENU 2
        self.menu2 = self.widget_set_B.add_scroll_menu('menu_2', 0, 0, row_span=7, column_span=2)
        self.menu2.add_key_command(py_cui.keys.KEY_ENTER, self.toggle_widget_1)


    def toggle_widget_1(self):
        self.master.apply_widget_set(self.widget_set_A)

    def toggle_widget_2(self):
        self.master.apply_widget_set(self.widget_set_B)


root = py_cui.PyCUI(10, 10)
root.set_title('CUI TODO List')
s = mainTUI(root)
root.start()

Environment:

  • Debian
  • Terminal: gnome-terminal
  • Version: v3.30.2

Add border color selection, improve widget coloring options.

Colors need to be expanded to allow for more complex configurations, and border color should be a simple setting

Describe the solution you'd like
Border color attribute and function. Defaults to standard widget color.

Additional coloration options to allow finer control of widget coloring. Color rules will remain top level coloration option, but if lower level one is assigned it will take precedence (ex - color selected item in scroll menu)

Add dialog popups and widgets

Add additional folder dialog_widgets, and include widgets and popups for selecting file/directory, forms, etc.

  • Forms
  • File select
  • Directory select

Set color mode issue.

I got a strange error while working with py-cui. When I execute my code everything looks great but when I press enter on one of my buttons it gives me this error. Any ideas?

File "/usr/local/lib/python3.7/dist-packages/py_cui/widgets.py", line 381, in draw self.renderer.set_color_mode(self.color) AttributeError: 'NoneType' object has no attribute 'set_color_mode'

Add function for selecting background color

Is your feature request related to a problem? Please describe.
Background color should be easily select-able, so TUIs with different styles can be more easily developed.

Describe the solution you'd like
A method in the base PyCUI class that takes a single background color paramter

KEY_TAB to switch between widgets and provide focus

For the app I built, I found the Overview and Focus modes a bit unfriendly to use. I have three widgets on my screen as follows.

+--------------------------------------------------------------------------+
|                                     |                                    |
|                                     |                                    |
|                                     |                                    |
|-------------------------------------|                                    |
|                                     |                                    |
|                                     |                                    |
|                                     |                                    |
+--------------------------------------------------------------------------+

Arrow navigation felt a bit wonky in overview mode (as previously reported by a different user), so I wanted to simply make consecutive TAB presses cycle through the three widgets and placing it in focus mode automatically.

So, here is what I did in case anyone else wants to do the same:

        focus = itertools.cycle(root.widgets.values())

        def select_next_widget():
            root.move_focus(next(focus))

        root.add_key_command(py_cui.keys.KEY_TAB, select_next_widget)
        menu1.add_key_command(py_cui.keys.KEY_TAB, select_next_widget)
        menu2.add_key_command(py_cui.keys.KEY_TAB, select_next_widget)
        menu3.add_key_command(py_cui.keys.KEY_TAB, select_next_widget)

Change Color Selected Item

Hi!

how can i change the color of a selected line in a scroll_menu?
(and in general of a particular part of a widget? )

Bold is not enough, What I have to do if I want to use reverse colors GREEN_ON_BLACK and BLACK_ON_GREEN for example.

Can I add an object to the scroll_menu and use maybe __str__ for rendering, so to have a selected object and not a string get() or an int get_selected_item() ?

thanks a lot

Popup multiline

@jwlodek what do you think about implementing multiline popup

basically I think is enough to change _draw_ method on Popup class (popyp.py)
with something like this.

I need something like that for implementing popups with helps or descriptions (readonly multiline popup text)

    def _draw(self):
        """Function that uses renderer to draw the popup

        Can be implemented by subclass. Base draw function will draw the title and text in a bordered box
        """

        super()._draw()

        self._text_lines = self._text.splitlines()
        if len(self._text_lines) == 0:
            self._text_lines.append('')

        # limit to 5 row of text 
        self._text_lines = self._text_lines[0:5]

        target_y = int(self._stop_y - ( self._start_y + len(self._text_lines))// 2)
        self._renderer.set_color_rules([])
        self._renderer._set_bold()
        self._renderer.set_color_mode(self._color)
        self._renderer.draw_border(self, with_title=False)
        self._renderer.draw_text(self, self._title, target_y - 2,
                                 centered=True, selected=True)
        for line in self._text_lines:
            self._renderer.draw_text(self, line,  target_y,
                                    centered=True, selected=True)
            target_y += 1
        self._renderer.unset_color_mode(self._color)
        self._renderer._unset_bold()
        self._renderer.reset_cursor(self)

Maybe passing Centered as parameter, changing size of the popup to fit all the text or at least more rows (I've limited to 5 line in the code) and positiong text at the top of the popup.

Is seems that changing the code will not break existing popups...

Logger Implementation

It would make the debugging process significantly easier if there was some type of logging ability. I would propose an INFO level logger, which would output to a file and then could be used to diagnose user issues, as well as a DEBUG level logger which could either output to the status bar or somewhere else on the screen while the TUI is still being drawn. The best resource for this would be pythons builtin logging module

This will be next on my list if someone else doesn't implement it first.

Border colors don't get set correctly when widget is in focus if not specified

Describe the bug
When the focus border color is not explicitly set, it defaults to widget color, instead of border color.

To Reproduce
Steps to reproduce the behavior:

  1. Set widget border color to non default value
  2. Select widget

Expected behavior
Focus border color should retain standard border color if not directly specified

Screenshots
In the below image the warning popup is broken since the focus border color defaults to WHITE_ON_BLACK despite the border color being set to YELLOW_ON_BLACK
image

Environment:

  • OS: N/A
  • Terminal: N/A
  • Version: v0.1.2

Improve live-debug mode for py_cui

Is your feature request related to a problem? Please describe.
Printing logger messages to status bar is not useful - if multiple log messages are printed before refresh only last one visible

Describe the solution you'd like
Add a default keybinding Ctrl + D if logging is enabled to toggle an overlay window with recent log messages.

Additional context
Can probably re-use a lot of code from popup menu.

set_title does nothing for BlockLabels

Because BlockLabels uses _lines instead of _title, set_title does nothing.

To reproduce:

import py_cui
root = py_cui.PyCUI(2,2)
root.set_title('Block Labels')
label = root.add_block_label('Hello\nWorld', 0,0, row_span=2, column_span=2)
label.set_title('Goodbye\nWorld')
root.start()

Expected behavior
set_title(title) should set the BlockLabel's ._lines attribute to title.splitlines(), or an alternative function should be supplied to set the lines attribute directly.

Environment:

  • OS: Windows 10
  • Terminal: Powershell Core
  • Version: 0.1.1

A way to destroy/forget widgets

Is your feature request related to a problem? Please describe.
I want to redraw just a small part of the screen I've created. In something like Tk, I would use either .destroy() or pack_forget, etc.

Trying to manually delete items from _widgets causes an error in the draw method.

Describe the solution you'd like
Either attach a destroy method to the overall widget class, or add something like a remove(widget-id) function to the py-cui and widgetset classes

Add chart widgets

Is your feature request related to a problem? Please describe.
Widgets for charts (line, bar, etc) should be added.

Describe the solution you'd like
A new folder called chart_widgets should be made, and individual chart widgets should be developed and placed there.

  • Bar Chart
  • Line Chart
  • Histogram
  • Pie Chart (Maybe?)
  • Scatterplot

pip install

Just as a suggestion for people confused why they are unable to install with pip, if getting error 'py-cui requires Python '>=3.2' but the running Python is 2.7.17', you must use pip3 to install once cloned.

Update Element with Thread

I've this little examples that does not work... It' doesn't update the screen until I press a key.

Basically what I want is to run a thread that updates some element and update the screen
Can you help me to understand why it is not completely working?

#!/usr/bin/env python3

import py_cui
import threading
import time
from datetime import datetime


class App:
    def __init__(self, master):
        self.master = master
        self.g1 = self.master.add_scroll_menu('PANEL',
                                              0, 0, row_span=6, column_span=6)
        thread = threading.Thread(target=self.thread_update, args=())
        thread.daemon = True
        thread.start()

    def thread_update(self):
        try:
            while True:
                d = "TIME:{}".format(datetime.now().strftime('%X'))
                self.g1.add_item(d)
                time.sleep(1)
        except Exception as e:
            print(e)


root = py_cui.PyCUI(9,6)
root.toggle_unicode_borders()
root.set_title('Test')
s = App(root)
root.start()

Is it a thread/curses limitation?

Youtube tutorial

Should make some youtube tutorial videos on how to write simple things with py_cui

Create base classes for text line, scroll cell

There is lots of redundant code between popups and widgets that does the same thing. A new set classes should be created, and the respective widgets/popups should extend these in addition to the base Widget and Popup classes.

Allow user to provide custom function to generate a line in ScrollMenu

Allow user to specify a custom to_str function for the ScrollMenu (and other similar widgets) that would be used to determine the line of text to display on the screen. Currently, only strings are allowed to be added to the widget.

Motivation? Imagine we had a list of Pet objects that might be defined as:

class Pet:
  def __init__(self, name, breed, age):
    self.name = name
    self.breed = breed
    self.age = age

  def purchase(self):
    print(f"You purchased a {self}")

  def __str__(self):
    return f"name={self.name} breed={self.breed} age={self.age}"

To display a list of pets and add an event handler for one that is selected in a ScrollMenu, I'd have to do the following:

pets = [Pet("Baloo", "Dog", 4), Pet("Wilbur", "Horse", 7)]
menu = root.add_scroll_menu("Pets", 0, 0)
menu.add_item_list(str(p) for p in pets) # explicitly convert to string

def process_selection():
  selected_pet_text = menu.get()
  for p in pets:
    if str(p) == selected_pet_text:
      p.purchase()

menu.add_key_command(py_cui.keys.KEY_ENTER, process_selection)

With a very small change to the code, users wouldn't have to look up which Pet was selected because the return value from ScrollMenu.get now returns the actual Pet object itself:

pets = [Pet("Baloo", "Dog", 4), Pet("Wilbur", "Horse", 7)]
menu = root.add_scroll_menu("Pets", 0, 0)
menu.add_item_list(pets) # Allow any object to be added

def process_selection():
  selected_pet_object = menu.get() # this returns the object in the list
  selected_pet_object.purchase()   # so we can use it directly

menu.add_key_command(py_cui.keys.KEY_ENTER, process_selection)

Here is a custom MyScrollMenu that I built to add the above, but it the change is so minor and backwards compatible, that I think it should be added to the widget directly. In the custom widget, I've added one new parameter to the constructor: to_str. It has a default value of str. And then in the body of the ScrollMenu.draw method, only one line is changed—the in clause of the for loop:

class MyScrollMenu(py_cui.widgets.ScrollMenu):
    def __init__(self, id, title, grid, row, column, row_span, column_span, padx, pady, to_str=str,
    ):
        super().__init__(id, title, grid, row, column, row_span, column_span, padx, pady)
        self.to_str = to_str

    def draw(self):
        super(py_cui.widgets.ScrollMenu, self).draw()

        self.renderer.set_color_mode(
            self.selected_color if self.selected else self.color
        )
        self.renderer.draw_border(self)

        counter = self.pady + 1
        line_counter = 0
        for line in (self.to_str(i) for i in self.view_items):
            if line_counter < self.top_view:
                line_counter = line_counter + 1
            else:
                if counter >= self.height - self.pady - 1:
                    break
                if line_counter == self.selected_item:
                    self.renderer.draw_text(
                        self, line, self.start_y + counter, selected=True
                    )
                else:
                    self.renderer.draw_text(self, line, self.start_y + counter)
                counter = counter + 1
                line_counter = line_counter + 1

        self.renderer.unset_color_mode(
            self.selected_color if self.selected else self.color
        )
        self.renderer.reset_cursor(self)

Another nice item with this approach is that one can have multiple ScrollMenu widgets all containing the same Pet list, but each could be displayed differently just by passing a custom to_str function:

pets = [Pet("Baloo", "Dog", 4), Pet("Wilbur", "Horse", 7)]

menu1 = root.add_scroll_menu("Pets Detail", 0, 0)
menu1.add_item_list(pets) # Allow any object to be added

menu2 = root.add_scroll_menu("Pet Names", 0, 0, to_str=lambda p: p.name)
menu2.add_item_list(pets) # Allow any object to be added

Move CI/CD to github actions

I would like to move the automated unit testing to use github actions instead of Travis. I would also like to add a workflow for automatically generating pypi releases on git tag.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.