.yaml)Create the configuration file at ~/.config/twidgets/widgets/custom.yaml
Naming schemes are described here.
You can create an infinite number of widgets, the file namescustom.yamlandcustom_widget.pyare just examples.
Configure name, emoji_title, title enabled, interval, height, width, y, x and z.
For simple widgets, set interval = 0 (see Configuration Guide)
.py)Create the Python file for the widget at ~/.config/twidgets/py_widgets/custom_widget.py
Naming schemes are described here.
You can create an infinite number of widgets, the file namescustom.yamlandcustom_widget.pyare just examples.
Note that the built-in widgets are located in your python installation, at
(python_installation_path)/lib/(python-version)/site-packages/twidgets/widgets/*_widget.py
Import:
from twidgets.core.base import Widget, WidgetContainer, Config, CursesWindowType
Then define a draw function:
def draw(widget: Widget, widget_container: WidgetContainer) -> None:
Start the function with:
draw_widget(widget, widget_container)
which will initialise the widget title and make it loadable and highlightable.
Add content with:
content: list[str] = ['line1', 'line2', 'line3', 'line4', 'line5']
widget.add_widget_content(content)
Advanced: For precise text positioning or colours in a terminal widget use
safe_addstr
from twidgets.core.base import CursesColors
y: int = 3
x: int = 2
text: str = 'Example text'
widget.safe_addstr(y, x, text, [widget_container.base_config.PRIMARY_PAIR_NUMBER], [CursesColors.BOLD])
If your widget requires heavy loading, API calls or the data does not need to be reloaded every frame, move the update logic into its own function:
Import if needed:
import typing
def update(widget: Widget, widget_container: WidgetContainer) -> list[str]:
Note that
widgetandwidget_containerwill always be passed to your update function, so make sure to keep these arguments, even if they are unused.
Additionally, modify the draw function to accept info.
(info will be passed automatically from the update function by the scheduler):
On top of that, if the
updatefunction is intended to return[]and, ex. only manipulatewidget.internal_data(see preserving data), the widget will stay in the loading state. To overcome this, make sure you never return[]. Instead, return['Success']or similar.
def draw(widget: Widget, widget_container: WidgetContainer, info: list[str]) -> None:
Example:
def draw(widget: Widget, widget_container: WidgetContainer, info: list[str]) -> None:
draw_widget(widget, widget_container)
widget.add_widget_content(info)
You can adapt the time, when the update function will be called again (reloading the data) by changing
interval in ~/.config/twidgets/widgets/custom.yaml
To integrate this, see building widget.
Example:
def mouse_click_action(widget: Widget, mx: int, my: int, b_state: int, widget_container: WidgetContainer) -> None:
# Click relative to widget border
local_y: int = my - widget.dimensions.y - 1 # -1 for top border
This function will get called whenever a mouse click happens (in your widget), so you can use it, for example, to make clickable buttons.
Note that the widget border colour will automatically be updated on every mouse click, without utilising your
mouse_click_actionfunction.
Example:
from twidgets.core.base import CursesKeys
def keyboard_press_action(widget: Widget, key: int, widget_container: WidgetContainer) -> None:
if key in (CursesKeys.ENTER, 10, 13): # Enter key + enter key codes
confirm = widget.prompt_user_input('Confirm deletion (y): ')
if confirm.lower().strip() in ['y']:
some_func(widget, ...)
This function will get called whenever a key is pressed while your widget is highlighted.
Example:
def init(widget: Widget, widget_container: WidgetContainer) -> None:
load_todos(widget) # Custom initialising logic, eg. loading todos
This function will get called initially when twidgets starts or when the user manually reloads it.
Example:
def draw_help(widget: Widget, widget_container: WidgetContainer) -> None:
draw_widget(widget, widget_container)
widget.add_widget_content(
[
f'Help page ({widget.name} widget)',
'',
'Displays information about something.'
]
)
This function will get called whenever the help key (default: h) is pressed for your widget.
To integrate any custom function, see building widget.
Import if needed:
import typing
Inside your update function:
def update(widget: Widget, widget_container: WidgetContainer) -> typing.Any:
You can then use:
data: typing.Any = widget_container.config_loader.get_secret(key)
to retrieve secrets.
Example:
def update(widget: Widget, widget_container: WidgetContainer) -> typing.Any:
api_key: str = widget_container.config_loader.get_secret('WEATHER_API_KEY')
Note that this can only be used in the
updatefunction, so secrets do not get reloaded every frame.
Example:
Python:
custom_attribute: typing.Any = widget.config.custom_attribute
YAML:
custom_attribute: 'this is a custom attribute!'
Note that this will not be checked by the ConfigScanner. It only checks
base.yamlfor integrity, as well asname,emoji_title,title,enabled,interval,height,width,y,xandzfor every widget.To detect if these attributes are missing, see the next section.
Example:
from twidgets.core.base import (
ConfigSpecificException,
LogMessages,
LogMessage,
LogLevels
)
def draw(widget: Widget, widget_container: WidgetContainer) -> None:
if not widget.config.some_value: # Will be None if no attribute is found
raise ConfigSpecificException(LogMessages([LogMessage(
f'Configuration for some_value is missing / incorrect ("{widget.name}" widget)',
LogLevels.ERROR.key)]))
With this you can add custom error messages to your widget, for example if certain attributes are missing.
widget.draw_data: list[str] = []: Data used by twidgets.core internally; a list that holds the value of the return result of the last update function call. If you don’t define any update function, this stays an empty list. Never read or modify this directly.
widget.error_data: dict[str, typing.Any] = {}: Data used by twidgets.core internally; a dictionary that holds values of errors that occured. Never read or modify this directly.
widget.internal_data: dict[typing.Any, typing.Any] = {}: Internal data stored by widgets; This is what you, as a developer, can use to save & preserve data over a longer period of time. Ex. in the news widget, this holds all new entries for later operationns.
If your widget has an update, mouse_click_action, keyboard_press_action, init or a draw_help function,
specify them here. (See the comments for examples)
def build(stdscr: CursesWindowType, config: Config) -> Widget:
return Widget(
config.name, config.title, config, draw, config.interval, config.dimensions, stdscr, # exactly this order!
update_func=None, # update_func=update
mouse_click_func=None, # mouse_click_func=mouse_click_action
keyboard_func=None, # keyboard_func=keyboard_press_action
init_func=None, # init_func=init
help_func=None # help_func=draw_help
)
While integration is automatic, your files must still follow a specific naming convention for the system to recognise them as a valid widget:
~/.config/twidgets/widgets/):
.yamlhello123.yaml, mycoolwidget.yaml, weather.yaml~/.config/twidgets/py_widgets/):
_widget.pyhello123_widget.py, mycoolwidget_widget.py, weather_widget.pyNote: Make sure to name the
.yamland.pyfiles the same way (excluding suffixes)