When run, the application described in this part of the tutorial shows a window
with two leaflets. One is used for the title bar, the other for the window
contents. When the window is resized or updated, the leaflets adapt and remain
in sync with each other.
.. |image1| image:: images/two-leaflets1.png
:scale: 25%
:alt: A screenshot of the application running in the phone environment
.. |image2| image:: images/two-leaflets2.png
:scale: 25%
:alt: A screenshot of the application running in the phone environment
.. centered:: |image1| |image2|
The main source code for the application can be found in the ``main.py`` file
within the ``src`` directory. The purpose of the other files is explained in
other tutorials, such as :ref:`First_Application`.
Design
------
We begin by looking at an overview of the window layout we want to create.
.. figure:: images/two-leaflets.svg
:width: 50%
:align: center
On the left is a title above a left page containing a button. On the right is a
subtitle containing a back button above a right page. When there is enough space
to display both pages we want to hide the buttons; otherwise, we will show them
so that the user can navigate between the pages.
The Program
-----------
We will not show all the main program here. Instead, we will focus on the parts
that have changed since the :ref:`previous part <Adaptive_UI_tutorial_part2>` of
this tutorial.
As before, almost everything is done in the ``do_activate`` method of the
``Application`` class where the widgets that make up the user interface are
organized using leaflets:
.. figure:: images/two-leaflets-leaflets.svg
:width: 70%
:align: center
As well as providing features for adaptive user interfaces, these two leaflets
also help us to structure the code. First, we create a leaflet to hold the
components of the title bar, then we create a leaflet to manage the main
contents of the window.
Creating a Title Bar
~~~~~~~~~~~~~~~~~~~~
In the ``do_activate`` method we create a application window and a title bar in a similar way to the :ref:`previous part <Adaptive_UI_tutorial_part2>` of this
tutorial. However, in this case, the title bar contains **two** ``Gtk.HeaderBar``
widgets instead of one, both contained within a ``Handy.Leaflet`` widget that we
refer to as the *title leaflet*:
.. literalinclude:: apps/part3/src/main.py
:language: python3
:start-at: Handy.TitleBar
:end-before: header = Gtk.HeaderBar
The first header bar contains the main heading for the window and a close button:
.. literalinclude:: apps/part3/src/main.py
:language: python3
:start-at: HeaderBar
:end-at: )
The second header bar contains a close button, but it is allowed to expand horizontally:
.. literalinclude:: apps/part3/src/main.py
:language: python3
:start-at: sub_header
:end-at: sub_header.add
It also contains a back button that is not part of the normal collection of
window decorations, which we define separately. The subheader is allowed to
expand horizontally to fill any available space -- this causes the close button
to be placed in the correct position when the window is fully expanded.
We call the ``add`` method to add the header and subheader to the leaflet,
calling the ``child_set`` method to give them names that the leaflet can use to
refer to them:
.. literalinclude:: apps/part3/src/main.py
:language: python3
:start-at: title_leaflet.add
:end-at: child_set(sub_header
We can then add the leaflet to the title bar and make the window use it:
.. literalinclude:: apps/part3/src/main.py
:language: python3
:start-at: title_bar.add(
:end-at: set_titlebar
One final task remains for the title leaflet. We need to place the two header
bars in a ``Handy.HeaderGroup`` so that their layout and visibility can be
managed:
.. literalinclude:: apps/part3/src/main.py
:language: python3
:start-at: header_group
:end-at: add_header_bar(sub_header)
This ensures that, for example, we do not see two close buttons when the window
is expanded.
Composing the Pages
~~~~~~~~~~~~~~~~~~~
The contents of the window itself are also managed by a ``Handy.Leaflet`` that
we refer to as the *content leaflet*:
.. literalinclude:: apps/part3/src/main.py
:language: python3
:start-at: content_leaflet
:end-at: )
We define the left page first, using a ``Gtk.Box`` that contains a label and a
button similar to the ones in the previous part of this tutorial:
.. literalinclude:: apps/part3/src/main.py
:language: python3
:start-at: page1 =
:end-at: pack_start(more_button
The right page is defined in a similar way, except that there is no need for a
button in the page for navigation because the back button in the subheader is
used for that purpose:
.. literalinclude:: apps/part3/src/main.py
:language: python3
:start-at: page2 =
:end-at: pack_start(more_label
We use a ``Gtk.Box`` to hold the label so that we can control its placement in
the page.
Both pages are created with their ``hexpand_set`` properties set to ``True``.
This allows them to expand horizontally if their child widgets require more
space.
Adding Pages to the Leaflet
~~~~~~~~~~~~~~~~~~~~~~~~~~~
With the two pages defined, we add them to the leaflet in the same way as
before, assigning names to them with the ``child_set`` method before adding the
leaflet to the window:
.. literalinclude:: apps/part3/src/main.py
:language: python3
:start-at: self.content_leaflet.add
:end-at: window.add
Note that we use the *same names* to refer to the two pages as we used for the
headers: ``left_page`` and ``right_page``. This helps us to synchronize the two
leaflets.
Aligning Headers and Content
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
We also want to make sure that each header is horizontally aligned with the
content it is associated with.
.. figure:: images/two-leaflets-size-groups.svg
:width: 50%
:align: center
This is done by placing each header in a ``Gtk.SizeGroup`` with its
corresponding page widget. The main header with the first page, the subheader
with the second:
.. literalinclude:: apps/part3/src/main.py
:language: python3
:start-at: left_page_size_group
:end-at: add_widget(page2)
Because each size group is used in ``horizontal`` mode, these place constraints
on the widgets in each group to ensure that their widths are synchronized.
Binding Properties and Connecting Signals
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
We bind the ``visible-child-name`` property of the content leaflet to the
corresponding property in the title leaflet, causing them to be synchronized:
.. literalinclude:: apps/part3/src/main.py
:language: python3
:start-after: Ensure that the header is updated when the page changes.
:end-at: )
This ensures that the appropriate header is always shown in the title bar for
the current page. It is also why we define names for the pages when we added
them to the leaflets.
Since the pages can be shown side-by-side, we want to hide the buttons in that
situation so that they are not confusing for users. To do this, we bind each
button's ``visible`` property to the ``folded`` state of the leaflet it is
associated with:
.. literalinclude:: apps/part3/src/main.py
:language: python3
:start-at: title_leaflet.bind
:end-before: End of property bindings
This ensures that the buttons will only appear when the leaflets are folded.
There is no need to show either of the buttons when the window is fully expanded.
We also connect each button's ``clicked`` signal to a method that will show the
appropriate page when the user clicks it:
.. literalinclude:: apps/part3/src/main.py
:language: python3
:start-at: back_button.connect
:end-at: more_button.connect
The connections only have an effect when the buttons are shown. It is not
possible for the user to click them when the leaflets are folded.
Setting a Default Size
~~~~~~~~~~~~~~~~~~~~~~
At the end of the ``do_activate`` method, we show the window and its contents:
.. literalinclude:: apps/part3/src/main.py
:language: python3
:start-at: window.set_default_size
:end-at: window.show_all
We assign a default size to the window to help inform the leaflets about the
amount of available space to expect.
Turning the Page
~~~~~~~~~~~~~~~~
The last thing to define in the ``Application`` class is the ``show_page``
method that responds to ``clicked`` signals from the buttons:
.. literalinclude:: apps/part3/src/main.py
:language: python3
:start-at: def show_page
:end-at: self.content_leaflet
Here, the page supplied as an argument is passed to the content leaflet so
that it can be shown.
Although we change the ``visible-child`` property to change the current page,
the ``visible-child-name`` property will also be updated. This means that the
property binding that we set up earlier will ensure that the title leaflet will
also be updated, keeping the leaflets synchronized.
Running the Application
-----------------------
See the :ref:`Adaptive_UI_tutorial_building` and :ref:`Adaptive_UI_tutorial_packaging`
sections for information about building, packaging and running the application.
Summary
-------
This part of the tutorial showed how two ``Handy.Leaflet`` widgets can be
connected together to ensure that the ``Gtk.HeaderBar`` shown in a window's
title bar is synchronized with the corresponding widget in the window's main
area.
We add two ``Gtk.HeaderBar`` widgets to a leaflet in the title bar, and we
add two ``Gtk.Box`` widgets to a leaflet in the window's main area. Each leaflet
is responsible for showing one or both widgets it contains, depending on the
amount of space available.
We use a ``Handy.HeaderGroup`` to manage the header bars, avoiding problems with
duplicate window decorations, and we use ``Gtk.SizeGroup`` objects to keep
headers and pages aligned and sized correctly.
Each header bar is assigned a name using the title leaflet's ``child_set``
method. The corresponding page is assigned the same name using the content
leaflet's ``child_set`` method. By binding the content leaflet's