.yaml)Create the configuration file at ~/.config/twidgets/widgets/custom.yaml
Naming schemes are described here.
You can create an infinite amount of widgets, the file namescustom.yamlandcustom_widget.pyare just examples.
Configure name, title, enabled, interval, height, width, y and x.
For simple widgets, set interval = 0 (see Configuration Guide)
.py)Create the widget’s Python file at ~/.config/twidgets/py_widgets/custom_widget.py
Naming schemes are described here.
You can create an infinite amount 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, draw_widget, add_widget_content, Config, UIState, BaseConfig, CursesWindowType
Then define a draw function:
def draw(widget: Widget, ui_state: UIState, base_config: BaseConfig) -> None:
Start the function with:
draw_widget(widget, ui_state, base_config)
which will initialize the widget title and make it loadable and highlightable.
Add content with:
content: list[str] = ['line1', 'line2', 'line3', 'line4', 'line5']
add_widget_content(widget, content)
Advanced: For precise text positioning or colors in a terminal widget use
safe_addstr
from twidgets.core.base import (
safe_addstr,
convert_color_number_to_curses_pair,
CursesBold
)
row: int = 3
col: int = 2
text: str = 'Example text'
safe_addstr(
widget, row, col, text,
convert_color_number_to_curses_pair(base_config.PRIMARY_PAIR_NUMBER) | CursesBold)
If your widget requires heavy loading, API calls or the data doesn’t need to be reloaded every frame, move the update logic into its own function:
from twidgets.core.base import ConfigLoader
import typing
def update(_widget: Widget, _config_loader: ConfigLoader) -> typing.Any:
Note that
widgetandconfig_loaderwill always be passed to your update function, so make sure to keep those arguments.
And modify the draw function to accept info.
(info will be passed automatically from the update function by the scheduler):
def draw(widget: Widget, ui_state: UIState, base_config: BaseConfig, info: typing.Any) -> None:
Example:
def draw(widget: Widget, ui_state: UIState, base_config: BaseConfig, info: list[str]) -> None:
draw_widget(widget, ui_state, base_config)
add_widget_content(widget, 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, ui_state: UIState) -> 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 to for example make clickable buttons.
Note that the widget border color will automatically be updated on every mouse click, without utilising your
mouse_click_actionfunction.
Example:
from twidgets.core.base import prompt_user_input, CursesKeys
def keyboard_press_action(widget: Widget, key: typing.Any, ui_state: UIState, base_config: BaseConfig) -> None:
if key in (CursesKeys.ENTER, 10, 13): # Enter key + enter key codes
confirm = prompt_user_input(widget, '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, _ui_state: UIState, _base_config: BaseConfig) -> None:
load_todos(widget)
This function will get called initially when twidgets is starting, or when the user manually reloads.
Example:
def draw_help(widget: Widget, ui_state: UIState, base_config: BaseConfig) -> None:
draw_widget(widget, ui_state, base_config)
add_widget_content(
widget,
[
f'Help page ({widget.name} widget)',
'',
'Displays information about something.'
]
)
This function will get called whenever the help key (Default: h) is getting pressed on your widget.
To integrate any custom function, see building widget.
Import:
from twidgets.core.base import ConfigLoader # Loading secrets (secrets.env)
import typing
Inside your update function:
def update(_widget: Widget, _config_loader: ConfigLoader) -> typing.Any:
You can now use:
data: typing.Any = _config_loader.get_secret(key)
to get secrets.
Example:
def update(_widget: Widget, _config_loader: ConfigLoader) -> typing.Any:
api_key: str = _config_loader.get_secret('WEATHER_API_KEY')
Note that this can only be used in the
updatefunction, so secrets don’t 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,title,enabled,interval,height,width,yandxfor 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, ui_state: UIState, base_config: BaseConfig) -> 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.
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 recognize 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)