Merge commit '81396a5322a7a48764fcf254d5d933ba1e57bdc5' as 'kiwi'

This commit is contained in:
2024-02-11 15:32:50 -06:00
76 changed files with 13184 additions and 0 deletions

View File

@@ -0,0 +1,4 @@
Kiwisolver C++ API
==================
Under construction

View File

@@ -0,0 +1,8 @@
kiwisolver
==========
.. toctree::
:maxdepth: 2
Python API <python.rst>
C++ API <cpp.rst>

View File

@@ -0,0 +1,7 @@
Kiwisolver Python API
=====================
.. automodule:: kiwisolver
:members:
:undoc-members:
:show-inheritance:

View File

@@ -0,0 +1,213 @@
.. _basis-basic-systems:
Constraints definition and system solving
=========================================
.. include:: ../substitutions.sub
A system within Kiwi is defined by a set of constraints, which may
be either equalities or inequalities (limited to >= and <=, as strict inequalities
are not supported). Each constraint can be assigned a specific 'strength',
indicating its relative importance in the problem-solving process. The subsequent
sections will delve into the methods of defining these constraints and extracting
results from the solver.
Defining variables and constraints
----------------------------------
The initial step involves defining variables, which represent
the values that the solver aims to determine. These variables are
encapsulated by |Variable| objects. The creation of these objects
can be accomplished as follows:
.. tabs::
.. code-tab:: python
from kiwisolver import Variable
x1 = Variable('x1')
x2 = Variable('x2')
xm = Variable('xm')
.. code-tab:: c++
#include <kiwi/kiwi.h>
using namespace kiwi
Variable x1("x1");
Variable x2("x2");
Variable xm("xm");
.. note::
Naming your variables is not mandatory but it is recommended since it will
help the solver in providing more meaningful error messages.
Now that we have some variables we can define our constraints.
.. tabs::
.. code-tab:: python
constraints = [x1 >= 0, x2 <= 100, x2 >= x1 + 10, xm == (x1 + x2) / 2]
.. code-tab:: c++
Constraint constraints[] = { Constraint {x1 >= 0},
Constraint {x2 <= 100},
Constraint {x2 >= x1 + 20},
Constraint {xm == (x1 + x2) / 2}
};
Next, add these variables to the solver, an instance of |Solver|:
.. tabs::
.. code-tab:: python
from kiwisolver import Solver
solver = Solver()
for cn in constraints:
solver.addConstraint(cn)
.. code-tab:: c++
Solver solver;
for(auto& constraint : constraints)
{
solver.addConstraint(constraint);
}
.. note::
You can start adding constraints to the solver without creating all your
variables first.
So far, we have defined a system representing three points on the segment
[0, 100], with one of them being the middle of the others, which cannot get
closer than 10. All those constraints have to be satisfied; in the context
of Cassowary, they are required constraints.
.. note::
Cassowary (and Kiwi) allows for redundant constraints, which means
even with two constraints (x == 10, x + y == 30) being equivalent to a
third one (y == 20), all three can be added to the solver without issues.
However, it is advisable not to add the same constraint multiple times
in the same form to the solver.
Managing constraints strength
-----------------------------
Cassowary also supports constraints that are not required. Those are only
respected on a best effort basis. To express that a constraint is not required
we need to assign it a *strength*. Kiwi specifies three standard strengths
besides the "required" strength: strong, medium, weak. A strong constraint
will always win over a medium constraint, which in turn will always override
a weak constraint [#f1]_ .
In our example, let's assume x1 would like to be at 40, without this being a
requirement. This is translated as follows:
.. tabs::
.. code-tab:: python
solver.addConstraint((x1 == 40) | "weak")
.. code-tab:: c++
solver.addConstraint(x1 == 40 | strength::weak);
Adding edit variables
---------------------
So far our system is pretty static; we have no way of trying to find solutions
for a particular value of `xm`, let's say. This is a problem. In a real
application (e.g. a GUI layout), we would like to find the size of the widgets
based on the top window but also react to the window resizing, so actually
adding and removing constraints all the time wouldn't be optimal. And there is
a better way: edit variables.
Edit variables are variables for which you can suggest values. Edit variables
have a strength which can be at most strong (the value of a edit variable can
never be required).
For the sake of our example, we will make "xm" editable:
.. tabs::
.. code-tab:: python
solver.addEditVariable(xm, 'strong')
.. code-tab:: c++
solver.addEditVariable(xm, strength::strong);
Once a variable has been added as an edit variable, you can suggest a value for
it and the solver will try to solve the system with it.
.. tabs::
.. code-tab:: python
solver.suggestValue(xm, 60)
.. code-tab:: c++
solver.suggestValue(xm, 60);
This would give the following solution: ``xm == 60, x1 == 40, x2 == 80``.
Solving and updating variables
------------------------------
Kiwi solves the system each time a constraint is added or removed, or a new
value is suggested for an edit variable. Solving the system each time makes for
faster updates and allows to keep the solver in a consinstent state. However,
the variable values are not updated automatically, and you need to ask
the solver to perform this operation before reading the values, as illustrated
below:
.. tabs::
.. code-tab:: python
solver.suggestValue(xm, 90)
solver.updateVariables()
print(xm.value(), x1.value(), x2.value())
.. code-tab:: c++
solver.suggestValue(xm, 90);
solver.updateVariables();
std::cout << xm.value() << ", " << x1.value() << ", " << x2.value();
This last update creates an infeasible situation by pushing x2 further than
100, if we keep x1 where it would like to be. As a consequence, we get the
following solution: ``xm == 90, x1 == 80, x2 == 100``
.. note::
To determine if a non-required constraint was violated when solving the system,
you can use the constraint's ``violated`` method.
.. versionadded:: 1.4
Footnotes
---------
.. [#f1] Actually, there are some corner cases in which this can be violated.
See :ref:`basics-internals`

View File

@@ -0,0 +1,22 @@
.. _basis:
Kiwisolver usage
================
.. include:: ../substitutions.sub
This section of the docs aims at getting you up and running with Kiwi. You will
in particular learn how to install Kiwi, create a system of constraints, solve,
update it etc... By the end of it you will know how to use the solver.
However if you are not familiar with Cassowary (or constraints solver in
general) it may not be enough to get you started using it in your project.
Hopefully the real world use cases described in :ref:`uses` will shed
more light on how to use it in real applications.
.. toctree::
:maxdepth: 2
installation.rst
basic_systems.rst
solver_internals.rst

View File

@@ -0,0 +1,87 @@
.. _basis-installation:
Installing Kiwisolver on Python
===============================
.. include:: ../substitutions.sub
Kiwisolver is supported on Python 3.7+. Installing it is a straight-forward
process. There are three approaches to choose from.
The easy way: Pre-compiled packages
-----------------------------------
The easiest way to install atom is through pre-compiled packages. Kiwisolver is
distributed pre-compiled in two-forms.
Conda packages
^^^^^^^^^^^^^^
If you use the `Anaconda`_ Python distribution platform (or `Miniconda`_, its
lighter-weight companion), the latest release of Kiwisolver can be installed
using conda from the default channel or the conda-forge channel::
$ conda install kiwisolver
$ conda install kiwisolver -c conda-forge
.. _Anaconda: https://store.continuum.io/cshop/anaconda
.. _Miniconda: https://conda.io/miniconda.html
Wheels
^^^^^^
If you don't use Anaconda, you can install Kiwisolver pre-compiled,
through PIP, for most common platforms::
$ pip install kiwisolver
Compiling it yourself: The Hard Way
-----------------------------------
Building Kiwisolver from scratch requires Python and a C++ compiler. On Unix
platform getting a C++ compiler properly configured is generally
straighforward. On Windows, starting with Python 3.6 the free version of the
Microsoft toolchain should work out of the box. Installing Kiwisolver is then
as simple as::
$ pip install .
.. note::
For MacOSX users on OSX Mojave, one needs to set MACOSX_DEPLOYMENT_TARGET
to higher than 10.9 to force the compiler to use the new C++ stdlib::
$ export MACOSX_DEPLOYMENT_TARGET=10.10
Supported Platforms
-------------------
Kiwisolver is known to run on Windows, OSX, and Linux; and compiles cleanly
with MSVC, Clang, GCC, and MinGW. If you encounter a bug, please report
it on the `Issue Tracker`_.
.. _Issue Tracker: http://github.com/nucleic/enaml/issues
Checking your install
---------------------
Once you installed kiwisolver you should be able to import it as follows:
.. code:: python
import kiwisolver
.. note::
On Windows, the import may fail with `ImportError: DLL load failed`. If it
does, it means your system is missing the Microsoft Visual C++
redistributable matching your Python version. To fix the issue download
and install the package corresponding to your Python version
(https://support.microsoft.com/en-us/help/2977003/the-latest-supported-visual-c-downloads):
- Python 2.7: Visual C++ Redistributable 2008
- Python 3.4: Visual C++ Redistributable 2010
- Python 3.5+: Visual C++ Redistributable 2015 or more recent

View File

@@ -0,0 +1,196 @@
.. _basics-internals:
Solver internals and tips
=========================
.. include:: ../substitutions.sub
Kiwi is not a mere rewriting of Cassowary, and due to this, it does not always
perfectly reflect the original implementation. The following sections point out
these discrepancies and provide tips on how to work effectively with Kiwi.
Inspecting the solver state
---------------------------
The state of the solver can be inspected by dumping a text representation of
its state either to stdout using the ``dump`` method of the solver, or to a
string using the ``dumps`` method. Typically, at least a basic understanding of
the Cassowary algorithm is necessary to analyse the output.
A typical output is reproduced below:
.. code::
Objective
---------
-2 + 2 * e2 + 1 * s8 + -2 * s10
Tableau
-------
v1 | 1 + 1 * s10
e3 | -1 + 1 * e2 + -1 * s10
v4 | -1 + -1 * d5 + -1 * s10
s6 | -2 + -1 * s10
e9 | -1 + 1 * s8 + -1 * s10
Infeasible
----------
e3
e9
Variables
---------
bar = v1
foo = v4
Edit Variables
--------------
bar
Constraints
-----------
1 * bar + -0 >= 0 | strength = 1
1 * bar + 1 <= 0 | strength = 1.001e+09
1 * foo + 1 * bar + 0 == 0 | strength = 1.001e+09
1 * bar + 0 == 0 | strength = 1
In the dump, the letters have the following meaning:
- v: external variable, corresponds to the variable created by you, the user
- s: slack symbol, used to represent inequalities
- e: error symbol, used to represent non-required constraints
- d: dummy variable, always zero, used to keep track of the impact of an
external variable in the tableau.
- i: invalid symbol, returned when no valid symbol can be found.
Stay constraints emulation
--------------------------
One feature of Cassowary that Kiwi abandoned is the notion of stay
constraints. Stay constraints are typically used in under-constrained
situations (drag and drop) to allow the solver to find a solution by keeping
non-modified variable close to their original position. A typical example is
a rectangle whose one corner is being dragged in a drawing application.
Kiwi does not have stay constraints mostly because in the context of widget
placement, the system is typically well constrained, rendering stay constraints
unnecessary.
If your application requires them, several workarounds can be considered:
- adding/removing non-required equality constraints to mimic stay constraints
- using edit-variables to mimic stay constraints
The first method will require to remove the old constraints as soon as they
stop making sense, while the second will require to update the suggested value.
Creating strengths and their internal representation
----------------------------------------------------
Kiwi provides three strengths in addition to the required strength by default:
"weak", "medium", and "strong". Contrary to Cassowary, which uses lexicographic
ordering to ensure that strength are always respected, Kiwi strives for speed
and uses simple floating point numbers.
.. note::
Using simple floating point, means that in some rare corner cases, a large
number of weak constraints may outweigh a medium constraint. However, in
practice, this rarely happens.
Kiwi allows to create custom strengths in the following manner:
.. tabs::
.. code-tab:: python
from kiwisolver import strength
my_strength = strength.create(1, 1, 1)
my_strength2 = strength.create(1, 1, 1, 2)
.. code-tab:: c++
double my_strength = strength::create(1, 1, 1);
double my_strength = strength::create(1, 1, 1, 2);
The first argument is multiplied by 1 000 000, the second argument by 1 000,
and the third by 1. No strength can be create larger than the required
strength. The default strengths in Kiwi correspond to:
.. code:: python
weak = strength.create(0, 0, 1)
medium = strength.create(0, 1, 0)
strong = strength.create(1, 0, 0)
required = strength.create(1000, 1000, 1000)
While Cassowary differentiates between strength and weight, those two concepts
are fused in Kiwi: when creating a strength, one can apply a weight (the fourth
argument) that will multiply it.
.. note::
Because strengths are implemented as floating point numbers, in order to be
effective, strengths must be different enough from one another. The
following is unlikely to produce any really useful result.
.. code:: python
weak1 = strength.create(0, 0, 1)
weak2 = strength.create(0, 0, 2)
weak3 = strength.create(0, 0, 3)
Managing memory
---------------
When removing a constraint, Kiwi does not check whether or not the variables
used in the constraint are still in use in other constraints. This is mostly
because such checks could be quite expensive. However, this means the map of
variables can grow over time.
To avoid this possibly causing large memory leaks, it is recommended to reset
the solver state (using the method of the same name) and to add back the
constraints, that are still valid at this point.
Representation of constraints
-----------------------------
If you browse through the API documentation you may notice a number of classes
that do not appear anywhere in this documentation: Term and Expression.
Those classes are used internally in constraints and are created automatically
by the library. A |Term| represents a variable/symbol and the coefficient that
multiplies it, |Expression| represents a sum of terms and a constant value and
is used as the left hand side of a constraint.
Performance implementation tricks
---------------------------------
Map type
^^^^^^^^
Kiwi uses maps to represent the state of the solver and to manipulate it. As a
consequence, the map type should be fast, with a particular emphasis on
iteration. The C++ standard library provides unordered_map and map that could
be used in kiwi, but none of those are very friendly to the CPU cache. For
this reason, Kiwi uses the AssocVector class implemented in Loki (slightly
updated to respect c++11 standards). The use of this class provides a 2x
speedup over std::map.
Symbol representation
^^^^^^^^^^^^^^^^^^^^^
Symbols are used in Kiwi to represent the state of the solver. Since solving the
system requires a large number of manipulations of the symbols, the operations
have to compile down to an efficient representation. In Kiwi, symbols compile
down to long long meaning that a vector of them fits in a CPU cache line.

169
kiwi/docs/source/conf.py Normal file
View File

@@ -0,0 +1,169 @@
# -*- coding: utf-8 -*-
#
# Configuration file for the Sphinx documentation builder.
#
# This file does only contain a selection of the most common options. For a
# full list see the documentation:
# http://www.sphinx-doc.org/en/main/config
# -- Path setup --------------------------------------------------------------
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
#
# import os
# import sys
# sys.path.insert(0, os.path.abspath('.'))
# NOTE the project needs to be installed for the docs to be properly built.
import kiwisolver
# -- Project information -----------------------------------------------------
project = "kiwisolver"
copyright = "2018-2021, Nucleic team"
author = "Nucleic team"
# The short X.Y version
version = ".".join(kiwisolver.__version__.split(".")[:2])
# The full version, including alpha/beta/rc tags
release = kiwisolver.__version__
# -- General configuration ---------------------------------------------------
# If your documentation needs a minimal Sphinx version, state it here.
#
# needs_sphinx = '1.0'
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = [
"sphinx.ext.autodoc",
"sphinx.ext.doctest",
"sphinx.ext.intersphinx",
"sphinx.ext.todo",
"sphinx.ext.coverage",
"sphinx.ext.imgmath",
"sphinx.ext.ifconfig",
"sphinx.ext.viewcode",
"sphinx.ext.graphviz",
"sphinx.ext.inheritance_diagram",
"sphinx.ext.autosummary",
"sphinx.ext.napoleon",
"sphinx_tabs.tabs",
]
# Add any paths that contain templates here, relative to this directory.
templates_path = ["_templates"]
# The suffix(es) of source filenames.
# You can specify multiple suffix as a list of string:
#
# source_suffix = ['.rst', '.md']
source_suffix = ".rst"
# The main toctree document.
main_doc = "index"
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
#
# This is also used if you do content translation via gettext catalogs.
# Usually you set "language" from the command line for these cases.
language = "en"
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
# This pattern also affects html_static_path and html_extra_path .
exclude_patterns = []
# The name of the Pygments (syntax highlighting) style to use.
pygments_style = "sphinx"
# -- Options for HTML output -------------------------------------------------
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
#
html_theme = "sphinx_rtd_theme"
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
# documentation.
#
# html_theme_options = {}
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
# html_static_path = ['_static']
# Custom sidebar templates, must be a dictionary that maps document names
# to template names.
#
# The default sidebars (for documents that don't match any pattern) are
# defined by theme itself. Builtin themes are using these templates by
# default: ``['localtoc.html', 'relations.html', 'sourcelink.html',
# 'searchbox.html']``.
#
# html_sidebars = {}
# -- Options for HTMLHelp output ---------------------------------------------
# Output file base name for HTML help builder.
htmlhelp_basename = "kiwidoc"
# -- Options for LaTeX output ------------------------------------------------
latex_elements = {
# The paper size ('letterpaper' or 'a4paper').
#
# 'papersize': 'letterpaper',
# The font size ('10pt', '11pt' or '12pt').
#
# 'pointsize': '10pt',
# Additional stuff for the LaTeX preamble.
#
# 'preamble': '',
# Latex figure (float) alignment
#
# 'figure_align': 'htbp',
}
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title,
# author, documentclass [howto, manual, or own class]).
latex_documents = [
(main_doc, "kiwi.tex", "kiwi Documentation", "Nucleic team", "manual"),
]
# -- Options for manual page output ------------------------------------------
# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [(main_doc, "kiwi", "kiwi Documentation", [author], 1)]
# -- Options for Texinfo output ----------------------------------------------
# Grouping the document tree into Texinfo files. List of tuples
# (source start file, target name, title, author,
# dir menu entry, description, category)
texinfo_documents = [
(
main_doc,
"kiwi",
"kiwi Documentation",
author,
"kiwi",
"One line description of project.",
"Miscellaneous",
),
]

View File

@@ -0,0 +1,29 @@
.. _developer:
Developer notes
================
These notes are meant to help developers and contributors with regards to some
details of the implementation and coding style of the project.
C++ codebase
------------
The C++ codebase currently targets C++11 compliance. It is header-only since
one of the focus of the library is speed.
Python bindings
---------------
Python bindings targets Python 3.7 and above. The bindings are hand-written and
relies on cppy (https://github.com/nucleic/cppy). Kiwisolver tries to use a
reasonably modern C API and to support sub-interpreter, this has a couple of
consequences:
- all the non exported symbol are enclosed in anonymous namespaces
- kiwisolver does not use static types and only dynamical types (note that the
type slots and related structures are stored in a static variable)
- modules use the multi-phases initialization mechanism as defined in
PEP 489 -- Multi-phase extension module initialization
- static variables use is limited to type slots, method def

View File

@@ -0,0 +1,32 @@
.. kiwi documentation main file, created by
sphinx-quickstart on Mon Oct 29 21:48:45 2018.
You can adapt this file completely to your liking, but it should at least
contain the root `toctree` directive.
Welcome to Kiwisolver's documentation!
======================================
Kiwisolver is an efficient C++ implementation of the Cassowary constraint
solving algorithm. Kiwi is an implementation of the algorithm based on the
seminal Cassowary paper. It is *not* a refactoring of the original C++ solver.
Kiwisolver has been designed from the ground up to be lightweight and fast.
Kiwisolver range from 10x to 500x faster than the original Cassowary solver
with typical use cases gaining a 40x improvement. Memory savings are
consistently > 5x.
In addition to the C++ solver, Kiwi ships with hand-rolled Python bindings.
.. toctree::
:maxdepth: 2
Getting started <basis/index.rst>
Use cases <use_cases/index.rst>
Developer notes <developer_notes/index.rst>
API Documentation <api/index.rst>
Indices and tables
==================
* :ref:`genindex`
* :ref:`modindex`
* :ref:`search`

View File

@@ -0,0 +1,16 @@
..
This file holds some common substitutions (mainly for referencing code).
To use it add the directive include:: substitutions.rst at the beginning of
the file.
.. ============================================================================
.. Kiwi substitutions
.. ============================================================================
.. |Variable| replace:: :py:class:`~kiwisolver.Variable`
.. |Solver| replace:: :py:class:`~kiwisolver.Solver`
.. |Term| replace:: :py:class:`~kiwisolver.Term`
.. |Expression| replace:: :py:class:`~kiwisolver.Expression`

View File

@@ -0,0 +1,142 @@
.. _uses-enaml:
Enaml
=====
.. include:: ../substitutions.sub
Enaml is a programming language and framework for creating professional-quality
user interfaces with minimal effort. It relies on Kiwi to layout widgets.
To implement its layout, Enaml uses a nestable model. Containers widgets
handle the constraints generation used to layout their children. Furthermore,
they pass to their their children a representation of the bounding box in
which they should live which allows the widgets to position themselves inside
their parent. Since each leaf component has a "preferred size", the system can
be solved from the bottom-up and set the size the parents based on the required
space of the children. If at a later time the parent is resized, this new input
can be used to solve the layout problem.
The following sections will describe in more details how the constraints are
generated and the preferred size estimated.
Widget variables
----------------
In Enaml, each widget that can be constrained defines a bunch of Kiwi
|Variable|: ``left``, ``top``, ``width`` and ``height``. In addition,
it provides easy access to combination of those: ``right``, ``bottom``,
``h_center``, ``v_center``. Those variables will be used to define the layout.
In addition, because each widget has a preferred size, it defines a set of
constraints related to that preferred size on which the user can act upon by
modifying their strength:
- hug_width: equivalent to (width == hint) | hug_width
- hug_height: equivalent to (height == hint) | hug_height
- resist_width: equivalent to (width >= hint) | resist_width
- resist_height: equivalent to (height >= hint) | resist_height
- limit_width: equivalent to (width <= hint) | limit_width
- limit_height: equivalent to (height <= hint) | limit_height
Finally, widget that can contain other widgets define a set of variables that
they expose to their children to allow to place themselves relative to their
parents. Those are: ``contents_left``, ``contents_top``, ``contents_width``,
``contents_height``, ``contents_right``, ``contents_bottom``,
``contents_h_center``, ``contents_v_center``. Those are usually not equivalent
to the non-prefixed variables (even-though they are related) because of the
container margins.
The base classes used a mixin to implement those behaviors are defined in:
https://github.com/nucleic/enaml/blob/main/enaml/layout/constrainable.py
Constraints definition
----------------------
Using the above variable, one can express any constraints. However, even for
simple vertical or horizontal boxes, the constraints to define, in
particular if one needs to introduce some spacing around the objects, become
quite painful to write by hand.
To make constraints definition easier, Enaml relies on helpers function and
classes. In the following, we will focus on how horizontal and vertical boxes
constraints are handled, by studying the following example in details:
.. image:: enaml_hbox.svg
Here we consider a container widget with three child widgets. The outer black
frame represents the limit of the container. The dashed frame represents the
contents visible to children when defining their constraint. The container uses
the margin definition to relate the outer left, top, width and height to their
'contents' equivalent.
The three widgets are arranged according to a horizontal box for which the
constraints are created using the `hbox` helper function which simply accepts
a list of widgets and spacers . To define the constraints from that list, Enaml
relies on the spacers represented here in orange. Each spacer has a
given size and a policy regarding that size (is it a minimum value, maximum,
how strongly to enforce that size). For each orientation, `hbox` add spacers so
that there is a spacer between each widget and between the widgets and the
parent boundaries. Some spacers can have a zero size, simply meaning that
widgets should be in contact.
When generating the constraints, `hbox` will be passed the container and use
the spacers to generate the constraints by simply glueing the anchors of
surrounding widgets. Each spacer can generate multiple constraints which gives
this process a lot of flexibility. Furthermore, those helpers define the same
variable as the widgets allowing for to position groups with respect to one
another.
.. note::
In practice, `hbox` itself relies on some helpers but the above gives you
the general idea.
For further details you can have a look at the source of the helpers described
in this section which can be found in the Enaml source:
- spacers: https://github.com/nucleic/enaml/blob/main/enaml/layout/spacers.py
- helpers:
- https://github.com/nucleic/enaml/blob/main/enaml/layout/layout_helpers.py
- https://github.com/nucleic/enaml/blob/main/enaml/layout/linear_box_helper.py
- https://github.com/nucleic/enaml/blob/main/enaml/layout/sequence_helper.py
Setting up the solver
---------------------
So far we have only defined the constraints that represent the layout, we will
now turn to how Enaml pass those to the solver and how it handle updates and
solver resets.
By default, each container manages its own solver independently. This has
the advantage of keeping the system relatively smalls and hence allow for
faster updates. When setting up the solver, the container will add for each
widget a set of constraints reflecting the preference of the widget regarding
its size as reported by the widget, and add to those the constraints defining
the layout. It will also add two edit variable representing the width and
height of the container.
Once the solver has been set up it can be used to compute different values,
such as the best size for the container (requesting a size of 0 with a 0.1*weak
strength), its min size (0 size, medium strength) and max size (max size,
medium strength).
When the parent is resized, the solver is invoked again with the new width and
height as suggestion. On the other hand, if the constraints change either
because widgets have been added or removed or because the users modified them,
the solver is reset and the constraints are rebuilt-from scratch. This means
that we never keep the solver around long enough to have to worry about memory
consumption due to unused variables in the solver.
In a complex hierarchy, the top parent will request the sizes of the nested
containers which will trigger the solving of their constraints. At some point
in the nested structure, we will only find widgets which provides a size hint
without requiring to solve constraints (ex: a button). This will allow to solve
the system and then propagate back upward.
Hopefully this brief introduction will have clarified how Enaml make use of
kiwi to layout its widgets. Some fine mechanics have been simplified for the
sake of this description but you can check Enaml sources for a more in depth
description.

View File

@@ -0,0 +1,244 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 18.1.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 449.8 112.7" enable-background="new 0 0 449.8 112.7" xml:space="preserve">
<font horiz-adv-x="1000">
<!-- Copyright 2000, 2004 , 2005Adobe Systems Incorporated. All Rights Reserved. U.S. Patent D454,582.Myriad is a registered trademark of Adobe Systems Incorporated. -->
<font-face font-family="MyriadPro-Bold" units-per-em="1000" underline-position="-100" underline-thickness="50"/>
<missing-glyph horiz-adv-x="500" d="M0,0l500,0l0,700l-500,0M250,395l-170,255l340,0M280,350l170,255l0,-510M80,50l170,255l170,-255M50,605l170,-255l-170,-255z"/>
<glyph unicode="C" horiz-adv-x="595" d="M538,138C508,126 457,116 411,116C275,116 195,201 195,336C195,486 289,559 410,559C464,559 507,547 538,534l31,121C542,669 482,685 403,685C199,685 35,557 35,327C35,135 155,-11 388,-11C470,-11 533,5 561,19z"/>
<glyph unicode="W" horiz-adv-x="888" d="M342,0l64,290C422,358 433,422 445,498l2,0C455,421 466,358 479,290l57,-290l165,0l174,674l-155,0l-55,-276C651,318 636,239 626,164l-2,0C614,239 603,311 588,390l-54,284l-162,0l-57,-276C298,315 282,234 271,161l-2,0C258,229 244,316 229,397l-51,277l-163,0l160,-674z"/>
<glyph unicode="a" horiz-adv-x="528" d="M469,289C469,404 417,500 254,500C165,500 98,476 64,457l28,-98C124,379 177,396 227,396C302,396 316,359 316,333l0,-7C143,327 29,266 29,139C29,61 88,-11 187,-11C245,-11 295,10 327,49l3,0l9,-49l137,0C471,27 469,72 469,119M321,178C321,169 320,160 318,152C308,121 277,96 239,96C205,96 179,115 179,154C179,213 241,232 321,231z"/>
<glyph unicode="d" horiz-adv-x="596" d="M383,710l0,-265l-2,0C359,479 312,500 252,500C135,500 32,405 33,240C33,88 126,-11 242,-11C305,-11 364,17 395,72l2,0l7,-72l135,0C537,33 535,91 535,146l0,564M383,218C383,205 382,194 379,183C370,140 335,110 291,110C228,110 187,162 187,245C187,322 223,384 292,384C339,384 372,350 381,309C382,301 383,290 383,282z"/>
<glyph unicode="e" horiz-adv-x="528" d="M493,196C495,207 498,230 498,256C498,377 438,500 280,500C110,500 33,363 33,239C33,86 128,-11 294,-11C360,-11 421,0 471,20l-20,103C410,110 368,103 316,103C245,103 183,133 178,196M177,300C181,341 207,399 271,399C341,399 357,337 357,300z"/>
<glyph unicode="g" horiz-adv-x="585" d="M524,344C524,417 526,458 528,489l-132,0l-5,-58l-2,0C364,471 322,500 255,500C135,500 33,400 33,243C33,102 119,4 240,4C296,4 343,27 372,68l2,0l0,-32C374,-54 319,-93 247,-93C190,-93 136,-74 105,-56l-30,-115C119,-196 187,-209 251,-209C323,-209 396,-195 450,-149C505,-100 524,-23 524,71M372,218C372,205 371,189 367,178C357,142 326,116 287,116C223,116 187,173 187,246C187,335 232,385 287,385C329,385 358,358 369,318C371,310 372,300 372,290z"/>
<glyph unicode="i" horiz-adv-x="274" d="M213,0l0,489l-152,0l0,-489M137,702C88,702 56,669 57,625C56,583 88,549 136,549C186,549 218,583 218,625C217,669 186,702 137,702z"/>
<glyph unicode="n" horiz-adv-x="586" d="M61,0l152,0l0,282C213,296 215,310 219,320C229,348 254,377 296,377C351,377 373,334 373,271l0,-271l152,0l0,290C525,434 450,500 350,500C269,500 219,453 199,422l-3,0l-7,67l-132,0C59,446 61,394 61,333z"/>
<glyph unicode="o" horiz-adv-x="577" d="M294,500C137,500 33,399 33,241C33,83 143,-11 286,-11C417,-11 544,71 544,250C544,397 444,500 294,500M290,392C359,392 387,318 387,245C387,157 349,98 290,98C225,98 190,161 190,245C190,317 217,392 290,392z"/>
<glyph unicode="1" horiz-adv-x="555" d="M236,0l147,0l0,650l-126,0l-172,-80l25,-114l124,59l2,0z"/>
<glyph unicode="r" horiz-adv-x="380" d="M61,0l152,0l0,248C213,260 214,272 216,282C226,329 264,359 320,359C337,359 350,357 361,354l0,144C350,500 343,500 329,500C282,500 222,470 197,399l-4,0l-5,90l-131,0C59,447 61,400 61,328z"/>
<glyph unicode=" " horiz-adv-x="202"/>
<glyph unicode="t" horiz-adv-x="367" d="M82,581l0,-92l-65,0l0,-112l65,0l0,-200C82,108 96,61 124,32C148,7 189,-11 238,-11C281,-11 317,-5 337,3l-1,115C321,115 312,114 291,114C245,114 231,141 231,200l0,177l109,0l0,112l-109,0l0,133z"/>
<glyph unicode="3" horiz-adv-x="555" d="M38,35C76,11 147,-11 231,-11C396,-11 497,73 497,188C497,273 434,332 356,346l0,2C436,376 475,430 475,499C475,588 398,661 259,661C175,661 97,637 58,612l31,-110C116,518 172,541 225,541C289,541 321,512 321,473C321,418 256,399 205,398l-59,0l0,-109l62,0C275,289 339,260 339,196C339,147 299,109 220,109C158,109 96,135 69,149z"/>
<glyph unicode="2" horiz-adv-x="555" d="M503,0l0,125l-245,0l0,2l60,50C412,261 491,348 491,457C491,575 410,661 263,661C175,661 99,631 50,594l43,-109C127,511 176,539 232,539C307,539 339,497 339,444C337,368 268,295 126,168l-84,-76l0,-92z"/>
</font>
<font horiz-adv-x="1000">
<!-- Copyright 2000, 2004 , 2005Adobe Systems Incorporated. All Rights Reserved. U.S. Patent D454,582.Myriad is a registered trademark of Adobe Systems Incorporated. -->
<font-face font-family="MyriadPro-Regular" units-per-em="1000" underline-position="-100" underline-thickness="50"/>
<missing-glyph horiz-adv-x="500" d="M0,0l500,0l0,700l-500,0M250,395l-170,255l340,0M280,350l170,255l0,-510M80,50l170,255l170,-255M50,605l170,-255l-170,-255z"/>
<glyph unicode="a" horiz-adv-x="482" d="M413,297C413,394 377,495 229,495C168,495 110,478 70,452l20,-58C124,416 171,430 216,430C315,430 326,358 326,318l0,-10C139,309 35,245 35,128C35,58 85,-11 183,-11C252,-11 304,23 331,61l3,0l7,-61l80,0C415,33 413,74 413,116M328,163C328,154 326,144 323,135C309,94 269,54 206,54C161,54 123,81 123,138C123,232 232,249 328,247z"/>
<glyph unicode="b" horiz-adv-x="569" d="M73,125C73,82 71,33 69,0l76,0l4,80l3,0C188,16 244,-11 314,-11C422,-11 531,75 531,248C532,395 447,495 327,495C249,495 193,460 162,406l-2,0l0,304l-87,0M160,281C160,295 163,307 165,317C183,384 239,425 299,425C393,425 443,342 443,245C443,134 388,59 296,59C232,59 181,101 164,162C162,172 160,183 160,194z"/>
<glyph unicode="e" horiz-adv-x="501" d="M462,226C463,235 465,249 465,267C465,356 423,495 265,495C124,495 38,380 38,234C38,88 127,-11 276,-11C353,-11 406,6 437,20l-15,63C389,69 351,58 288,58C200,58 124,107 122,226M123,289C130,350 169,432 258,432C357,432 381,345 380,289z"/>
<glyph unicode="f" horiz-adv-x="292" d="M169,0l0,417l117,0l0,67l-117,0l0,26C169,584 188,650 263,650C288,650 306,645 319,639l12,68C314,714 287,721 256,721C215,721 171,708 138,676C97,637 82,575 82,507l0,-23l-68,0l0,-67l68,0l0,-417z"/>
<glyph unicode="g" horiz-adv-x="559" d="M486,351C486,410 488,449 490,484l-77,0l-4,-73l-2,0C386,451 340,495 256,495C145,495 38,402 38,238C38,104 124,2 244,2C319,2 371,38 398,83l2,0l0,-54C400,-93 334,-140 244,-140C184,-140 134,-122 102,-102l-22,-67C119,-195 183,-209 241,-209C302,-209 370,-195 417,-151C464,-109 486,-41 486,70M399,206C399,191 397,174 392,159C373,103 324,69 270,69C175,69 127,148 127,243C127,355 187,426 271,426C335,426 378,384 394,333C398,321 399,308 399,293z"/>
<glyph unicode="h" horiz-adv-x="555" d="M73,0l88,0l0,292C161,309 162,322 167,334C183,382 228,422 285,422C368,422 397,356 397,278l0,-278l88,0l0,288C485,455 381,495 316,495C283,495 252,485 226,470C199,455 177,433 163,408l-2,0l0,302l-88,0z"/>
<glyph unicode="i" horiz-adv-x="234" d="M161,0l0,484l-88,0l0,-484M117,675C85,675 62,651 62,620C62,590 84,566 115,566C150,566 172,590 171,620C171,651 150,675 117,675z"/>
<glyph unicode="l" horiz-adv-x="236" d="M73,0l88,0l0,710l-88,0z"/>
<glyph unicode="m" horiz-adv-x="834" d="M73,0l86,0l0,292C159,307 161,322 166,335C180,379 220,423 275,423C342,423 376,367 376,290l0,-290l86,0l0,299C462,315 465,331 469,343C484,386 523,423 573,423C644,423 678,367 678,274l0,-274l86,0l0,285C764,453 669,495 605,495C559,495 527,483 498,461C478,446 459,425 444,398l-2,0C421,455 371,495 305,495C225,495 180,452 153,406l-3,0l-4,78l-77,0C72,444 73,403 73,353z"/>
<glyph unicode="n" horiz-adv-x="555" d="M73,0l88,0l0,291C161,306 163,321 167,332C182,381 227,422 285,422C368,422 397,357 397,279l0,-279l88,0l0,289C485,455 381,495 314,495C234,495 178,450 154,404l-2,0l-5,80l-78,0C72,444 73,403 73,353z"/>
<glyph unicode="o" horiz-adv-x="549" d="M278,495C144,495 38,400 38,238C38,85 139,-11 270,-11C387,-11 511,67 511,246C511,394 417,495 278,495M276,429C380,429 421,325 421,243C421,134 358,55 274,55C188,55 127,135 127,241C127,333 172,429 276,429z"/>
<glyph unicode="p" horiz-adv-x="569" d="M73,-198l87,0l0,263l2,0C191,17 247,-11 311,-11C425,-11 531,75 531,249C531,396 443,495 326,495C247,495 190,460 154,401l-2,0l-4,83l-79,0C71,438 73,388 73,326M160,280C160,292 163,305 166,316C183,382 239,425 299,425C392,425 443,342 443,245C443,134 389,58 296,58C233,58 180,100 164,161C162,172 160,184 160,197z"/>
<glyph unicode="r" horiz-adv-x="327" d="M73,0l87,0l0,258C160,273 162,287 164,299C176,365 220,412 282,412C294,412 303,411 312,409l0,83C304,494 297,495 287,495C228,495 175,454 153,389l-4,0l-3,95l-77,0C72,439 73,390 73,333z"/>
<glyph unicode=" " horiz-adv-x="212"/>
<glyph unicode="t" horiz-adv-x="331" d="M93,573l0,-89l-75,0l0,-67l75,0l0,-264C93,96 102,53 127,27C148,3 181,-11 222,-11C256,-11 283,-5 300,2l-4,66C283,64 269,62 245,62C196,62 179,96 179,156l0,261l126,0l0,67l-126,0l0,116z"/>
</font>
<g>
<g>
<polyline fill="none" stroke="#000000" stroke-miterlimit="10" points="418.2,95.1 418.2,101.1 412.2,101.1 "/>
<line fill="none" stroke="#000000" stroke-miterlimit="10" stroke-dasharray="12.2842,12.2842" x1="399.9" y1="101.1" x2="37.5" y2="101.1"/>
<polyline fill="none" stroke="#000000" stroke-miterlimit="10" points="31.4,101.1 25.4,101.1 25.4,95.1 "/>
<line fill="none" stroke="#000000" stroke-miterlimit="10" stroke-dasharray="11.6852,11.6852" x1="25.4" y1="83.4" x2="25.4" y2="42.5"/>
<polyline fill="none" stroke="#000000" stroke-miterlimit="10" points="25.4,36.6 25.4,30.6 31.4,30.6 "/>
<line fill="none" stroke="#000000" stroke-miterlimit="10" stroke-dasharray="12.2842,12.2842" x1="43.6" y1="30.6" x2="406" y2="30.6"/>
<polyline fill="none" stroke="#000000" stroke-miterlimit="10" points="412.2,30.6 418.2,30.6 418.2,36.6 "/>
<line fill="none" stroke="#000000" stroke-miterlimit="10" stroke-dasharray="11.6852,11.6852" x1="418.2" y1="48.3" x2="418.2" y2="89.2"/>
</g>
</g>
<rect x="14.7" y="19.5" fill="none" stroke="#000000" stroke-miterlimit="10" width="414.1" height="92.7"/>
<g>
<rect x="34" y="58.4" fill="#349980" stroke="#000000" stroke-miterlimit="10" width="87.4" height="20.8"/>
<text transform="matrix(1 0 0 1 55.2495 72.3085)" font-family="'MyriadPro-Bold'" font-size="12">Widget 1</text>
</g>
<g>
<rect x="144.1" y="48.3" fill="#349980" stroke="#000000" stroke-miterlimit="10" width="102.2" height="41"/>
<text transform="matrix(1 0 0 1 172.7653 72.3085)" font-family="'MyriadPro-Bold'" font-size="12">Widget 2</text>
</g>
<g>
<rect x="275.8" y="44" fill="#349980" stroke="#000000" stroke-miterlimit="10" width="120" height="42.2"/>
<text transform="matrix(1 0 0 1 313.3092 68.6263)" font-family="'MyriadPro-Bold'" font-size="12">Widget 3</text>
</g>
<g>
<g>
<line fill="none" stroke="#000000" stroke-miterlimit="10" x1="15.9" y1="35.1" x2="24.2" y2="35.1"/>
<g>
<polygon points="17.9,36.8 16.2,35.1 17.9,33.4 16.4,33.4 14.7,35.1 16.4,36.8 "/>
</g>
<g>
<polygon points="22.2,36.8 23.9,35.1 22.2,33.4 23.7,33.4 25.4,35.1 23.7,36.8 "/>
</g>
</g>
</g>
<g>
<g>
<line fill="none" stroke="#000000" stroke-miterlimit="10" x1="419.3" y1="32.1" x2="427.7" y2="32.1"/>
<g>
<polygon points="421.3,33.8 419.6,32.1 421.3,30.4 419.9,30.4 418.2,32.1 419.9,33.8 "/>
</g>
<g>
<polygon points="425.7,33.8 427.4,32.1 425.7,30.4 427.1,30.4 428.8,32.1 427.1,33.8 "/>
</g>
</g>
</g>
<g>
<line fill="#13007C" x1="395.8" y1="69.1" x2="418.2" y2="69.1"/>
<g>
<line fill="none" stroke="#FF8000" stroke-miterlimit="10" x1="396.9" y1="69.1" x2="417" y2="69.1"/>
<g>
<polygon fill="#FF8000" points="398.9,70.8 397.2,69.1 398.9,67.4 397.5,67.4 395.8,69.1 397.5,70.8 "/>
</g>
<g>
<polygon fill="#FF8000" points="415,70.8 416.7,69.1 415,67.4 416.5,67.4 418.2,69.1 416.5,70.8 "/>
</g>
</g>
</g>
<g>
<line fill="#13007C" x1="245.7" y1="69.1" x2="275.8" y2="69.1"/>
<g>
<line fill="none" stroke="#FF8000" stroke-miterlimit="10" x1="246.9" y1="69.1" x2="274.6" y2="69.1"/>
<g>
<polygon fill="#FF8000" points="248.9,70.8 247.2,69.1 248.9,67.4 247.4,67.4 245.7,69.1 247.4,70.8 "/>
</g>
<g>
<polygon fill="#FF8000" points="272.6,70.8 274.3,69.1 272.6,67.4 274.1,67.4 275.8,69.1 274.1,70.8 "/>
</g>
</g>
</g>
<g>
<g>
<line fill="none" stroke="#FF8000" stroke-miterlimit="10" x1="122.5" y1="68.8" x2="143" y2="68.8"/>
<g>
<polygon fill="#FF8000" points="124.5,70.5 122.8,68.8 124.5,67.1 123.1,67.1 121.4,68.8 123.1,70.5 "/>
</g>
<g>
<polygon fill="#FF8000" points="141,70.5 142.7,68.8 141,67.1 142.4,67.1 144.1,68.8 142.4,70.5 "/>
</g>
</g>
</g>
<g>
<line fill="#13007C" x1="25.4" y1="71.1" x2="34" y2="71.1"/>
<g>
<line fill="none" stroke="#FF8000" stroke-miterlimit="10" x1="26.5" y1="71.1" x2="32.9" y2="71.1"/>
<g>
<polygon fill="#FF8000" points="28.5,72.8 26.8,71.1 28.5,69.4 27.1,69.4 25.4,71.1 27.1,72.8 "/>
</g>
<g>
<polygon fill="#FF8000" points="30.9,72.8 32.6,71.1 30.9,69.4 32.3,69.4 34,71.1 32.3,72.8 "/>
</g>
</g>
</g>
<g>
<line fill="#13007C" x1="73.2" y1="58.9" x2="73.2" y2="30.6"/>
<g>
<line fill="none" stroke="#FF8000" stroke-miterlimit="10" x1="73.2" y1="57.8" x2="73.2" y2="31.8"/>
<g>
<polygon fill="#FF8000" points="74.9,55.8 73.2,57.5 71.5,55.8 71.5,57.2 73.2,58.9 74.9,57.2 "/>
</g>
<g>
<polygon fill="#FF8000" points="74.9,33.8 73.2,32.1 71.5,33.8 71.5,32.3 73.2,30.6 74.9,32.3 "/>
</g>
</g>
</g>
<g>
<line fill="#13007C" x1="73.2" y1="100.7" x2="73.2" y2="79.2"/>
<g>
<line fill="none" stroke="#FF8000" stroke-miterlimit="10" x1="73.2" y1="99.6" x2="73.2" y2="80.3"/>
<g>
<polygon fill="#FF8000" points="74.9,97.6 73.2,99.3 71.5,97.6 71.5,99 73.2,100.7 74.9,99 "/>
</g>
<g>
<polygon fill="#FF8000" points="74.9,82.3 73.2,80.6 71.5,82.3 71.5,80.9 73.2,79.2 74.9,80.9 "/>
</g>
</g>
</g>
<g>
<line fill="#13007C" x1="195.2" y1="100.6" x2="195.2" y2="89.9"/>
<g>
<line fill="none" stroke="#FF8000" stroke-miterlimit="10" x1="195.2" y1="99.4" x2="195.2" y2="91.1"/>
<g>
<polygon fill="#FF8000" points="196.9,97.4 195.2,99.1 193.5,97.4 193.5,98.9 195.2,100.6 196.9,98.9 "/>
</g>
<g>
<polygon fill="#FF8000" points="196.9,93.1 195.2,91.4 193.5,93.1 193.5,91.6 195.2,89.9 196.9,91.6 "/>
</g>
</g>
</g>
<g>
<line fill="#13007C" x1="189.9" y1="48.3" x2="189.9" y2="30.6"/>
<g>
<line fill="none" stroke="#FF8000" stroke-miterlimit="10" x1="189.9" y1="47.1" x2="189.9" y2="31.8"/>
<g>
<polygon fill="#FF8000" points="191.6,45.1 189.9,46.8 188.2,45.1 188.2,46.6 189.9,48.3 191.6,46.6 "/>
</g>
<g>
<polygon fill="#FF8000" points="191.6,33.8 189.9,32.1 188.2,33.8 188.2,32.3 189.9,30.6 191.6,32.3 "/>
</g>
</g>
</g>
<g>
<line fill="#13007C" x1="330.9" y1="101.1" x2="330.9" y2="86.2"/>
<g>
<line fill="none" stroke="#FF8000" stroke-miterlimit="10" x1="330.9" y1="99.9" x2="330.9" y2="87.3"/>
<g>
<polygon fill="#FF8000" points="332.6,97.9 330.9,99.6 329.2,97.9 329.2,99.4 330.9,101.1 332.6,99.4 "/>
</g>
<g>
<polygon fill="#FF8000" points="332.6,89.3 330.9,87.6 329.2,89.3 329.2,87.9 330.9,86.2 332.6,87.9 "/>
</g>
</g>
</g>
<g>
<line fill="#13007C" x1="330.9" y1="44" x2="330.9" y2="30.6"/>
<g>
<line fill="none" stroke="#FF8000" stroke-miterlimit="10" x1="330.9" y1="42.9" x2="330.9" y2="31.8"/>
<g>
<polygon fill="#FF8000" points="332.6,40.9 330.9,42.6 329.2,40.9 329.2,42.3 330.9,44 332.6,42.3 "/>
</g>
<g>
<polygon fill="#FF8000" points="332.6,33.8 330.9,32.1 329.2,33.8 329.2,32.3 330.9,30.6 332.6,32.3 "/>
</g>
</g>
</g>
<g>
<g>
<line fill="none" stroke="#000000" stroke-miterlimit="10" x1="175.8" y1="111" x2="175.8" y2="102.7"/>
<g>
<polygon points="177.5,109.1 175.8,110.7 174.1,109.1 174.1,110.5 175.8,112.2 177.5,110.5 "/>
</g>
<g>
<polygon points="177.5,104.7 175.8,103 174.1,104.7 174.1,103.2 175.8,101.6 177.5,103.2 "/>
</g>
</g>
</g>
<g>
<g>
<line fill="none" stroke="#000000" stroke-miterlimit="10" x1="172.8" y1="29" x2="172.8" y2="20.7"/>
<g>
<polygon points="174.5,27 172.8,28.7 171.1,27 171.1,28.4 172.8,30.1 174.5,28.4 "/>
</g>
<g>
<polygon points="174.5,22.6 172.8,20.9 171.1,22.6 171.1,21.2 172.8,19.5 174.5,21.2 "/>
</g>
</g>
</g>
<text transform="matrix(1 0 0 1 175.7678 27.3395)" font-family="'MyriadPro-Regular'" font-size="10">top margin</text>
<text transform="matrix(1 0 0 1 178.9794 109.875)" font-family="'MyriadPro-Regular'" font-size="10">bottom margin</text>
<text transform="matrix(1 0 0 1 1.220703e-04 16.7404)" font-family="'MyriadPro-Regular'" font-size="10">left margin</text>
<text transform="matrix(1 0 0 1 397.8374 16.7405)" font-family="'MyriadPro-Regular'" font-size="10">right margin</text>
<text transform="matrix(1 0 0 1 199.7852 8.3701)" font-family="'MyriadPro-Bold'" font-size="10">Container</text>
</svg>

After

Width:  |  Height:  |  Size: 17 KiB

View File

@@ -0,0 +1,13 @@
.. _uses:
Kiwisolver real uses
====================
The following sections describe how kiwisolver is used in third-party project,
and aim at providing more involved example of how kiwisolver can be used in
real life project.
.. toctree::
:maxdepth: 2
enaml.rst