A common question that comes up on the Textual Discord server is what is the difference between render and compose methods on a widget?
In this article we will clarify the differences, and use both these methods to build something fun.
Render and compose are easy to confuse because they both ultimately define what a widget will look like, but they have quite different uses.
The render method on a widget returns a Rich renderable, which is anything you could print with Rich.
The simplest renderable is just text; so render() methods often return a string, but could equally return a Text instance, a Table, or anything else from Rich (or third party library).
Whatever is returned from render() will be combined with any styles from CSS and displayed within the widget's borders.
The compose method is used to build compound widgets (widgets composed of other widgets).
A general rule of thumb, is that if you implement a compose method, there is no need for a render method because it is the widgets yielded from compose which define how the custom widget will look.
However, you can mix these two methods.
If you implement both, the render method will set the custom widget's background and compose will add widgets on top of that background.
Let's look at an example that combines both these methods.
We will create a custom widget with a linear gradient as a background.
The background will be animated (I did promise fun)!
fromtimeimporttimefromtextual.appimportApp,ComposeResult,RenderResultfromtextual.containersimportContainerfromtextual.renderables.gradientimportLinearGradientfromtextual.widgetsimportStaticCOLORS=["#881177","#aa3355","#cc6666","#ee9944","#eedd00","#99dd55","#44dd88","#22ccbb","#00bbcc","#0099cc","#3366bb","#663399",]STOPS=[(i/(len(COLORS)-1),color)fori,colorinenumerate(COLORS)]classSplash(Container):"""Custom widget that extends Container."""DEFAULT_CSS=""" Splash { align: center middle; } Static { width: 40; padding: 2 4; } """defon_mount(self)->None:self.auto_refresh=1/30# (1)!defcompose(self)->ComposeResult:yieldStatic("Making a splash with Textual!")# (2)!defrender(self)->RenderResult:returnLinearGradient(time()*90,STOPS)# (3)!classSplashApp(App):"""Simple app to show our custom widget."""defcompose(self)->ComposeResult:yieldSplash()if__name__=="__main__":app=SplashApp()app.run()
Refresh the widget 30 times a second.
Compose our compound widget, which contains a single Static.
Render a linear gradient in the background.
The Splash custom widget has a compose method which adds a simple Static widget to display a message.
Additionally there is a render method which returns a renderable to fill the background with a gradient.
Tip
As fun as this is, spinning animated gradients may be too distracting for most apps!