Package: tgzr.declare
tgzr.declareComponent based UI Declaration and Rendering.For now, Qt is the only supported backend. IMGUI is planned for later.Please findinstallation instructionat the end of this page.Here is a screenshot of the component section in Qt demo app:A note ontgzrtgzr.declareis part of thetgzrcomunity powered platform but is completely independent from othertgzrpackages and you can use it without any knowlegde oftgzriteself.That being said,tgzris awesone and you should check it out ! ;)https://www.tgzr.orgSynopsisSome parts of your application willdeclareGUI usingcomponents, and parts
of you application willrenderthisdeclarationinto an actual GUI.Thedeclarationis held on an object that you can easily dump todictorjsonand re-create fromdictorjson.Using adeclarationand render your GUI gives you some nice advantages:It forces the separation of the logic and the GUI since you can not declare your logic withtgzr.declareYou can build youdeclarationin a procedural way without ever subclassing or instanciating anything yourself. This is pretty enjoyable, you should try it !Yourdeclarationhas a high abstraction level than a GUI library, giving you the opportunity to leveral and streamline the UX delivered to your users.Thedeclarationpart and therenderpart of your application may live in different services connected thru the network (REST API, Websocket, grpc, ...)Using theincludecomponent, you can build a main GUI composed of several sources (python plugins, other services in you distributed application, ...)Since can be turn into jsaon, you can store yourdeclarationin a database or in a file, and even let your users customize them !UI DeclarationIn order to declare a UI, you will use aSchema.Note:
As of today, the only supported schema is called 'Default' and can be accessed astgzr.declare.default.schema.DefaultSchema. This schema is supported by our Qt renderer:tgzr.declare.qt.renderer.QtRenderer.ASchemacontains all the components you can use with it, along with all the parameters they support. To declare a component, you just need to call it with the parameters you want to use:fromtgzr.declare.default.schemaimportDefalutSchemaassmy_button=s.Button(label='This is my button')Some component arguments may need a structure value. For example, every widget-like component of theDefaultSchemahave awidgetparameter which let you control widget properties likeenabledorvisible. To set those, you can use adict, but the easiest way is to use a dotted notation on the component declaration:fromtgzr.declare.default.schemaimportDefalutSchemaass# This can be useful programatically:widget_params=dict(enabled=False,visible=True)my_button=s.Button(label='This is my button',widget=widget_params)# This is equivalent and easier when GUI is written by hand:my_button=s.Button(label='This is my button',widget=widget_params)my_button.widget.enabled=Falsemy_button.widget.visible=TrueTip: Your IDE should provide completion for component names as well as component argument ! A great effort has been given for this while developingtgzr.declareand it is a key part in the pleasure of using it !
If you don't have completion, see how you can tweak your IDE configuration or consider migrating to visual code ;)In order to build you UI you will want to place some components inside other components. This is achieved by using components as context. Here is an example using theVBoxcomponent which lays out its children vertically:fromtgzr.declare.default.schemaimportDefalutSchemaasswiths.VBox():s.Button(label='This is my button')When your declaration is done, you will probably want to use it. Our first example was assigning theButtoncomponent to themy_buttonvariable. When using a component as context, you can specify the variable to assign it to and use it later on:fromtgzr.declare.default.schemaimportDefalutSchemaasswiths.VBox()asUI:s.Button(label='This is my button')do_something_with_my_ui(UI)Using this aproach gives you opportinities to split you declaration into separated reusable and potentially procedural functions.This example has some of the GUI skiped for non-admin users, and delegates parts of the UI declaration to some plugin:fromtgzr.declare.default.schemaimportDefaultSchemaassfrom.importadmin_toolsfrom.importbusiness_stuffdefadmin_panel(parent):ifadmin_tools.is_user_admin():s.button('Kill the server')defadd_plugin_footer():s.Button(label='Submit')defadd_plugin_tab(plugin):withs.Tab(label=plugin.name):plugin.declare_gui(layout)add_plugin_footer()defget_ui():withs.Tabs()asUI:forplugininbusiness_stuff.loaded_plugins():add_plugin_tab(parent,plugin)returnUIUI RenderingIn order to render your ui, you will use aRendererand a host.Note: As of today, the only provided renderer isQtRendererand can be accessed astgzr.declare.qt.renderer.QtRenderer. This renderer support theDefaultSchemawe've been using in previous examples.With theQtRenderer, the host can be a simpleQWidget:fromqtpyimportQtWidgetsfromtgzr.declare.qt.rendererimportQtRendererfrom.guiimportget_gui# a function returning you GUI declaration dict# Create you app and your host:app=QtWidgets.QApplication([])host=QtWidgets.QWidget(None)# Create your renderer using the host widget:renderer=QtRenderer(host)# render you ui, it will be contained in the host:renderer.render(get_gui())If you're building an application with a 100%tgzr.declareGUI, you may want to use our QtAppwhich does all this boilerplate for you. Using the@App.setupdecorator, you will receive the app'sQtRendererand have the opportunity to configure and use it:fromtgzr.declare.qt.appimportAppfrom.guiimportget_gui# a function returning you GUI declaration dictmy_app=App('My Title')@app.setupdefsetup(renderer):renderer.render(get_gui())ActionsSome components trigger actions. For example, theButtoncomponent triggers aclickedaction. You will implement your application logic by reacting to action.To do so, you must sethandlerfunctions to yourrenderer. Those function will receive many information which will help you decide what to do:# this is the required signature for an action handler:defmy_handler(renderer,key,action,context,*args,**kwargs):pass# this is how you set a handler on your renderer:renderer.set_handler(my_handler,'some_component_key','some_component_action',)/!\We're still in alpha. This signature will probably changed and drop
therendererargument.Among the arguements received by you handler, thekeyone can be used to identify the component which triggered the action. This key can be specified in the component declaration with thekeyparameter. It defaults to theIDparameter, which in turn defaults toNone.So for example, with this declaration, your handler can act differently if the receivedkeyisOkorCancel:withs.HBox():s.Stretch()s.Button(key='Ok')s.Button(key='Cancel')When setting handlers, you can optionnally specify thekeyand/or theactionto indicate what you want to receive. This is handy to dispatch actions to the appropriate handler:withs.VBox():s.Toggle(label="Allow overwrite")s.Button('save_button',label="Save")defon_toggled(renderer,key,action,context,checked):print('Toggled',key,checked)defon_clicked(renderer,key,action,context):print('clicked',key)defon_action(renderer,key,action,context,*args,**kwargs):print('Unhandled action:',key,action,args,kwargs)renderer=QtRenderer(host)# This handler will only receive `clicked` actions:renderer.set_handler(on_clicked,action='clicked')# This handler will only receive actions from the 'save_button' component:renderer.set_handler(on_clicked,key='save_button')# This handler will only receive `toggled` actions:renderer.set_handler(on_toggled,action='toggled')# This handler will receive all otherwise not handled actions:renderer.set_handler(on_action)Note: There is a default handler in therendererwhich will catch all unhandled actions and print the key and the action type. So you can start building your UI and use it to see what it triggers :)The Handle componentSometimes your handler code is only performing some UI related logic. In such case, you may want to define your handler direclty in the UI declaration. This is the purpose of theHandlecomponent. It has ascriptparameter where you can provide a string that will be executed as python code. This script will have interesting informations and functions available (inlocals), see its docstring for more details.For example, thisHandlecomponent will enable theButtononly if theTogglevalue isTrue:defadd_some_ui(schema):withschema.VBox():schema.Toggle('Confirm',action_key='confirmation')withschema.Button('Submit'):schema.Bind(property_name='enabled',state_key='button_enabled')# Initial state value:schema.State(name='button_enabled',value=False)# Handling toggle:script="set_state('button_enabled', args[0])"schema.Handler(script=script,key='confirmation')StatesTherenderercontains astate store, a dict-like object which can store values associated to a string key.Binding properties to statesWhen you need to access a value from your GUI, you willbinda component'spropertyto astatein the renderer's state store. This will ensure the state value and the property value are always syncronized.Once a property is binded, you can use therendererto get or set its value.The binding is done by using a special value for a component's parameter. This value is built usingSomeSchema.Bind(property_name, state_key), like this:my_button=schema.Button(schema.Bind(property_name='label',state_key='my_button_label'))Since this is a very common operation, there is a shorthand you may prefer:my_button=schema.Button(label='@binded:my_button_label')Reading statesOnce a property is binded, you can use therendererto get or set its value.In this example, we're using twoInputcomponents binded to two keys in the state store. The state store can retreive all keys starting with a prefix at once (we call this a namespace). We're leveraging this to turn all the input as the arguments of a callback functioncreate_user():fromtgzr.declare.default.schemaimportDefaultSchemaassfrommy_businessimportcreate_userwiths.HBox()asUI:s.Input('name',value='@binded:form/name')s.Input('email',value='@binded:form/email')s.Button('Submit')defon_submit(renderer,key,action,context):create_user_kwargs=renderer.get_states('form/'))create_user(**create_user_kwargs)renderer.set_handler(on_submit,key='Submit')renderer.render(UI)Updating StateStatesare also used to affect the GUI. If you change the value of a state, anypropertybinded to it will update:fromtgzr.declare.default.schemaimportDefaultSchemaass# Declare a disabled toggle and a disabled button with their `enabled` property# binded to the same state:withs.VBox()aslayout:withlayout.toggle(label='Overwrite',enable='@binded:form_ui/enabled')astoggle:toggle.widget.enabled=Falsewithlayout.button(label='Save',enable='@binded:form_ui/enabled')asbutton:button.widget.enabled=Falserenderer.update_states({'form_ui/enabled':False})renderer.render(layout)# the ui is disabled.........# later, this will enable both the toggle and the button:renderer.update_states({'form_ui/enabled':True})This can be use on most parameters of thecomponents. Not all are bindable, but their documentation will clearly identify the ones you can bind to states.TipFor now, the easier way to browse component arguments is theDocumentationview of theGUI Editor. See the dedicated section below...Declaring StatesYour declaration can set state value too !To do so, you can use theStatecomponent:fromtgzr.declare.default.schemaimportDefaultSchemaasswiths.VBox():s.Input(value='@binded:form/name)s.Input(value='@binded:form/email)s.State(name='form/name',value='bob')s.State(name='form/email',value='bob@bob.org')Tip: ThoseStatecomponents may be declared before or after binding to them. But declaring them before hand may reduce the number of updates on GUI startup.State namespacingIf you are using namespaced states (state keys with a"/"in them), you may want to use theStatescomponent.This is equivalent as the previous example, but the"form"namespace is written only once:fromtgzr.declare.default.schemaimportDefaultSchemaasswiths.VBox():s.Input(value='@binded:form/name)s.Input(value='@binded:form/email)withs.States(namespace='form'):s.State(name='name',value='bob')s.State(name='email',value='bob@bob.org')Using namespaced states is very usefull when you need to gather all the states you will use as arguments for a function or a REST call for example. In the example above, you would be able to callrenderer.get_states(prefix='form/')and receive a dict with both thenameandemailkeys.Structured StatesSome component parameters can accept or require a value with structured data.For example, thecolumnsproperty of anItemViewcan be a list of strings:columns=["Name","EMail","is_super_user"]or a list of columns properties:columns=[dict(Label='Name',icon='fa.user'),dict(Label='EMail',icon='fa.envelope'),dict('is_super_user',hidden=True,icon='fa.user-plus'),]Theitemsyou want to display in theItemViewis even more complex !To help you provide the appropriate data, you can use their corresponding components.
Here is an example ofItemsandColumnsyou can use on aItemView.fromtgzr.declare.default.schemaimportDefaultSchemaassfrommy_businessimportget_user_listdefdeclare_view_states():withs.States(namesapce='my_item_view'):withs.Columns('my_columns'):s.Column(label="Name",icon="fa.user")s.Column(label="EMail",icon="fa.envelope")s.Column(label="is_super_user",hidden=True,icon='fa.user-plus')withs.Items('my_users'):foruseringet_user_list():withs.Item(user.user_uid)asuser:s.Cell(column='Name',value=user.name,icon=user.icon,)s.Cell(column='EMail',value=user.email,background_color=user.email_color,)s.Cell(column='is_super_user',value=user.is_super_user,)GroupWe have seen how theStatescomponent can be used to "prefix" the state you're using in a whole branch of your UI declaration.The same can be done with theGroupcomponent. The difference is that it also acts on the componentactions!In this example, theSubmitbutton will trigger aform/Clickedaction instead of the defaultclickedaction:fromtgzr.declare.default.schemaimportDefaultSchemaasswiths.VBox()asUI:withs.Group(namespace='form'):s.Input(value='@binded:name')s.Input(value='@binded:email')s.Button(label='Submit)defon_submit(renderer,key,action,context):print('Submitting:',context.get_states('form/'))renderer.add_handler(on_submit,action='form/clicked')This is particularily useful once you start composing GUI from different source which don't want to mess with each others' states and actions.IconsWe're usingqtawesomefor all icons.Note: We might migrate to another plaform since we need icon support for other backends than qt, but we will make our best to keep the icon names compatibles and the migration frictionless.When you see aniconparameter, you must provide a validqtawesomeicon name (there's like 14k icons there, you should be covered ;))See theqtawesomedoc for more details:https://github.com/spyder-ide/qtawesome#supported-fontsWe're quite found of the font awesome solid collection 5, which you can browse here:https://fontawesome.com/v5/search?m=free&s=solidIf you installed the Qt backend (see installation instructions below), you will also have a nice icon browser available in you virtualenv with the command:$ qta-browserDemoYou can launch thedemowith:python -m tgzr.declare.qt.demoIt will showcase many components with some explainations, totally worth looking at :)You can also have a look at theplayer_appdemo application, its code can be used to explore all the concepts available in the library:python -m tgzr.declare.qt.demo.player_appGUI EditorWe have a rudimentary GUI editor. You can launch it with:python -m tgzr.declare.qt.editorYou will be presented with 4 panels, from left to right:DocumentationSelect a component in the component tree and its documentation will appear here.
Using the button at the top, you can toggle to "Source" mode to see the component source code.Declaration treeRight click to show a list of components to add.
If a component is selected, the new one will be created as a child of it.You can drag'n'drop component to reorder and reparent them.Right click a component to access the menu where you will be able to add/remove properties, etc...The ">" button at the top will load the resulting declaration to the UI view.The ">>" button will do the same + render this UI in the Render View.UIHere you can see and edit the UI as python code or as json (use the central button at the top to toggle between the available modes).Using the "<" button at the top will load the current displayed UI in the Declaration Tree.The ">" button will render the current displayed UI in the Render View.RenderThis panel renders the UI you see in the UI panel.State TreeThis is not available yet but we plan on adding a 5th panel where you'll be able to see and edit the state values in a tree.DefaultSchemaComponent ListThe lib is still in early alpha and the APIwillchange.Most notably, the component list and their name in theDefaultSchemaboth need a curation. We aim at higher level components that we have today.That being said, here is the list of components currently available inDefaultSchema, along with their parameters:Click to expand the components list- Anchor
    - name
    - trigger_state
    - effect

- Anchors
    - name

- Bind
    - property_name
    - state_key

- Button
    - label
    - icon
    - action_key
    - widget
    - widget
        - widget_class
        - enabled
        - visible
        - tooltip
        - layout
        - layout
            - stretch
            - alignment
            - separator

- Frame
    - title
    - checkable
    - checked
    - flat
    - widget
    - widget
        - widget_class
        - enabled
        - visible
        - tooltip
        - layout
        - layout
            - stretch
            - alignment
            - separator

- Group
    - namespace
    - affect_states
    - affect_actions

- H1
    - text
    - word_wrap
    - fixed_width

- H2
    - text
    - word_wrap
    - fixed_width

- H3
    - text
    - word_wrap
    - fixed_width

- H4
    - text
    - word_wrap
    - fixed_width

- HBox
    - debug
    - stretch
    - margins

- Handle
    - script
    - key
    - action

- Include
    - source_state
    - trigger_state

- Input
    - value
    - action_key
    - realtime
    - widget
    - widget
        - widget_class
        - enabled
        - visible
        - tooltip
        - layout
        - layout
            - stretch
            - alignment
            - separator

- ItemView
    - columns
    - items
    - multiple_selection
    - current_item_state
    - selected_items_state
    - group_by
    - auto_group_separator
    - auto_expand_groups
    - sortable
    - filter
    - use_filter
    - decorated_root
    - header_hidden
    - icon_size
    - widget
    - widget
        - widget_class
        - enabled
        - visible
        - tooltip
        - layout
        - layout
            - stretch
            - alignment
            - separator

- Label
    - text
    - word_wrap
    - fixed_width
    - format
    - widget
    - widget
        - widget_class
        - enabled
        - visible
        - tooltip
        - layout
        - layout
            - stretch
            - alignment
            - separator

- Layout
    - debug
    - stretch
    - margins
    - layout_class
    - orientation

- LayoutItem
    - stretch
    - alignment
    - separator

- ListState
    - name

- ListStateAppend
    - value

- Markdown
    - text
    - min_width
    - min_height
    - widget
    - widget
        - widget_class
        - enabled
        - visible
        - tooltip
        - layout
        - layout
            - stretch
            - alignment
            - separator

- Menu
    - label
    - icon
    - popup_at_cursor
    - trigger_state

- MenuAction
    - label
    - icon
    - checkable
    - checked
    - action_key
    - hovered_action_key

- Overlay
    - name
    - visible
    - enabled

- PrintContextButton

- PrintStatesButton

- Set
    - name
    - value

- Splitter
    - orientation
    - widget
    - widget
        - widget_class
        - enabled
        - visible
        - tooltip
        - layout
        - layout
            - stretch
            - alignment
            - separator

- SplitterPanel
    - layout_orientation
    - layout_stretch
    - widget
    - widget
        - widget_class
        - enabled
        - visible
        - tooltip
        - layout
        - layout
            - stretch
            - alignment
            - separator

- State
    - name
    - value

- States
    - namespace

- Stretch

- Tab
    - title
    - icon
    - layout_orientation
    - layout_stretch
    - widget
    - widget
        - widget_class
        - enabled
        - visible
        - tooltip
        - layout
        - layout
            - stretch
            - alignment
            - separator

- Tabs
    - closable
    - movable
    - current
    - widget
    - widget
        - widget_class
        - enabled
        - visible
        - tooltip
        - layout
        - layout
            - stretch
            - alignment
            - separator

- Text
    - text
    - word_wrap
    - fixed_width

- Toggle
    - label
    - value
    - action_key
    - widget
    - widget
        - widget_class
        - enabled
        - visible
        - tooltip
        - layout
        - layout
            - stretch
            - alignment
            - separator

- VBox
    - debug
    - stretch
    - margins

- Widget
    - widget_class
    - enabled
    - visible
    - tooltip
    - layout
    - layout
        - stretch
        - alignment
        - separatorThis list was generated by running:python -m tgzr.declare.default.schemaYou will have the list corresponding to your version if you run this command yourself.InstallFor DeclarationInstall the bare package if you only need to declare UIs. This install will have no dependency to any backend libraries and will be compatible with headless environments:pip install tgzr.declareNote: With this, an ImportError could be raised when importingtgzr.declare.qtand anything under it. See the 'For Rendering' section below for an installation handling declaration AND rendering.For RenderingInstall with some extra(s) requirement to specify the backend you want to use. For now,Qtis the only supported backend.If you have a Qt wrapper install by other mean, just use theqtpyextra:pip install -U tgzr.declare[qtpy]If you don't have any Qt wrapper installed, you can add thePySide2extra:pip install -U tgzr.declare[qtpy,PySide2]With these, you will be able to usetgzr.declare.qt.QtRendererto render your UI.Note: Alltgzr.declareinstallation can be used to declare some GUI, so those command actually install the declaration AND the rendering parts.