Skip to content

Quickstart

This page takes you through concepts of Quasargui.

If you haven't yet installed Quasargui, you can install Quasargui by

pip install quasargui

Now, you're ready to run our first example.

The simplest window

The simplest possible window only takes 3 lines of code.

Hello_world_window

from quasargui import *
layout = Rows(children=['This is the', '<i>Hello, world!</i>', 'window.'])
run(layout, title='The simplest', size=(200, 100))

The important parts of the code:

  1. run runs the GUI - and the program stays at this line until all the windows are closed.
  2. run always takes a Component, the main component to display. In this case the component is Rows.
  3. children can be a list of Component's and str's and some other types that we'll discuss later.
  4. The children strings accept html code, so simple formats such as <i> and <b> can be comfortably written directly. For more complex cases, use components such as Link, Heading, etc.

Let's move on to a more advanced code that also can be used as a boilerplate for your app.

Starter code

To quickly get a nice window layout, you can use the code of the window below.

Quasargui menu header boilerplate

"""
This is a starter template.
"""

import quasargui
from quasargui import *

loading = Model(True)


def set_loaded():
    loading.value = False


layout = QLayout([
    QHeader([
        QToolbar([QToolbarTitle([
            QIcon('ramen_dining', 'lg', classes='q-mx-md'),
            'Your Program Title'
        ])])
    ]),
    QDrawer([
        '<b>'
        'Your drawer'
        '</b>'
        '<div class="q-mt-md">'
        'for your parameters.'
        '</div>'
    ]),
    QDrawer(side=QDrawer.RIGHT, show=False, children=[
        '<b>'
        'Your right drawer.'
        '</b>'
        '<div class="q-mt-md">'
        'If you delete a drawer,its sandwich menu disappears'
        '</div>'
    ]),
    QPage([
        'Here comes the contents of your QPage'
    ]),
    v_show(
        loading,
        QFooter(children=[
            'Here is your footer that is only displayed if loading.value == True',
            QButton('ok', events={'click': toggle(loading)})
        ])
    )
])

quasargui.run(layout, title='Program title')

The things you need to know about this window:

QLayout, QDrawer, etc. are all Components that correspond to Vue components. In particular, we use Quasar's professional-looking components - hence the name Quasargui. All the components you see on the Quasar documentation page, are imported into Quasargui, wrapped into Component's.

The first parameter of a Component is children, so you don't have to type constantly children=.
Note that QButton's first parameter is not children but label since typically you want to add a label to a button and not children (which is also still possible of course...).

Quasargui uses Python's typing system, so if you use an IDE you can always check with typing system if you're using Quasargui correctly.

To make changes on the window, we use Model's, like loading in this example. A Model provides "two-way binding" between your code and the GUI. This means that every change to a Model's value is reflected in the GUI and every user input changes the Model's value.

Debugging

To get confident with any system, it is best to know how to debug it. With Quasargui you have an extra debugging option: if you enable debug=True in run(), you can see a browser inspector if you right-click and choose inspect. If you would lose the title bar of the window (as we observed it in Mac), you can just go to fullscreen and exit fullscreen and vois-lĂĄ, you got your titlebar back. Luckily, we haven't observed the titlebar disappear in production so far.

If you want even more detailed debugging, also set _render_debug=True in run().

debug_screen

If you enable `debug=True` and `_render_debug=True`, you get all the information you need (and more... therefore the underscore).

Vue vs. Quasargui

If you have experience with Vue, this section shows you how concepts in Vue are transferred to Quasargui.
The transfer from Vue is natural since under the hood Quasargui builds up a Vue/Quasar webpage and interacts with it through pywebview's api.

For example

ok_label = Model('ok')
Div(children=[
    'text', 
    QButton(props={'label': ok_label})
])
roughly results in
<div>
    text
    <q-button :label='data[1]' />
</div>

So, if you look into quasar's documentation, all Vue components have their Component counterpart in Quasargui, with the same name. As an example, <q-button/> corresponds to QButton.

Vue components to Component parameters

Among a Component's constructor's parameters,

  • children corresponds to html-style children.
  • props corresponds to props (eg. label="ok", :label="data[1]"),
  • events corresponds to Vue events (eg. @click)
  • classes corresponds to class attribute.
  • style corresponds to style attribute.

Additionally, we have defined some shorthand parameters for frequently used props (such as label in case of QButton).

Directives are defined as functions. v_show and other v_* functions correspond to v-* directives. Only v-model gets special treatment, it is always accessible as the model parameter of a ComponentWithModel.

Any Model corresponds to Vue data that is defined on the Vue app. There is also Computed that works similar to 'computed' in Vue.

Vue slots in Quasargui

In components, Vue's slots can be accessed as Slot('slot-name', [...children...]). Since Slot is a Component, it also has props, events, styles and classes.

Slots example
The map sign of the input field is defined in a slot.

from quasargui import *

layout = QInput(
    classes='q-ma-lg',
    label='Your city',
    children=[
        Slot('prepend', [
            QIcon('place')
        ])
    ])

run(layout, title='slots example')

Scoped slots

Scoped slots can be accessed a little-bit differently. Since scoped slots are meant as a template, in python we express them as functions, Slot('slot-name', lambda props: [... children...]). In this formula props is a PropVar that behaves just like a Model.

Scoped slots example
This example is the Quasargui variant of Quasar's tree example where items in the tree view have customized default-header and default-body.

"""
ref. https://quasar.dev/vue-components/tree#customize-content
"""

from quasargui import *

customize = Model([
    {
        'label': 'Satisfied customers',
        'header': 'root',
        'children': [
            {
                'label': 'Good food',
                'icon': 'restaurant_menu',
                'header': 'generic',
                'children': [
                    {
                        'label': 'Quality ingredients',
                        'header': 'generic',
                        'body': 'story',
                        'story': 'Lorem ipsum dolor sit amet.'
                    },
                    {
                        'label': 'Good recipe',
                        'body': 'story',
                        'story': 'A Congressman works with his equally conniving wife to exact revenge '
                                 'on the people who betrayed him.'
                    }
                ]
            },
            {
                'label': 'Good service',
                'header': 'generic',
                'body': 'toggle',
                'caption': 'Why are we as consumers so captivated by stories of great customer service? '
                           'Perhaps it is because...',
                'enabled': False,
                'children': [
                    {'label': 'Prompt attention'},
                    {'label': 'Professional waiter'}
                ]
            },
            {
                'label': 'Pleasant surroundings',
                'children': [
                    {'label': 'Happy atmosphere'},
                    {'label': 'Good table presentation', 'header': 'generic'},
                    {'label': 'Pleasing decor'}
                ]
            }
        ]
    }
])

layout = QTree(
    props={
        'nodes': customize,
        'node-key': 'label',
        'default-expand-all': True
    }, children=[
        Slot('default-header', lambda prop: [
            Div(classes='row items-center', children=[
                QIcon(
                    name=Computed(lambda ic: ic or 'share', prop['node']['icon']),
                    size='28px',
                    color='orange',
                    classes='q-mr-sm'),
                Div(classes='text-weight-bold text-primary', children=[
                    prop['node']['label']
                ])
            ])
        ]),
        Slot('default-body', lambda prop: [
            v_if(prop['node']['story'], Div([
                CustomComponent('span', classes='text-weight-bold', children=['This node has a story']),
                ': ',
                prop['node']['story']
            ])),
            v_else(
                CustomComponent('span', classes='text-weight-light text-black', children=[
                    'This is some default content.'
                ])
            )
        ])
    ])

run(layout, title='Scoped slot demo')

Model and Computed

In Quasargui Model and Computed enable Component's to change dynamically. They are the most convenient way to handle complex user interaction.
To see the interactions with Model and Computed, consider the following example:

Even or odd
If you type a number into a or b,
the window displays "is even" or "is odd",
depending on the parity of a+b

from quasargui import *

a = Model(0)
b = Model(0)
even = Computed(lambda x, y: (x+y) % 2 == 0, a, b)
odd = Computed(lambda x: not x, even)

layout = Rows([
    QInput('a', model=a),
    '+',
    QInput('b', model=b),
    v_if(even, Div(['is even'])),
    v_if(odd, Div(['is odd'])),
])

run(layout)

In the example above even and odd are computed from the value of a and b. Although a QInput's model value is usually str, in this example it is automatically converted to int, since a and b has initial value of type int.

In general, a Model accepts any combination of basic python types (including list and dict). If you want to use other python classes, you'll need to write your custom logic to convert a Model's value into json-compatible values.

To access the value of a Model or a Computed, use the .value property. It is a read/write property for Model's and read-only for Computed. If you want to be more explicit, you can also use .set_value().

Watching changes

If you want to execute code when a Model gets a certain value, you just call add_callback on that Model.

Accept_conditions
When the conditions are accepted, a notification pops up.

from quasargui import *
accepted = Model(False)
layout = InputBool("Accept conditions", accepted)


def notify_if_accepted():
    if accepted.value:
        accepted.api.plugins.notify('Thanks for accepting!')


accepted.add_callback(notify_if_accepted)
run(layout)

You can add a callback anytime of course, even after a component is added to the window.

Structured Models

Models can hold int, str, bool values, and also nested list's and dict's. If a model is a dict, it can be accessed just like a normal variable:

deep_model = Model({'deep': {'data': 'deep value'}})
data_model = deep_model['deep']['data']  # is also a Model
data_model.value = 'new value'
assert deep_model.value['deep']['data'] == 'new value'  # True!
list_model = Model(['a', 'b', 'c'])
list_model[1].value == 'b'
list_model[1].value = 'new'
assert list_model.value == ['a', 'new', 'c']  # True!
Keep in mind that you can access any depth of items of a Model, with the item accessor, and it is going to be a Model that refers to the same value as the original model. Don't do your_model.value['item'] == 'new value' since it will not dynamically set the value in the GUI, and you'll have to manually run your_model.update().

Events

Events can be defined on Components using the events property at construction time. Events that can be defined are specific for a Component. If you want to check the state of the system at the event, access your Model and Computed values.

Click event
When the user clicks the button, a notification pops up.

from quasargui import *

layout = QButton('Click me', events={'click': lambda: layout.api.plugins.notify("I've got clicked!")})
run(layout)

Notifications, dialogs

Quasargui offers a range of notification and dialog options, wrapping Quasar's plugins. You can access most Quasar plugins via your_component.api.plugins or your_model.api.plugins.

Click event
An extensive example. To check out all the dialogs and grid menus, you can run this example if you copy-paste it into your Python prompt.

Click event
Dark mode can be a breeze. The switch is in the top-right corner.

Click event
Notifications can be easily configured (use your_component.api.plugins.notify(), with setting keyword arguments besides message). See Quasar documentation of options; on QuasarConfOptions you can use any option as a keyword argument to notify().

Click event
One type of dialog is options dialog. You can have a simple yes-no prompt, a string value input prompt and other custom options.

Click event
A grid menu can nicely de-clog your app.

from quasargui import *


def show_notification(message):
    layout.notify(message=message, position='top-right', group=False, timeout=1500)


def show_success():
    layout.api.plugins.notify(message='This is a success!', caption='Just now', type='positive', icon='done')


dialog_events = {
    'ok': lambda data: show_notification('OK clicked, data={}'.format(json.dumps(data))),
    'cancel': lambda: show_notification('Cancel clicked'),
    'dismiss': lambda: show_notification('Dialog dismissed'),
}


def show_alert():
    layout.api.plugins.dialog(props={'title': 'Alert', 'message': 'Some message'}, events=dialog_events)


def show_confirm():
    layout.api.plugins.dialog(props={
        'title': 'Confirm',
        'message': 'Would you like to turn on the wifi?',
        'cancel': True,
        'persistent': True
    }, events=dialog_events)


def show_prompt():
    layout.api.plugins.dialog(props={
        'title': 'Prompt',
        'message': 'What is your name?',
        'prompt': {'model': '', 'type': 'text'},
        'cancel': True,
        'persistent': True
    }, events=dialog_events)


def show_options():
    layout.api.plugins.dialog(props={
        'title': 'Options',
        'message': 'Choose an option',
        'options': {
            'model': 'opt1',
            'type': 'radio',
            'items': [
                {'label': 'Option 1', 'value': 'opt1', 'color': 'secondary'},
                {'label': 'Option 2', 'value': 'opt2'},
                {'label': 'Option 3', 'value': 'opt3'}
            ]
        },
        'cancel': True,
        'persistent': True
    }, events=dialog_events)


def show_bottom_sheet(grid: bool):
    layout.api.plugins.bottom_sheet(props={
        'message': 'Bottom Sheet message',
        'grid': grid,
        'actions': [
            {'label': 'Drive', 'id': 'drive', 'img': 'https://cdn.quasar.dev/img/logo_drive_128px.png'},
            {'label': 'Keep', 'id': 'keep', 'img': 'https://cdn.quasar.dev/img/logo_keep_128px.png'},
            {'label': 'Google Hangouts', 'id': 'calendar', 'img': 'https://cdn.quasar.dev/img/logo_hangouts_128px.png'},
            {},
            {'label': 'Share', 'icon': 'share', 'id': 'share'},
            {'label': 'Upload', 'icon': 'cloud_upload', 'color': 'primary', 'id': 'upload'},
            {},
            {'label': 'John', 'avatar': 'https://cdn.quasar.dev/img/boy-avatar.png', 'id': 'john'}
        ]

    }, events=dialog_events)


dark_mode = Model(False)
dark_mode.add_callback(
    lambda: layout.api.plugins.dark(dark_mode.value)
)

layout = QLayout([
    QHeader([QToolbar([
        QToolbarTitle([
            QIcon('announcement', 'lg', classes='q-mx-md'),
            'Dialogs'
        ]),
        QSpace(),
        QButton(
            label=Computed(lambda dark: 'light mode' if dark else 'dark mode', dark_mode),
            icon=Computed(lambda dark: 'light_mode' if dark else 'dark_mode', dark_mode),
            props={'stretch': True},
            events={'click': toggle(dark_mode)}
        )
    ])]),
    QPage([Rows(classes='q-py-xl', children=[

        QButton('show a success notification', events={'click': show_success}),
        QButton('show an alert', events={'click': show_alert}),
        QButton('show a confirmation', events={'click': show_confirm}),
        QButton('show a prompt', events={'click': show_prompt}),
        QButton('show options', events={'click': show_options}),
        QButton('show a grid menu', events={'click': lambda: show_bottom_sheet(grid=True)}),
        QButton('show a list menu', events={'click': lambda: show_bottom_sheet(grid=False)}),

        Div(classes='q-mt-xl', children=[
            'See even more examples at ',
            Link('Quasar dialog documentation',
                 'https://quasar.dev/quasar-plugins/dialog#predefined')])
    ])])
])

run(layout, title='Dialogs demonstration', debug=True)

In general, in Quasar's plugins documentation, if you see notify({options}) you can expect to write in Python my_component.api.plugins.notify(**options).

Note that not all plugins are wrapped (because many plugins do not make sense in a desktop app) and some plugins work a little bit different. Look up QuasarPlugins in the Quasargui code to see all the implemented plugins.

Custom styles, defaults

Once you start working with Quasargui, you will probably want to create your own styling. Luckily, Quasar is pretty flexible, offers a range of styling classes (eg. 'q-mt-xl' means margin top should be extra large) and Q* components have lots of props (eg. QButton(props={'glossy': True})).

It can be a bit of a hustle though to set the same options again and again. So, defaults to the rescue!

Styling with defaults
An alternative styling for form elements. Check out the code to see how styles can be set.

from quasargui import *

QButton.defaults['props'] = {
    'glossy': True,
    'color': 'orange',
    'rounded': True
}
QInput.defaults['props'] = {
    'outlined': True,
    'rounded': True
}

layout = Rows(classes='q-mt-lg q-gutter-md', children=[
    Columns([QButton('one'), QButton('two'), QButton('oranje!')]),
    QInput('me is outlined')
])

run(layout, title='We likes glossy', size=(300, 200))

Window access

Quasargui's run(layout) command automatically produces a window for you. You can access this window through layout.api - in fact, your_component.api and your_model.api points to the same api after the component or the model is attached to a window. If you want to modify the window's properties, access system dialogs or create new windows, you can find these functionality as api's (quasargui.main.Api's) functions.

So, in the following sections we'll basically walk through quasargui.main.Api.

Menu can be added using run(menu=menu_definition) where menu_definition can look like

[{
    'title': 'Edit', 
    'children': [
        {'title': 'Copy', 'key': 'c', 'action': copy_cb},
        {'title': 'Paste', 'key': 'p', 'action': paste_cb}
    ]
}]
There are other subtleties such as defining a menu that is specific to a certain operations system. These are covered in the documentation of Api.

Example native mac menu
An example menu on Mac - check out the other screenshots for other systems.

Example menu under the titlebar
The menu on other systems is implemented using Quasargui Component's, mimicking a Windows menu.

"""
From this example you can learn how to set a menu,
and how to declare it dynamically (eg. for internationalization).

At the moment, the menu only works for Mac/Cocoa render.
"""

from quasargui import *
from quasargui.tools import static_vars

menu = [
    {'title': 'Top Action', 'action': lambda: layout.notify("Top Action"), 'key': 't'},
    {'title': 'Custom menu 1', 'children': [
        {'title': 'Action 1', 'action': lambda: layout.notify("Hello 1"), 'key': 'b'},
        {'title': 'Action 2', 'action': lambda: layout.notify("Hello 2"), 'key': 'd'},
        None,  # separator
        {'title': 'Submenu', 'children': [
            {'title': 'Action 3', 'action': lambda: layout.notify("Hello 3")},
            {'title': 'Submenu 2', 'children': [
                {'title': 'Submenu goes forever:)', 'children': [
                    {'title': 'Action 5', 'action': lambda: layout.notify("Hello 5")}
                ]},
                {'title': 'Action 4', 'action': lambda: layout.notify("Hello 4")}
            ]},
        ]},
    ]},
]


@static_vars(counter=0)
def switch_menu():
    new_menu = [{'title': 'Alternative action', 'action': lambda: layout.notify("Alternative action")}]
    switch_menu.counter += 1
    if switch_menu.counter % 2 == 1:
        layout.api.set_menu(new_menu)
    else:
        layout.api.set_menu(menu)


@static_vars(spare_menu=None)
def hide_menu_if_not(platform):
    layout.api.set_menu({platform: menu, 'default': []})


props = {'clickable': True}

layout = Rows(
    classes='q-ma-lg',
    children=[
        Heading(5, 'Click on a menu'),
        QList(props={'bordered': True}, classes='rounded-borders', children=[
            QItem([QItemSection(['Replace menu with alternative menu'])], props=props, events={'click': switch_menu}),
            QItem([QItemSection(['No menu if not Mac'])], props=props, events={'click': lambda: hide_menu_if_not('mac')}),
            QItem([QItemSection(['No menu if not Windows'])], props=props, events={'click': lambda: hide_menu_if_not('windows')})
        ])
    ])

run(layout, menu=menu)

If you want to dynamically change the menu, you can do that using Api's set_menu() command.

Window operations

To access window operations, we use Api's methods. You can access the api instance pointing to the window by a Component that is already mounted to a window (your_component.api) or a Model that is mounted to a window (your_model.api). To minimize, fullscreen or close a window, call .minimize_window(), .toggle_fullscreen() or .close_window(). To change the title, call .set_title('New title').

The standard file/folder/save dialogs can be accessed the same way - see the code of this example below.

window_operations
Copy the code of this example into your Python prompt to see window properties in action.

from quasargui import Model, QLayout, QHeader, QPage, QToolbar, QSpace, QButton, QInput, Rows, TrueFalse, toggle
from quasargui.main import run


title_model = Model('Your title goes here')
title_model.add_callback(
    lambda: title_model.api.set_window_title(title_model.value),
    immediate=True
)
is_fullscreen = Model(False)
width = Model(400)
width.add_callback(
    lambda: width.api.resize_window((width.value, None)) if 300 <= width.value <= 1000 else None,
    immediate=True
)
height = Model(500)
height.add_callback(
    lambda: height.api.resize_window((None, height.value)) if 300 <= height.value <= 1000 else None,
    immediate=True
)


def toggle_fullscreen():
    layout.api.toggle_fullscreen()
    is_fullscreen.value = not is_fullscreen.value


layout = QLayout([
    QHeader([QToolbar([
        QSpace(),
        QButton(icon='minimize',
                props={'stretch': True},
                events={'click': lambda: layout.api.minimize_window()}),
        QButton(icon=TrueFalse('fullscreen_exit', 'fullscreen', is_fullscreen),
                props={'stretch': True},
                events={'click': toggle_fullscreen}),
        QButton(icon='close',
                props={'stretch': True},
                events={'click': lambda: layout.api.close_window()}),
    ])]),
    QPage([
        Rows([
            QInput('Window title', title_model),
            QInput('Resize window width', width, type='number'),
            QInput('Resize window height', height, type='number'),
            QButton(
                'Show file dialog',
                events={
                    'click': lambda: layout.notify('You chose: {}'.format(
                        layout.api.show_open_dialog()))
                }),
            QButton(
                'Show folder dialog',
                events={
                    'click': lambda: layout.notify('You chose: {}'.format(
                        layout.api.show_folder_dialog()))
                }),
            QButton(
                'Show save dialog',
                events={
                    'click': lambda: layout.notify('You chose: {}'.format(
                        layout.api.show_save_dialog()))
                }),
        ])
    ])
])

run(layout, size=(400, 500))

Frameless windows are also possible.

window_operations
This toolbar is a frameless window.

from quasargui import *

props = {
    'dense': True
}

QButton.defaults['props'] = {
    'dense': True,
    'unelevated': True
}
Columns.defaults['classes'] = ''
Rows.defaults['classes'] = ''


layout = QLayout(children=[
    QPage([
        QBar(props={'dense': True}, classes='q-pr-xs', children=[
            QSpace(),
            QButton(icon='minimize',
                    props=props,
                    events={'click': lambda: layout.api.minimize_window()}),
            QButton(icon='close',
                    props=props,
                    events={'click': lambda: layout.api.close_window()}),
        ]),
        Rows([
            Columns([QButton(icon='language'), QButton(icon='favorite')]),
            Columns([QButton(icon='send'), QButton(icon='help')]),
        ])
    ])
])

run(layout, frameless=True, size=(64, 100), resizable=False)

Multiple windows

You can manage multiple windows. In that case the main rule of thumb is that each Component, Model and Computed can belong to only one window.
So, it is best to create your layout and models via a factory function.

window_handling
This example creates a new random-sized window when the user clicks 'create window'. Setting window title (that is a model) affects only its own window.

from random import randint
from time import sleep

from quasargui import Model, QLayout, QHeader, QPage, QToolbar, QSpace, QButton, QInput, Rows, TrueFalse
from quasargui.main import create_window, run


def create_new_window():
    layout = create_layout('New window')
    create_window(layout, position=(randint(1, 100), randint(1, 100)), size=(400, 500))
    sleep(0.5)
    layout.notify('A brand new window!', type='positive', timeout=200000)


def close_window(layout):
    layout.api.close_window()
    print('The window is closed but the app still runs')


def toggle_fullscreen(layout, model):
    model.value = not model.value
    layout.api.toggle_fullscreen()


def create_layout(title):
    title_model = Model(title)
    title_model.add_callback(
        lambda: title_model.api.set_window_title(title_model.value),
        immediate=True
    )
    is_fullscreen = Model(False)
    width = Model(400)
    width.add_callback(
        lambda: width.api.resize_window((width.value, None)) if 300 <= width.value <= 1000 else None,
        immediate=True
    )
    height = Model(300)
    height.add_callback(
        lambda: height.api.resize_window((None, height.value)) if 300 <= height.value <= 1000 else None,
        immediate=True
    )

    layout = QLayout([
        QHeader([QToolbar([
            QSpace(),
            QButton(icon='minimize',
                    props={'stretch': True},
                    events={'click': lambda: layout.api.minimize_window()}),
            QButton(icon=TrueFalse('fullscreen_exit', 'fullscreen', is_fullscreen),
                    props={'stretch': True},
                    events={'click': lambda: toggle_fullscreen(layout, is_fullscreen)}),
            QButton(icon='close',
                    props={'stretch': True},
                    events={'click': lambda: close_window(layout)}),
        ])]),
        QPage([
            Rows([
                QInput('Window title', title_model),
                QInput('Resize window width', width, type='number'),
                QInput('Resize window height', height, type='number'),
                QButton('Create new window', events={'click': create_new_window}),
           ])
        ])
    ])
    return layout


main_layout = create_layout('Main window')
run(main_layout, size=(400, 300))

Most important components

In this part we'll go through the most important components. If you're interested in seeing all components, go to the components list.

Layout components

The window (or run() function) can take any component as component, still it is recommended to use QLayout with QHeader, QPage and QFooter as children, and optionally QDrawer's. This leads to a standard layout where you can put your most important functions into QHeader, as QButton ('stretch' prop is recommended). QHeader's background and foreground can be set using 'q-*' classes, using styles={'background': '#abc', 'color': '#fff'} or adding your own stylesheet, pushing the path of your css file to your_component.style_sources or calling your_component.api.import_styles() after your component is mounted.

Note that adding QHeader and QFooter to QLayout is entirely optional.

Rows and Columns

If you want the classic vertical or horizontal layout, use Rows or Columns. These result in a html "flex" layout which means that Columns will wrap automatically if the window is not wide enough.

columns_and_rows
Columns and rows can be assembled using Columns and Rows component.

from quasargui import *

layout = QLayout([QPage([
    Columns(styles={'height': '100vh'}, column_classes='col-grow', children=[
        Rows(row_classes='bg-grey-1 row-grow align-center', children=[
            Div(['({}, {})'.format(i+1, j+1)]) for j in range(6)
        ])
        for i in range(4)
    ])
])])

run(layout, 'Columns and rows example', debug=True, size=(400, 300))

Note that columns don't grow naturally, for even spacing you need to set 'col-grow', also with Rows you need to set 'row-grow' for even alignment.

Form elements

You have access to a range of form elements. There are the Quasar form components (starting with Q, in quasargui.quasar_form module).

Form_simple
Some basic list of quasar form-components in a window

from quasargui import *
from quasargui.vue_tags_input import VueTagsInput


def init_person():
    return {
        'name': '',
        'role': '',
        'office': 'New York',
        'experience': 0,
        'height': 100,
        'tags': []
    }


person = Model(init_person())
accept = Model(False)


def add_employee():
    if accept.value:
        person.api.plugins.notify(
            type='positive',
            icon='done',
            message='<b>Employee added!</b>\n'
                    'name: {name}\n'
                    'role: {role}\n'
                    'experience: {experience}\n'
                    'height: {height}\n'
                    'tags: {tags}'.format(**person.value))
        person.value = init_person()
    else:
        person.api.plugins.notify('Have to accept the terms.', type='negative', icon='report_problem')


label_classes = 'q-mr-md'

Columns.defaults['classes'] = ''

layout = QLayout([QPage([
    QForm(classes='easyread q-px-md q-py-lg', children=[
        Heading(4, 'Add Employee', classes='q-mb-md q-pt-lg'),
        QInput('name', person['name']),
        QSelect('role', person['role'], props={'options': ['Manager', 'Worker'], 'use-input': True}),
        QSelect('office', person['office'], props={'options': ['London', 'New York'], 'readonly': True, 'disabled': True}),
        Columns(classes='items-center', children=[
            Div(['experience:'], label_classes),
            QKnob(person['experience'], props={'min': 0, 'max': 10, 'step': 1}),
        ]),
        Columns(classes='items-center', children=[
            Div(['height:'], label_classes),
            QSlider(person['height'], props={'min': 100, 'max': 250, 'step': 1}, styles={'min-width': '200px'})
        ]),
        Columns(classes='items-center', children=[
            Div(['tags:'], label_classes),
            VueTagsInput(person['tags']),
        ]),
        Div([
            QCheckbox('accept terms', accept),
        ]),
        QButton('submit', events={'click': add_employee}, classes='bg-primary text-white')
    ])
])])

run(layout, 'Register employee', size=(500, 700))

Also, there are form elements by input value type. These elements are in quasargui.quasar_form_improved module and are named according to the input value type, eg. InputStr, InputBool, etc. The idea is to automatically get a combination of controls that is set up correctly. You can get an apropriate appearance for your input, and later you can refine it, choosing from one of its available appearance. Eg. an InputChoice can be a radio, a series of pushable buttons, or a select dropdown. Or, if you allow multiple choices, you can get checkbox, toggles, tags input or a multi-choice select. If you don't choose one, the control is determined based on the number of available choices.

Form_input_choice-single
InputChoice with different single-choice appearance settings. If the appearance is not set, it will automatically choose an appearance based on the number of choices

Form_input_choice-multiple
InputChoice with different multi-choice appearance settings. If the appearance is not set, it will automatically choose an appearance based on the number of choices

from quasargui import *

few_choices = ['blonde', 'red', 'brown', 'blue', 'pink']
many_choices = [
    'blonde', 'red', 'brown', 'blue', 'pink',
    'black', 'grey', 'white', 'bold', 'long',
    'short', 'thin', 'thick', 'medium', 'rasta'
]

my_choice1 = Model('blonde')
my_choice2 = Model('blonde')
my_choice3 = Model('blonde')
my_choice4 = Model('blonde')
multi_choice1 = Model([])
multi_choice2 = Model([])
multi_choice3 = Model([])

gap_classes = 'q-pt-lg'

selected_tab = Model('single')

single_choice_components = [
    InputChoice(
        'my choice - no choice set',
        my_choice1,
        classes=gap_classes),
    InputChoice(
        'my choice', my_choice2,
        choices=few_choices,
        classes=gap_classes),
    InputChoice(
        'my choice - many choices',
        my_choice3,
        choices=many_choices,
        classes=gap_classes),
    InputChoice(
        'my choice - appearance=buttons',
        my_choice4,
        choices=few_choices,
        appearance='buttons',
        classes=gap_classes)
]

multiple_choice_components = [
    InputChoice(
        'multiple choices - no choice set',
        multi_choice1,
        classes=gap_classes,
        multiple=True),
    InputChoice(
        'my choice - a few choices set',
        multi_choice2,
        choices=few_choices,
        classes=gap_classes,
        multiple=True),
    InputChoice(
        'my choice - many choices set',
        multi_choice3,
        choices=many_choices,
        classes=gap_classes,
        multiple=True),
]

layout = QLayout([QPage(classes='easyread', children=[
    Heading(4, 'Input choice appearances', classes='q-mb-md'),
    QTabs(selected_tab, classes='text-primary', children=[
        QTab('single', label='single value'),
        QTab('multiple', label='multiple values')],
    ),
    QTabPanels(selected_tab, [
        QTabPanel('single', single_choice_components),
        QTabPanel('multiple', multiple_choice_components),
    ]),
])])

run(layout, 'Form - input choice demo', size=(600, 550), _render_debug=True, debug=True)

There's InputTime, InputDate and InputDateTime that are implementations of Quasar's recommendations on data-time input, with all the conversions between Python and the GUI taken care of (remember that date, time and datetime is a special class that is not a basic type.)

Form_datetime
Datetime inputs look similar to QInput. The user can enter the date or time via keyboard and with help of popups, clicking on the side icons.

Form_datetime-time
When the user click on the clock symbol, a time picker pops up.

Form_datetime-date
When the user click on the calendar symbol, a date picker pops up.

from quasargui import *

layout = QLayout([QPage(classes='easyread q-my-lg', children=[
    Heading(4, 'Time, Date and DateTime'),
    InputDate('My birthday'),
    InputTime('Work starts usually at'),
    InputDateTime('Experiment started'),
])])

run(layout, size=(700, 700))

Form validation

You can validate form components right when the user inputs a value - but for snappier action you need to add raw javascript to handle simple field validations, using JSRaw.

Form_validation-field
When the user fails to fill username, it gets into error state.

Form_validation-submit
When the user clicks submit, and there's error with the form data, a notification pops up.

import quasargui
from quasargui import *


def on_submit():
    validation = True
    validation = validation and input_name.validate()
    validation = validation and input_age.validate()
    if not accept.value:
        accept.api.plugins.notify(
            color='negative',
            message='You need to accept the license and terms first',
            icon='report_problem'
        )
    elif validation:
        accept.api.plugins.notify(
            icon='done',
            color='positive',
            message='Submitted'
        )


def on_reset():
    input_name.reset_validation()
    input_age.reset_validation()


QInput.defaults['props'].update({
    'filled': True,
    'lazy-rules': True
})


accept = Model(False)

input_name = QInput('Your name *', props={
    'hint': 'Name and surname',
    'rules': JSRaw("[ val => val && val.length > 0 || 'Please type something']")
})
input_age = QInput('Your age *', type='number', props={
    'rules': JSRaw("""[
              val => val !== null && val !== '' || 'Please type your age',
              val => val > 0 && val < 100 || 'Please type a real age'
            ]""")
})
validate_on_submit = QForm(
    styles={'max-width': '30em', 'margin': '0 auto'},
    events={
        'submit.prevent.stop': on_submit,
        'reset.prevent.stop': on_reset
    }, children=[
        Heading(5, 'Form validation on submit'),
        Div(classes='q-mb-md', children=[
            'Reference: ',
            Link('here', "https://quasar.dev/vue-components/input#example--form-validation")
        ]),
        input_name,
        input_age,
        QToggle('I accept the license and terms', accept),
        Columns([
            QButton('Submit', type='submit', color='primary', props={
                'unelevated': False
            }),
            QButton('Reset', type='reset', color='primary', props={
                'flat': True
            })
        ])
    ])

layout = QLayout([
    QHeader(['Form validation <small>- on submit</small>']),
    QPage([validate_on_submit])
])

quasargui.run(layout, 'Form validation demo', size=(600, 550))

You can also stick to validation on submit, in this case you need to set up a Model for errors.

Creating your own components

It is usually a better idea to use a factory function if you want to create a complex component, from existing components. However, you can also create your own assembled component. In that case, set component='div' and in the __init__() method you assemble the children parameter so that it suits your needs, even if it is only a single component. And you can also extend an existing component.

TODO: example - custom component


Integration

Matplotlib integration

Matplotlib_figure-png
Quick financial simulation with matplotlib. This window makes it possible to quickly check out the model for different parameters.

Matplotlib_figure-interactive
Quick financial simulation with matplotlib. This window makes it possible to quickly check out the model for different parameters.

"""
Coin-flip stock market price simulator

This example requires matplotlib and mpld3
"""
import math
import random
import time

# noinspection PyPackageRequirements, PyUnresolvedReferences
import matplotlib.pyplot as plt

from quasargui import *
from quasargui.plot import Plot

loading = Model[bool](True)
calculation_time = Model(0.0)
n = Model(1000)
n_processes = Model(10)
start = Model(10.0)
drift = Model(0.001)
variance = Model(0.02)
interactive = Model(False)
animated = Model(False)
fig, ax = plt.subplots(1, 1)
plot = Plot(renderer=Computed(lambda i: 'mpld3' if bool(i) else 'png', interactive))


def add_scenario(update_plot=True):
    global ax, plot, animated

    def create_random_process():
        coinflips = random.choices([-1, 1], k=int(n.value))
        random_process: list = [0]*(len(coinflips)+1)
        random_process[0] = float(start.value)
        mu = float(drift.value)
        var = float(variance.value)
        for i, flip in enumerate(coinflips, start=1):
            s_0 = random_process[i-1]
            s_1 = s_0 * math.e ** (mu - 0.5 * var**2 + var * flip)
            random_process[i] = s_1
        return random_process

    ax.plot(create_random_process())
    if update_plot:
        plot.update()


def redraw_plot():
    global fig, ax, plot, animated
    loading.value = True
    t1 = time.time()

    fig, ax = plt.subplots(1, 1)
    plot.set_figure(fig)
    for _ in range(int(n_processes.value)):
        add_scenario(update_plot=bool(animated.value))

    ax.grid(True, alpha=0.3)
    ax.set_xlabel('x')
    ax.set_ylabel('y')

    if not animated.value:
        plot.update()
    t2 = time.time()
    calculation_time.value = round(t2 - t1, 2)
    loading.value = False


# This example loads the plot on load.
QToggle.defaults['props'] = {
    'color': 'white',
    'keep-color': True,
    'left-label': True
}
layout = QLayout(events={'load': redraw_plot}, children=[
    QHeader([
        QToolbar([
            QToolbarTitle([
                QIcon('insights', 'lg', classes='q-mx-md'),
                'Plot demo <small>- Stock simulation by coin flip</small>'
            ]),
            QToggle(label='Interactive', model=interactive),
            QToggle(label='Animated', model=animated)
        ])
    ]),
    QPage([
        plot,
        v_if(
            interactive,
            Div(classes='text-center', children=[
                "Interactive plot is rendered by ",
                Link('mpld3', 'https://mpld3.github.io/')
            ])
        ),
        v_if(
            Not(Or(interactive, loading)),
            Div(classes='text-center', children=[
                'Non-interactive plot is rendered by matplotlib as png.'
            ])
        )
    ]),
    QDrawer([Rows([
        QInput(label='Number of coinflips', model=n),
        QInput(label='Number of screnarios', model=n_processes),
        QInput(label='Start value (S_0)', model=start),
        QInput(label='Drift (Îź)', model=drift),
        QInput(label='Variance (σ)', model=variance),
        QButton(label='Redraw', events={'click': redraw_plot}, props={'loading': loading}),
        QButton(label='Add scenario', events={'click': add_scenario}, props={'disable': loading}),
    ])]),
    v_if(
        calculation_time,
        QFooter(['Plotting took ', calculation_time, ' seconds.'])
    )
])

run(layout)

Adding an existing Vue component

The simplest method to add an existing Vue component is to get the .uml.js and its .css then its as simple as

class VueTagsInput(ComponentWithModel):
    component = 'vue-tags-input'
    script_sources = ['vue-tags-input.2.1.0.js']
    style_sources = ['vue-tags-input.css']
If a Vue component has model, extend ComponentWithModel, if it has even label, then LabeledComponent, otherwise extend Component. Set component to the component's html tag. If you look at VueTagsInput in Quasargui's source, you'll see that we've added a couple more lines. In the constructor of the component, we added some useful conveniences that make the component work more seamlessly by default.

from quasargui import ComponentWithModel, Model, ClassesType, StylesType, PropsType, build_props


class VueTagsInput(ComponentWithModel):
    """
    see http://www.vue-tags-input.com/#/examples/hooks
    Note that model points to 'tags' property,
    'v-model' can be accessed via self.current_tag.
    """
    component = 'vue-tags-input'
    script_sources = ['vue-tags-input.2.1.0.js']
    style_sources = ['vue-tags-input.css']

    def __init__(self,
                 model: Model = None,
                 classes: ClassesType = None,
                 styles: StylesType = None,
                 props: PropsType = None):
        props = build_props({}, props, {
            'tags': model or Model([])
        })
        events = {
            'tags-changed': lambda new_tags: model.set_value(new_tags)
        }
        self.dependents = [props['tags']]
        self.current_tag = Model('')
        super().__init__(
            model=self.current_tag,
            props=props,
            classes=classes,
            styles=styles,
            events=events)
First, the actual model of vue-tags-input is not its v-model but its tags prop. So, we've changed that. Remember to add any extra model you define within the component to self.dependents. (The model you pass to model parameter gets passed to self.dependents automatically.) If you don't add a model to self.dependents, it does not get attached to the GUI and you will see weird behavior. To debug this, set _render_debug=True in run(). It is worth do do a quick test rig of your component, something like the following:

Component_test_rig
The test rig displays a notification each time the component's value gets modified. Also, there's plenty of debug information about the state of the whole system. If you right-click, you can even open up the built-in browser inspector.

from quasargui import *
model = Model([])
model.add_callback(lambda: model.api.plugins.notify(json.dumps(model.value)))
run(VueTagsInput(model), debug=True, _render_debug=True)
In this code, replace VueTagsInput with your component.

Importing a SPA-style Vue component

Component_spa
TODO: example - from spa-style own js.

```python

TODO: ```