Textual is a TUI (Text User Interface) framework for Python inspired by modern web development.

Related tags

python terminal tui rich
Overview

Textual

screenshot

Textual is a TUI (Text User Interface) framework for Python inspired by modern web development. Currently a work in progress, but usable by brave souls who don't mind some API instability between updates.

Textual currently runs on MacOS / Linux only. Windows support is in the pipeline.

Follow @willmcgugan for progress updates, or post in Discussions if you have any requests / suggestions.

How it works

Textual uses Rich to render rich text, so anything that Rich can render may be used in Textual.

Event handling in Textual is asynchronous (using async and await keywords). Widgets (UI components) can independently update and communicate with each other via message passing.

Textual has more in common with modern web development than it does with curses; layout is done with CSS grid and (soon) the theme may be customized with CSS. Other techniques are borrowed from JS frameworks such as Vue and Reactive.

Installation

You can install Textual via pip (pip install textual), or by checking out the repo and installing with poetry.

poetry install

Once installed you can run the following command for a quick test, or see examples (below):

python -m textual.app

Textual requires Python 3.7 or above.

Examples

Until I've written the documentation, the examples may be the best way to learn Textual.

You can see some of these examples in action in the Developer Video Log.

  • animation.py Demonstration of 60fps animation easing function
  • calculator.py A "clone" of the MacOS calculator using Grid layout
  • code_viewer.py A demonstration of a tree view which loads syntax highlighted code
  • grid.py A simple demonstration of adding widgets in a Grid layout
  • grid_auto.py A demonstration of automatic Grid layout
  • simple.py A very simple Textual app with scrolling Markdown view

Building Textual applications

This guide is a work in progress

Let's look at the simplest Textual app which does something:

from textual.app import App


class Beeper(App):
    async def on_key(self, event):
        self.console.bell()


Beeper.run()

Here we can see a textual app with a single on_key method which will receive key events. Any key event will result in playing the terminal bell (which will generally emit an irritating beep). Hit Ctrl+C to exit.

Event handlers in Textual are defined by convention, not by inheritance (so you won't find an on_key method in the base class). Each event has a name attribute which for the key event is simply "key". Textual will call the method named on_ if it exists.

Let's look at a slightly more interesting example:

from textual.app import App


class ColorChanger(App):
    async def on_key(self, event):
        if event.key.isdigit():
            self.background = f"on color({event.key})"


ColorChanger.run(log="textual.log")

This example also handles key events, and will set App.background if the key is a digit. So pressing the keys 0 to 9 will change the background color to the corresponding ansi color.

Note that we didn't need to explicitly refresh the screen or draw anything. Setting the background attribute to a Rich style is enough for Textual to update the visuals. This is an example of reactivity in Textual. To make changes to the terminal interface you modify the state and let Textual update the UI.

Widgets

To make more interesting apps you will need to make use of widgets, which are independent user interface elements. Textual comes with a (growing) library of widgets, but you can develop your own.

Let's look at an app which contains widgets. We will be using the built in Placeholder widget which you can use to design application layouts before you implement the real content.

from textual import events
from textual.app import App
from textual.widgets import Placeholder


class SimpleApp(App):

    async def on_mount(self, event: events.Mount) -> None:
        await self.view.dock(Placeholder(), edge="left", size=40)
        await self.view.dock(Placeholder(), Placeholder(), edge="top")


SimpleApp.run(log="textual.log")

This app contains a single event handler on_mount. The mount event is sent when the app or widget is ready to start processing events, and is typically used for initialization. In this case we are going to call self.view.dock to add widgets to the interface.

Here's the first line in the mount handler:

await self.view.dock(Placeholder(), edge="left", size=40)

Note this method is asynchronous like almost all API methods in Textual. We are awaiting self.view.dock which takes a newly constructed Placeholder widget, and docks it on to the "left" edge of the terminal with a size of 40 characters. In a real app you might use this to display a side-bar.

The following line is similar:

await self.view.dock(Placeholder(), Placeholder(), edge="top")

You will notice that this time we are docking two Placeholder objects onto the "top" edge. We haven't set an explicit size this time so Textual will divide the remaining size amongst the two new widgets.

The last line calls the run class method in the usual way, but with an argument we haven't seen before: log="textual.log" tells Textual to write log information to the given file. You can tail textual.log to see events being processed and other debug information.

If you run the above example, you will see something like the following:

widgets

If you move the mouse over the terminal you will notice that widgets receive mouse events. You can click any of the placeholders to give it input focus.

The dock layout feature is very flexible, but for more sophisticated layouts we can use the grid API. See the calculator.py example which makes use of Grid.

Creating Widgets

You can create your own widgets by subclassing the textual.widget.Widget class and implementing a render() method which should return anything that can be rendered with Rich, including a plain string which will be interpreted as console markup.

Let's look at an example with a custom widget:

None: self.mouse_over = True async def on_leave(self, event: events.Leave) -> None: self.mouse_over = False class HoverApp(App): """Hover widget demonstration.""" async def on_mount(self, event: events.Mount) -> None: hovers = (Hover() for _ in range(10)) await self.view.dock(*hovers, edge="top") HoverApp.run(log="textual.log") ">
from rich.panel import Panel

from textual import events
from textual.app import App
from textual.reactive import Reactive
from textual.widget import Widget


class Hover(Widget):

    mouse_over: Reactive[bool] = Reactive(False)

    def render(self) -> Panel:
        return Panel("Hello [b]World[/b]", style=("on red" if self.mouse_over else ""))

    async def on_enter(self, event: events.Enter) -> None:
        self.mouse_over = True

    async def on_leave(self, event: events.Leave) -> None:
        self.mouse_over = False


class HoverApp(App):
    """Hover widget demonstration."""

    async def on_mount(self, event: events.Mount) -> None:
        hovers = (Hover() for _ in range(10))
        await self.view.dock(*hovers, edge="top")


HoverApp.run(log="textual.log")

The Hover class is a custom widget which displays a panel containing the classic text "Hello World". The first line in the Hover class may seem a little mysterious at this point:

mouse_over: Reactive[bool] = Reactive(False)

This adds a mouse_over attribute to your class which is a bool with a default of False. The typing part (Reactive[bool]) is not required, but will help you find bugs if you are using a tool like Mypy. Adding attributes like this makes them reactive, and any changes will result in the widget updating.

The following render() method is where you define how the widget should be displayed. In the Hover widget we return a Panel containing rich text with a background that changes depending on the value of mouse_over. The goal here is to add a mouse hover effect to the widget, which we can achieve by handling two events: Enter and Leave. These events are sent when the mouse enters or leaves the widget.

Here are the two event handlers again:

    async def on_enter(self, event: events.Enter) -> None:
        self.mouse_over = True

    async def on_leave(self, event: events.Leave) -> None:
        self.mouse_over = False

Both event handlers set the mouse_over attribute which, because it is reactive, will result in the widget's render() method being called.

The app class has a Mount handler where we dock 10 of these custom widgets from the top edge, stacking them vertically. If you run this script you will see something like the following:

widgets

If you move your mouse over the terminal you should see that the widget under the mouse cursor changes to a red background.

Actions and key bindings

Actions in Textual are white-listed functions that may be bound to keys. Let's look at a trivial example of binding a key to an action. Here is an app which exits when we hit the Q key:

from textual.app import App


class Quitter(App):
    async def on_load(self, event):
        await self.bind("q", "quit")


Quitter.run()

If you run this you will get a blank terminal which will return to the prompt when you press Q.

Binding is done in the Load event handler. The bind method takes the key (in this case "q") and binds it to an action ("quit"). The quit action is built in to Textual and simply exits the app.

To define your own actions, add a method that begins with action_, which may take parameters. Let's create a simple action that changes the color of the terminal and binds keys to it:

None: self.background = f"on {color}" Colorizer.run() ">
from textual.app import App


class Colorizer(App):

    async def on_load(self, event):
        await self.bind("r", "color('red')")
        await self.bind("g", "color('green')")
        await self.bind("b", "color('blue')")

    async def action_color(self, color:str) -> None:
        self.background = f"on {color}"


Colorizer.run()

If you run this app you can hit the keys R, G, or B to change the color of the background.

In the on_load method we have bound the keys R, G, and B to the color action with a single parameter. When you press any of these three keys Textual will call the method action_color with the appropriate parameter.

You could be forgiven for thinking that "color('red')" is Python code which Textual evaluates. This is not the case. The action strings are parsed and may not include expressions or arbitrary code. The reason that strings are used over a callable is that (in a future update) key bindings may be loaded from a configuration file.

More on Events

TODO

Watchers

TODO

Animation

TODO

Timers and Intervals

Textual has a set_timer and a set_interval method which work much like their Javascript counterparts. The set_timer method will invoke a callable after a given period of time, and set_interval will invoke a callable repeatedly. Unlike Javascript, these methods expect the time to be in seconds, not milliseconds.

Let's create a simple terminal based clock with the set_interval method:

from datetime import datetime

from rich.align import Align

from textual.app import App
from textual.widget import Widget


class Clock(Widget):
    async def on_mount(self, event):
        self.set_interval(1, callback=self.refresh)

    def render(self) -> Align:
        time = datetime.now().strftime("%X")
        return Align.center(time, vertical="middle")

class ClockApp(App):
    async def on_mount(self, event):
        await self.view.dock(Clock())


ClockApp.run()

If you run this app you will see the current time in the center of the terminal until you hit Ctrl+C.

The Clock widget displays the time using rich.align.Align to position it in the center. In the clock's Mount handler there is the following call to set_interval:

self.set_interval(1, callback=self.refresh)

This tells Textual to call a function (in this case self.refresh which updates the widget) once a second. When a widget is refreshed it calls Clock.render again to display the latest time.

Developer Video Log

Since Textual is a visual medium, I'll be documenting new features and milestones here.

Update 1 - Basic scrolling

Textual update 1

Update 2 - Keyboard toggle

Textual update 2

Update 3 - New scrollbars and smooth scrolling

Textual update 3

Update 4 - Animation system with easing function

Now with a system to animate changes to values, going from the initial to the final value in small increments over time . Here applied to the scroll position. The animation system supports CSS like easing functions. You may be able to tell from the video that the page up / down keys cause the window to first speed up and then slow down.

Textual update 4

Update 5 - New Layout system

A new update system allows for overlapping layers. Animation is now synchronized with the display which makes it very smooth!

Textual update 5

Update 6 - New Layout API

New version (0.1.4) with API updates and the new layout system.

Textual update 6

Update 7 - New Grid Layout

11 July 2021

Added a new layout system modelled on CSS grid. The example demonstrates how once created a grid will adapt to the available space.

Textual update 7

Update 8 - Tree control and scroll views

6 Aug 2021

Added a tree control and refactored the renderer to allow for widgets within a scrollable view

Textual update 8

Issues
  • Add remaining easing functions

    Add remaining easing functions

    Fixes #81 , does the same work as #86 ; I chose to open the PR either way because the work had already been done.

    opened by RojerGS 18
  • error when running examples

    error when running examples

    i installed textual with pip and i tried to run the first example in the readme with python3 filename.py but i get the error TypeError: on_key() takes 1 positional argument but 2 were given when i press a key. got a similiar error with another example. sorry if im being dumb : p

    opened by itsUrcute 16
  • Add more easing functions

    Add more easing functions

    There are a number of 'easing functions' used by the animation system. At the top of _animator.py you will see the following:

    EASING = {
        "none": lambda x: 1.0,
        "round": lambda x: 0.0 if x < 0.5 else 1.0,
        "linear": lambda x: x,
        "in_cubic": lambda x: x * x * x,
        "in_out_cubic": lambda x: 4 * x * x * x if x < 0.5 else 1 - pow(-2 * x + 2, 3) / 2,
        "out_cubic": lambda x: 1 - pow(1 - x, 3),
    }
    

    These function have been copied from https://easings.net/ and translated from Javascript to Python. I would like to add the remaining functions to the EASING dict above.

    See the animation.py example as a way of testing the above functions.

    help wanted good first issue 
    opened by willmcgugan 15
  • Horizontal scrolling

    Horizontal scrolling

    (hope you don't mind me opening an issue to ask a question, I've tried hard to get to the bottom of this myself)

    I'm trying to get horizontal scrolling working with textual and a rich table, but no luck

    Below is what I have so far.

    It seems like the table is refusing to exceed the width of the terminal, thus the horizontal scroll bar of ScrollView is not coming into play.

    Vertical scrolling is working great.

    What am I doing wrong?

    (ref: https://twitter.com/samuel_colvin/status/1426289617632960515)

    from rich.table import Table
    
    from textual import events
    from textual.app import App
    from textual.widgets import Header, Footer, ScrollView
    
    
    class MyApp(App):
        """An example of a very simple Textual App"""
    
        async def on_load(self, event: events.Load) -> None:
            await self.bind("q", "quit", "Quit")
    
        async def on_mount(self, event: events.Mount) -> None:
    
            self.body = body = ScrollView()
            body.virtual_size.width = 300
    
            await self.view.dock(body)
    
            async def add_content():
                table = Table(title="Demo", width=300, min_width=300)
    
                for i in range(40):
                    table.add_column(f'Col {i + 1}', style='magenta')
    
                for i in range(40):
                    table.add_row(*[f'cell {i},{j}' for j in range(40)])
    
                await body.update(table)
    
            await self.call_later(add_content)
    
    
    MyApp.run(title="Simple App", log="textual.log")
    

    Versions:

    python 3.9.5
    textual==0.1.10
    rich==10.7.0
    OS: Ubuntu 21.04 with standard terminal
    
    bug 
    opened by samuelcolvin 9
  • Typing error with Reactive

    Typing error with Reactive

    PyCharm gives me typing error with Reactive, see screenshot:

    image

    I'm unsure why this is the case given that mypy doesn't yield such error.

    Side question: now that I have found myself a project to use Textual while fully understanding that this is WIP, I'll likely come across many little things like this one you may already be aware of. Should I keep opening issues? Or should I use some other, less formal avenue to keep the noise low and only open ticket when you request it? I'm thinking of Twitter DMs, or possibly some discord server and/or DM. Let me know what works best for you at this stage.

    opened by abey79 7
  • Remove unreachable code

    Remove unreachable code

    Because there is a return preceding the yield it isn't possible to reach the yield

    opened by sanders41 6
  • asyncio event loop isn't closed on exit

    asyncio event loop isn't closed on exit

    When run with Python 3.8.5 on Ubuntu 20.04 LTS, the event loop isn't closed when the example is quit by pressing q.

    Following error is output and the program exists:

    /usr/lib/python3.8/asyncio/base_events:654: ResourceWarning: unclosed event loop <_UnixSelectorEventLoop running=False closed=False debug=False>
    

    The terminal is then printing raw bytes (?) of the mouse movement anytime you move the cursor over the window.

    image

    I'm not sure if it's the Python version or some difference between Linux and OSX.

    (In order to run the example I had to patch src/textual/_parser.py, because the type hint list[str] isn't supported in 3.8. I just replaced it with list.)

    opened by sinus-x 5
  • Dynamically adding placeholders

    Dynamically adding placeholders

    I was trying to add e.g. a new row to a layout after it was created in on_startup(), but it looks like it has no effect (I tried refresh() and require_layout()). Just asking as I know it might be early for that. Keep up the great work anyway!

    opened by davidbrochart 5
  • Scrolling and hitting Q at the same time in textual.app hung the script entirely

    Scrolling and hitting Q at the same time in textual.app hung the script entirely

    See attached video - if I hit Q while scrolling up and down with the trackpad I've occasionally managed to cause the terminal app to hang - such that it no longer responds to keyboard commands or scrolling, plus hitting Ctrl+C or even Ctrl+Z fails to exit it.

    I recorded a video here with software that shows the keys I'm pressing:

    https://user-images.githubusercontent.com/9599/122653493-bfe7b680-d0f9-11eb-9b71-c76fe32d10a8.mov

    bug 
    opened by simonw 5
  • 0.1.9 build failing with Poetry.

    0.1.9 build failing with Poetry.

    Going to admit this is my first time using Poetry so I'm not too familiar with it yet, but it appears to not like the Rich package source that's provided. No idea why. Going to try a manual install, if that fails, I'll try the regular Rich repo and hope nothings broken :)

    $ poetry install
    Installing dependencies from lock file
    
    Package operations: 1 install, 0 updates, 0 removals
    
      • Installing rich (10.6.0 8ae1d4a): Failed
    
      CalledProcessError
    
      Command '['git', 'clone', '--recurse-submodules', '[email protected]:willmcgugan/rich', '/home/hyoon/.local/share/virtualenvs/textual-4a5vNUb8/src/rich']' returned non-zero exit status 128.
    
      at ~/.local/lib/python3.6/site-packages/poetry/utils/_compat.py:218 in run
          214│                 raise
          215│             retcode = process.poll()
          216│             if check and retcode:
          217│                 raise CalledProcessError(
        → 218│                     retcode, process.args, output=stdout, stderr=stderr
          219│                 )
          220│         finally:
          221│             # None because our context manager __exit__ does not use them.
          222│             process.__exit__(None, None, None)
    
    === test session starts ===
    platform linux (ubuntu 18) -- Python 3.7.11, pytest-6.2.4, py-1.10.0, pluggy-0.13.1 -- /home/hyoon/.local/share/virtualenvs/textual-4a5vNUb8/bin/python
    cachedir: .pytest_cache
    ...
    Coverage.py warning: Module textual was never imported. (module-not-imported)
    
    (because of the build fail)
    

    Will report back,.

    opened by ethixkr 4
  • Replace set comprehention with `set()`

    Replace set comprehention with `set()`

    The comprehension here is unnecessary

    opened by tusharsadhwani 0
  • Added some information about grid making

    Added some information about grid making

    null

    opened by Shirobachi 0
  • No project description on pypi.org

    No project description on pypi.org

    opened by schneiderfelipe 0
  • Make header symbol configurable

    Make header symbol configurable

    Make it possible to change the symbol shown in the left side of the Header widget.

    Let me know if there are any other changes I can make, I'm happy to help!

    opened by Danmou 0
  • Make footer style configurable

    Make footer style configurable

    Simple change to allow using a different style for the Footer widget.

    opened by Danmou 0
  • Allow binding of the space key

    Allow binding of the space key

    Allow the space key to be bound to the application.

    opened by pvmm 0
  • Added 'title' attribute to Placeholder class

    Added 'title' attribute to Placeholder class

    A new attribute which allows one to change the title of a placeholder independently of the internal name of the window or the class.

    This introduces a feature I felt was lacking, so I implemented it myself.

    Example: await self.view.dock(Placeholder(title='Output'), Placeholder(title='Data'), edge="top")

    image

    The results can be seen in the image above.

    opened by alshapton 0
  • result = App.run()

    result = App.run()

    To be added by @willmcgugan

    1. Thanks for such a great library
    2. We are using your library! See the result here: https://github.com/fluidattacks/makes/pull/725
    3. This is not an outstanding feature that must be added
    4. But It would be very useful in some cases

    The App class is normally used as a whole application: App.run()

    It would be useful if we could return data from it on exit so we can do result = App.run(). This allows for having many 'scenes' (Apps) in our application, for instance for asking user for a value and then do some work, then show other 'scene' (other App), etc

    enhancement 
    opened by kamadorueda 2
  • Update README.md to fix link for Grid example

    Update README.md to fix link for Grid example

    The URL for the Grid layout example was pointing to the calculator example. URL has been updated to link to grid.py.

    opened by jpecor 0
  • Text Input Widget

    Text Input Widget

    To be added by @willmcgugan

    Sorry if this is already a thing, or goes against the general philosophy of the project.

    I have a need for an input widget a user could type text into. My use case is to search through a long list of items such as a directory tree.

    One of the bigger hurdles here would be keys that are already bound to action. There doesn't seem to be a way to "unbind" a key, or "unbind" all keys temporarally when the correct input panel is selected.

    Thoughts?

    opened by CyberSmash 4
Releases(v0.1.11)
  • v0.1.11(Sep 12, 2021)

    [0.1.11] - 2021-09-12

    Changed

    • Changed message handlers to use prefix handle_
    • Renamed messages to drop the Message suffix
    • Events now bubble by default
    • Refactor of layout

    Added

    • Added App.measure
    • Added auto_width to Vertical Layout, WindowView, an ScrollView
    • Added big_table.py example
    • Added easing.py example
    Source code(tar.gz)
    Source code(zip)
  • v0.1.10(Aug 25, 2021)

    [0.1.10] - 2021-08-25

    Added

    • Added keyboard control of tree control
    • Added Widget.gutter to calculate space between renderable and outside edge
    • Added margin, padding, and border attributes to Widget

    Changed

    • Callbacks may be async or non-async.
    • Event handler event argument is optional.
    • Fixed exception in clock example https://github.com/willmcgugan/textual/issues/52
    • Added Message.wait() which waits for a message to be processed
    • Key events are now sent to widgets first, before processing bindings
    Source code(tar.gz)
    Source code(zip)
  • v0.1.3(Jul 5, 2021)

    In this version there is a new more-powerful renderer that supports overlapping regions. There is also a new layout engine which is more intuitive that Rich's Layout clas.

    Source code(tar.gz)
    Source code(zip)
  • v0.1.2(Jun 24, 2021)

Owner
Will McGugan
I'm a full-stack software developer, and Python expert. Creator of Rich and @PyFilesystem.
Will McGugan
Rich.tui is a TUI (Text User Interface) framework for Python using Rich as a renderer.

rich.tui Rich.tui is a TUI (Text User Interface) framework for Python using Rich as a renderer. The end goal is to be able to rapidly create rich term

Will McGugan 5.9k Oct 21, 2021
A simple yet powerful TUI framework for your Python (3.7+) applications

A simple yet powerful TUI framework for your Python (3.7+) applications

null 170 Oct 14, 2021
Turn (almost) any Python command line program into a full GUI application with one line

Gooey Turn (almost) any Python 2 or 3 Console Program into a GUI application with one line Support this project Table of Contents Gooey Table of conte

Chris 15k Oct 22, 2021
Edifice: a declarative GUI library for Python

Edifice is a Python library for building reactive UI, inspired by modern Javascript libraries such as React.

David Ding 77 Oct 9, 2021
A library for building modern declarative desktop applications in WX.

re-wx is a library for building modern declarative desktop applications. It's built as a management layer on top of WXPython, which means you get all the goodness of a mature, native, cross-platform UI kit, wrapped up in a modern, React inspired API.

Chris 92 Oct 12, 2021
A little Python library for making simple Electron-like HTML/JS GUI apps

Eel Eel is a little Python library for making simple Electron-like offline HTML/JS GUI apps, with full access to Python capabilities and libraries. Ee

Chris Knott 4.6k Oct 22, 2021
Web-Broswer simple using PyQt5 tools

Web-Broswer Simple web broswer made using PyQt Completely simple and easy to use How to set it up git clone https://github.com/AsjadOooO/Web-Broswer.g

Asjad 1 Oct 19, 2021
Dress up your code with a beautiful graphical user interface !

Dresscode Dress up your code with a beautiful graphical user interface ! This project is part of the Pyrustic Ecosystem. Look powered by the cyberpunk

null 22 Sep 2, 2021
Write desktop and web apps in pure Python

Flexx Want to stay up-to-date about (changes to) Flexx? Subscribe to the NEWS issue. Introduction Flexx is a pure Python toolkit for creating graphica

flexxui 2.8k Oct 14, 2021
Write desktop and web apps in pure Python

Flexx Want to stay up-to-date about (changes to) Flexx? Subscribe to the NEWS issue. Introduction Flexx is a pure Python toolkit for creating graphica

flexxui 2.8k Oct 15, 2021
A keyboard-driven, vim-like browser based on PyQt5.

qutebrowser is a keyboard-focused browser with a minimal GUI. It’s based on Python and PyQt5 and free software, licensed under the GPL.

qutebrowser 7.3k Oct 23, 2021
pyglet is a cross-platform windowing and multimedia library for Python, for developing games and other visually rich applications.

pyglet pyglet is a cross-platform windowing and multimedia library for Python, intended for developing games and other visually rich applications. It

null 950 Oct 22, 2021
A lightweight file-copying interface.

autosort A lightweight file-copying interface. Preview What is autosort? Autosort is a lightweight file-copying interface. It allows you to copy sever

null 29 Jun 17, 2021
HDLG is a modern cross-platform GUI for hdl-dump with Batch installation capabilities.

HDLG is a modern cross-platform GUI for hdl-dump with Batch installation capabilities. Looking for Artwork This project is looking for an Icon an

null 2 Oct 16, 2021
Browser - A GTK browser trying to follow the GNOME Human Interface Guidelines.

A simple GTK browser trying to follow the GNOME Human Interface Guidelines.

Cleo Menezes 12 Sep 9, 2021
Uma interfáce de usuário relativamente simples em pyqt5 + escolha de dispositivos

Interface para Scrcpy Uma interfáce de usuário relativamente simples em pyqt5 para sistemas UNIX Requerimentos: Python3 PyQt5 adb scrcpy Você pode ins

hayukimori 6 Oct 17, 2021
Yasb is a highly configurable and hackable taskbar written in python with Qt6.

Yasb: Yet Another Status Bar Yasb is a highly configurable and hackable taskbar written in python with Qt6. This project is still in (very) early deve

Dan 1 Oct 24, 2021
Dear PyGui: A fast and powerful Graphical User Interface Toolkit for Python with minimal dependencies

(This library is available under a free and permissive license however, if you Enjoy Dear PyGui please consider becoming a Sponsor) Dear PyGui is a si

Jonathan Hoffstadt 6.2k Oct 22, 2021