Plié: A Python Terminal User Interface Library¶
Plié helps you create interactive complexly laid out terminal applications with ease. Plié can be used to create single pane, or multi-pane layouts. It strives to be fast and precise enough that you could use it for a rogue like rpg, while still being easy enough to pick up in a few minutes.
The broad strokes of Plié:¶
plie.Text
is the meat and potatoes. It can handle
displaying a single line text or whole paragraphs of text. If you want
plie.Text
to be a button, give it a callout function, if you want it to be updateable
give it a callback function.
plie.MultiText
adds on another layer to plie.Text
and serves as a container. If
you want a menu of selectable items, use plie.Multitext
, which is just a list filled
with plie.Text
as it’s items.
- Both
plie.Text
andplie.MultiText
haveupdate()
methods, which allow you - to update the contents of them, and have the updated contents be shown the next time the screen is rendered.
plie.Renderer
is where all the action happens. plie.Renderer
takes what is
called a ‘valid view dict’ which represents a layout of everything on the screen, and displays it
to the terminal.
Currently implemented features:¶
- Basic layouts, with a header, body and footer
- rendering text to the screen, and updating text
- support for different border styles
Planned features:¶
- popup windows that can display text messages for a specified amount of time.
- menus which contain multiple selectable items for interface control flow.
- solid asyncio event integration for handling a variety of events, including keyboard, timers and voice commands (via speech_recognition)
Other useful pages¶
Renderer¶
-
class
plie.
Renderer
(size=None, view=None)[source]¶ Bases:
object
-
add_view
(view)[source]¶ Take in a valid view dict, and initialize anything that needs initializing, then store it ready for displaying
Notes
To initialize in this context means find the size for each Renderable, by examining the bounds, padding and positioning. So that when the Renderable is initialized, it can start at the appropriate size. The instance of the Renderable for each renderable section then gets added to that section.
Sections get turned into ChainMaps, if they share a name with any of the default sections, they can inherit attributes from that default.
Parameters: view – a valid view dict Returns: nothing, but adds an initialized view to the ChainMap
-
composite
(cellspace, position=(0, 0))[source]¶ Given a cellspace and position, add that to the internal dict at the correct position.
Parameters: - cellspace – a cell space dictionary representation of a view object
- position – where the top left corner should go
-
display
(update=True)[source]¶ Display fullscreen out to the terminal.
Parameters: update – whether or not to update before displaying Notes
Uses Blessed for fullscreen terminal support.
-
Text¶
-
class
plie.
Text
(text='', callout=None, justify='left', bounds=None, replace_whitespace=True)[source]¶ Bases:
object
Text is the universal class for dealing with all single text snippets.
Text helps with basic formatting, justification, word wrapping to the appropriate width and acts as a way to contain a block of text for applying styles to it or developing a layout.
Parameters: - text – the text to display (can be changed later
- callout – an attribute made available for storing a function, it is used if this Text object gets selected by some event.
- justify – specifies which justification the text should have, options are: ‘left’ where all the text aligns with the left edge, ‘center’ where all the text is centered in the middle of the, available space ‘right’ where all the text aligns with the right edge
- bounds – the bounding box for the Text object in screen space cells, will be set automatically by Renderer usually
Returns: an initialized Text object
-
as_cells
()[source]¶ Translates the internal state and translates it into a screen-space cell dict of size bounds
Returns: Dictionary with keys of format (x,y), of size bounds and single character strings as the values.
-
lines
¶ Returns: the number of lines that the Text instance uses
MultiText¶
-
class
plie.
MultiText
(texts=(), bullet_choice='', justify='left', bounds=None)[source]¶ Bases:
plie.text.Text
MultiText displays sequences of text, including bulleted lists.
MultiText can be used for lists of text, menus, and other things that involve multiple distinct elements of vertically arranged text.
Parameters: - texts – sequence of text strings to display (preferably immutable)
- bullet_choice – a string that will be used as the bullet for each list item
- justify – which justification to use for the list items
- bounds – bounding box specifying size in cells (x,y), usually set by Renderer
-
__str__
()[source]¶ creates a printable string of this instance
Individual texts in in the MultiText instance are prefixed with self.bullet_choice, multiline texts have subsequent lines indented to where they start at an equal indent to the first line
Returns: a printable string of the contents of this instance
-
as_cells
()[source]¶ Translates the internal state into a cell space based format for transferring
Returns: a dictionary cell space representation of all the contained text objects
-
update
(bounds=None, texts=None, specific_text=(), bullet_choice=None)[source]¶ For changing internal state
Parameters: - bounds – update the bounds, either a tuple or a Bounds object
- texts – update the entire list of texts
- specific_text – a tuple of format (new_text_string, index)
- bullet_choice – update the bullet_choice
Returns: nothing, but updates the internal state
View¶
What counts as a ‘valid view dict’?¶
This grammar specifies what Renderer accepts as a ‘valid view dict’ in Extended Backus-Naur Form.
valid_view_dict = sections
sections = renderable_sections | non_renderable_sections
renderable_sections = (section_label ':' renderable_section_contents ',')*
renderable_section_contents = renderable_True, [positioning], [bounds],
[view_object], [contents], [styles], [padding], [VO_instance]
renderable_True = "'renderable' : True, "
section_label = "'"?string_literal?"'"
positioning = "'positioning' :" positioning_contents
positioning_contents = positioning_tuple | positioning_nt
positioning_tuple = '(' vertical_option ',' horizontal_option ')'
positioning_nt = 'Positioning(vertical=' vertical_option ', horizontal=' horizontal_option ')'
vertical_option = percent | vertical_choice | positioning_int_pair
horizontal_option = percent | vertical_choice | positioning_int_pair
positioning_int_pair = '(' integer ',' integer ')'
vertical_choice = 'top' | 'middle' | 'bottom'
horizontal_choice = 'left' | 'middle' | 'right'
bounds = "'bounds' : " bounds_contents
bounds_contents = None | bounds_tuple | bounds_namedtuple
bounds_tuple = '(' bounds_measure ',' bounds_measure ')'
bounds_namedtuple = 'Bounds(width=' bounds_measure ', height=' bounds_measure ')'
bounds_measure = percent | integer
percent = integer '%' ['+' | '-' integer]
integer = nonzerodigit digit* | '0'+
nonzerodigit = '1'...'9'
digit = '0'...'9'
view_object = ?renderable_callable?
contents = '[' (key_value_argument_pairs)* ']'
key_value_argument_pairs = ?string_literal? ':' contents_value
contents_value = ?list? | ?tuple? | ?mapping? | integer | "'"?string_literal?"'"
styles = '[' (style_tuple',')* ']' | style_tuple
style_tuple = ?style_function? [',' key_value_argument_pairs]
padding = padding_all_around | padding_two_sides | padding_three_sides | padding_four_sides
padding_all_around = integer
padding_two_sides = '(' integer ',' integer ')'
padding_three_sides = '(' integer ',' integer ',' integer ')'
padding_four_sides = '(' integer ',' integer ',' integer ',' integer ')'
VO_instance = "'instance' :" ?instance of the view object?
non_renderable_sections = renderable_False , [blanking] (extra_data_utils)*
renderable_False = "'renderable' : False"\
banking = "'blanking' :" boolean
boolean = 'True' | 'False'
In case that means little or nothing to you, or if you just want that in plain english:
- a valid view dict is made up of sections
- sections can be either renderable or not (specified by whether the
'renderable'
key in the section points toTrue
orFalse
- Sections are dictionaries.
renderable sections contain any of the following keys
'renderable'
,'positioning'
,'bounds'
,'view_object'
,'contents'
,'styles'
,'padding'
'renderable'
points to a boolean, which for a renderable section must be True
'positioning'
describes where in the available space the output should be put.
plie.Position
provides a convenient way to store positioning information.
- positioning has options, it be specified either as percentages or text choices:
- for the vertical component:
'top'
,'center'
,'bottom'
are the three text choices- percentages in this case mean that 0% is first row of the terminal and 100% is the last row.
- for the horizontal component:
'left'
,'center'
,'right'
are the three text choices- percentages in this case mean that 0% is first column of the terminal and 100% is the last column.
percentages can have an optional addition or subtraction of cells to specify a precise offset. Example:
'100%-1'
would be the penultimate row or column.
'bounds'
specifies how big an object is.plie.Bounds
provides a convenient way to store bounding information. Bounds are in the(width, height)
format, where there are options for how width and height are represented:
- percentage based: 100% means take up the whole screen, 0% is nothing. Percentages have an optional addition or subtraction of cells. Example:
'100%-1'
means in that direction the object should be as big as the screen minus one cell.- cell based: a cell is the basic unit of the screen, it’s usually a character. Cell based sizes specify in cells how big the object should be. Example:
50
(that’s just an integer) would mean the object in that direction should be 50 cells big.
'view_object'
points to the class of the view object that’s going to be rendered here, without actually initializing it.
'contents'
points to the arguments that should be passed to the view_object constructor. This uses keyword arguments, and just passes them as**kwargs
so any error in naming will cause an error.
'styles'
points to a list of style tuples that should be applied to this view_object
- style tuples contain the style callable and then any arguments that it should call with.
'padding'
points to a specification for how much padding (in cells) should go around the view_object. This padding gets applied to the size of the view_object to squeeze it down to a smaller size than bounds. Useful for applying borders.
nonrenderable
sections contain other stuff, but I’m not exactly sure what yet. They don’t get executed or processed in any way currently (version 0.4.0)
Styles¶
This page contains examples of what all the various styles look like.
- Sometimes the formatting on this page due to fonts or scaling messes up the examples slightly. In
- a terminal they will all line up correctly though.
Border styles¶
Border styles are kept in the BORDER_STYLE
dictionary in plie.styles
default¶
Styles dictionary name: 'default'
╒Default Border Style════════╕
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
╘════════════════════════════╛
heavy¶
Styles dictionary name: 'heavy'
┏Heavy Border Style━━━━━━━┓
┃ ┃
┃ ┃
┃ ┃
┃ ┃
┃ ┃
┃ ┃
┃ ┃
┃ ┃
┃ ┃
┃ ┃
┗━━━━━━━━━━━━━━━━━━┛
rounded¶
Styles dictionary name: 'rounded'
╭The Title───────────────────╮
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
╰────────────────────────────╯
Glossary¶
- View:
- An organizational structure for arranging view objects on the screen. Can be constructed with a dictionary all at once or by constructing it piecewise.
- Field:
- The various sections of a view. ‘header’, ‘body’, ‘footer’, ‘styles’ and ‘util’ are all fields.
- View Object:
- Something that gets displayed to the screen, makes up the view, gets put into view fields
- Renderer:
- A class to handle rendering, compositing of views and arranging view objects into screen space
- Text:
- A universal text displaying view object, that can handle changes from other places (via a callback), single line and multiple line displaying, text formatting. Also has an attribute for a callout where if the text is selected by another view object (like a menu) the function referenced in the callout will be called.
- Multitext:
- A sequence of text objects for creating menus
- Cell:
- A single character in the grid of characters that make up the terminal. The base unit of terminal space.
- Cell Based Representation:
- Many cells arranged in a grid, like a 2D matrix, also sometimes referred to as the screen-space representation.
Plié development todo list¶
Format:
- [docs] [tests] [implementation] feature
- [docs] [tests] [implementation] sub-feature
- ::
[d][ ][ ] MultiText
- [d][ ][ ] Text
- [d][t][i] init
- [d][ ][ ] update
- [d][ ][i] text update
- [d][ ][i] bounds update
- [ ][ ][ ] generic update
[ ] [ ] decide how to handle newlines in Text fields (textwrap.wrap drops them by default
- [ ][ ][ ] display
- [d][t][i] basic functionality
- [d][t][i] left justify
- [d][t][i] centered
- [d][t][i] right justify
[ ][ ][ ] speed improvements
- [ ][ ][i] Renderer
[d][t][i] init
- [d][ ][i] formulate
- [d][t][i] basic formulate (only one char fill)
- [d][ ][i] advanced formulate (works with real text objects)
[d][ ][i] display
- [d][ ][i] add_view
- [ ][ ][ ] inserting of a blank view underneath it if needed
[d][ ][i] composite
- [d][t][i] _extract_bounds_information
- [ ] decide if bounding boxes can be negative, and what that would mean
[ ][ ][ ] handling of view stacks
- [ ][ ][ ] application of styles
- [ ][ ][ ] border
- [ ][ ][ ] background
- [d][ ][ ] View
[d][ ][i] basic init
[d][ ][i] View repr returns a valid view dict
[d][t][i] Section repr returns a valid section dict
- [ ] [ ] Decide whether View should start with an empty list
for the body, or a list with one Section already in it.
[d][t][i] Sections count as False when empty (__bool__)
[ ][ ][ ] Write valid view dict specifying grammar
[ ] [ ] Decide whether add_view should be replaced by a property of View.view_stack
- [ ][ ][ ] instead of needing to call header.view_object.update or body[0].view_object,
View’s should have sane defaults setup, so one could just do header.update(), or body.update() and have it pick the only one if there’s only one or the first one if there’s multiple. Maybe make one body element user selectably primary?
- [ ][ ][ ] Event handling
- [ ] [ ] Create mockup
- [ ] [ ] continue exploring ways to integrate multiple invent systems -cough- asyncio
[ ][ ][ ] Docs
- [ ][ ][ ] Other
[x] learn more about ABCs
[ ] use that knowledge in the Renderable ABC
[ ] update Text and MultiText to subclass Renderable
- [x] refine view specifying grammar
- [ ] examine edge cases
- [x] write mock ups for different layouts
[x] Renderer rewrite
- [ ] ChainMap with sections for defaults and view inheritance
- (Note: this was formerly ChainMap for views, but ChainMap as it’s programmed, doesn’t support nested dictionaries.
[ ] ScrollableText Renderable
[ ] navigable base class
[ ] use memoryview’s for dictionaries with more lazy production
[ ] (after 1.0 release) create a view maker, for easy view creation
[ ] (after 1.0 release) create a template engine for views
- Profiling results:
- Start 3/18 12.0s
- Commit 587040e 3/18 0.168s
- Commit 4ebb27b 3/20 0.467s (but with more functionality than the last)
- Commit 3/20 0.267s (CellSpace optimizations
Plié is in early alpha currently, all publicly exposed names and attributes may change during alpha.