mirror of https://github.com/kivy/kivy.git
217 lines
9.4 KiB
ReStructuredText
217 lines
9.4 KiB
ReStructuredText
|
.. _architecture:
|
||
|
|
||
|
Architectural Overview
|
||
|
======================
|
||
|
|
||
|
We would like to take a moment to explain how we designed Kivy from a
|
||
|
software engineering point of view. This is key to understanding how
|
||
|
everything works together.
|
||
|
If you just look at the code, chances are you will get a rough idea
|
||
|
already, but since this approach certainly is daunting for most users,
|
||
|
this section explains the basic ideas of the implementation in more detail.
|
||
|
|
||
|
Kivy consists of several building blocks that we will explain in the
|
||
|
following.
|
||
|
|
||
|
|
||
|
Core Providers and Input Providers
|
||
|
----------------------------------
|
||
|
|
||
|
One idea that is key to understanding Kivy's internals is that of modularity and
|
||
|
abstraction. We try to abstract from basic tasks such as opening a window,
|
||
|
displaying images and text, playing audio, getting images from a camera,
|
||
|
spelling correction and so on. We call these *core* tasks.
|
||
|
This makes the API both easy to use and easy to extend. Most importantly, it
|
||
|
allows us to use -- what we call -- specific providers for the respective
|
||
|
scenario in which your app is being run.
|
||
|
For example, on OSX, Linux and Windows, there are different native APIs for the
|
||
|
different core tasks. A piece of code that uses one of these specific APIs to
|
||
|
talk to the operating system on one side and to Kivy on the other (acting as an
|
||
|
intermediate communication layer) is what we call a *core provider*.
|
||
|
The advantage of using specialized core providers for each platform is that we
|
||
|
can fully leverage the functionality exposed by the operating system and act as
|
||
|
efficiently as possible. It also gives users a choice. Furthermore, by using
|
||
|
libraries that are shipped with any one platform, we effectively reduce the size
|
||
|
of the Kivy distribution and make packaging easier. It's also easier to port
|
||
|
Kivy to other platforms. The Android port did greatly benefit from this.
|
||
|
|
||
|
We follow the same concept with input handling. *An input provider* is a piece
|
||
|
of code that adds support for a specific input device, such as Apple's
|
||
|
trackpads, TUIO or a mouse emulator.
|
||
|
If you need to add support for a new input device, you can simply provide a new
|
||
|
class that reads your input data from your device and transforms them into Kivy
|
||
|
basic events.
|
||
|
|
||
|
|
||
|
Graphics
|
||
|
--------
|
||
|
|
||
|
Kivy's graphics API is our abstraction of OpenGL. On the lowest level,
|
||
|
Kivy issues hardware-accelerated drawing commands using OpenGL. Writing
|
||
|
OpenGL code however can be a bit confusing, especially to newcomers.
|
||
|
That's why we provide the graphics API that lets you draw things using
|
||
|
simple metaphors that do not exist as such in OpenGL (e.g. Canvas,
|
||
|
Rectangle, etc.).
|
||
|
|
||
|
All of our widgets themselves use this graphics API, which is implemented
|
||
|
on the C level for performance reasons.
|
||
|
|
||
|
Another advantage of the graphics API is its ability to automatically
|
||
|
optimize the drawing commands that your code issues. This is especially
|
||
|
helpful if you're not an expert at tuning OpenGL. This makes your drawing
|
||
|
code more efficient in many cases.
|
||
|
|
||
|
You can, of course, still use raw OpenGL commands if you prefer that. The
|
||
|
version we target is OpenGL 2.0 ES (GLES2) on all devices, so if you want to
|
||
|
stay cross-platform compatible, we advise you to only use the GLES2 functions.
|
||
|
|
||
|
|
||
|
Core
|
||
|
----
|
||
|
|
||
|
The code in the core package provides commonly used features, such as:
|
||
|
|
||
|
Clock
|
||
|
You can use the clock to schedule timer events. Both one-shot timers
|
||
|
and periodic timers are supported
|
||
|
|
||
|
Cache
|
||
|
If you need to cache something that you use often, you can use our
|
||
|
class for that instead of writing your own.
|
||
|
|
||
|
Gesture Detection
|
||
|
We ship a simple gesture recognizer that you can use to detect
|
||
|
various kinds of strokes, such as circles or rectangles. You can
|
||
|
train it to detect your own strokes.
|
||
|
|
||
|
Kivy Language
|
||
|
The kivy language is used to easily and efficiently describe user
|
||
|
interfaces.
|
||
|
|
||
|
Properties
|
||
|
These are not the normal properties that you may know from python.
|
||
|
It is our own properties class that links your widget code with
|
||
|
the user interface description.
|
||
|
|
||
|
|
||
|
UIX (Widgets & Layouts)
|
||
|
-----------------------
|
||
|
|
||
|
The UIX module contains commonly used widgets and layouts that you can
|
||
|
reuse to quickly create a user interface.
|
||
|
|
||
|
Widgets
|
||
|
Widgets are user interface elements that you add to your program
|
||
|
to provide some kind of functionality. They may or may not be
|
||
|
visible. Examples would be a file browser, buttons, sliders, lists
|
||
|
and so on. Widgets receive MotionEvents.
|
||
|
|
||
|
Layouts
|
||
|
You use layouts to arrange widgets. It is of course possible to
|
||
|
calculate your widgets' positions yourself, but often it is more
|
||
|
convenient to use one of our ready made layouts. Examples would be
|
||
|
Grid Layouts or Box Layouts.
|
||
|
You can also nest layouts.
|
||
|
|
||
|
|
||
|
Modules
|
||
|
-------
|
||
|
|
||
|
If you've ever used a modern web browser and customized it with some
|
||
|
add-ons then you already know the basic idea behind our module classes.
|
||
|
Modules can be used to inject functionality into Kivy programs, even if
|
||
|
the original author did not include it.
|
||
|
|
||
|
An example would be a module that always shows the FPS of the current
|
||
|
application and some graph depicting the FPS over time.
|
||
|
|
||
|
You can also write your own modules.
|
||
|
|
||
|
|
||
|
Input Events (Touches)
|
||
|
----------------------
|
||
|
|
||
|
Kivy abstracts from different input types and sources such as touches, mice,
|
||
|
TUIO or similar. What all of these input types have in common is that you
|
||
|
can associate a 2D onscreen-position with any individual input event. (There are
|
||
|
other input devices such as accelerometers where you cannot easily find a
|
||
|
2D position for e.g. a tilt of your device. This kind of input is handled
|
||
|
separately. In the following we describe the former types.)
|
||
|
|
||
|
All of these input types are represented by instances of the Touch()
|
||
|
class. (Note that this does not only refer to finger touches, but all the other
|
||
|
input types as well. We just called it *Touch* for the sake of simplicity.
|
||
|
Think of it of something that *touches* the user interface or your screen.)
|
||
|
A touch instance, or object, can be in one of three states. When a touch
|
||
|
enters one of these states, your program is informed that the event
|
||
|
occurred.
|
||
|
The three states a touch can be in are:
|
||
|
|
||
|
Down
|
||
|
A touch is down only once, at the very moment where it first
|
||
|
appears.
|
||
|
Move
|
||
|
A touch can be in this state for a potentially unlimited time.
|
||
|
A touch does not have to be in this state during its lifetime.
|
||
|
A 'Move' happens whenever the 2D position of a touch changes.
|
||
|
Up
|
||
|
A touch goes up at most once, or never.
|
||
|
In practice you will almost always receive an up event because
|
||
|
nobody is going to hold a finger on the screen for all eternity,
|
||
|
but it is not guaranteed. If you know the input sources your users
|
||
|
will be using, you will know whether or not you can rely on this
|
||
|
state being entered.
|
||
|
|
||
|
|
||
|
Widgets and Event Dispatching
|
||
|
-----------------------------
|
||
|
|
||
|
The term *widget* is often used in GUI programming contexts to describe
|
||
|
some part of the program that the user interacts with.
|
||
|
For Kivy, a widget is an object that receives input events. It does not
|
||
|
necessarily have to have a visible representation on the screen.
|
||
|
All widgets are arranged in a *widget tree* (which is a tree data structure
|
||
|
as known from computer science classes): One widget can have any number of
|
||
|
child widgets or none. There is exactly one *root widget* at the top of the
|
||
|
tree that has no parent widget, and all other widgets are directly or
|
||
|
indirectly children of this widget (which is why it's called the root).
|
||
|
|
||
|
When new input data is available, Kivy sends out one event per touch.
|
||
|
The root widget of the widget tree first receives the event.
|
||
|
Depending on the state of the touch, the on_touch_down,
|
||
|
on_touch_move or on_touch_up event is dispatched (with the touch as the
|
||
|
argument) to the root widget, which results in the root widget's
|
||
|
corresponding on_touch_down, on_touch_move or on_touch_up event handler
|
||
|
being called.
|
||
|
|
||
|
Each widget (this includes the root widget) in the tree can choose to
|
||
|
either digest or pass the event further. If an event handler returns True
|
||
|
it means that the event has been digested and handled properly. No further
|
||
|
processing will happen with that event. Otherwise, the event handler
|
||
|
passes the widget on to its own children by calling its superclass's
|
||
|
implementation of the respective event handler. This goes all the way up
|
||
|
to the base Widget class, which -- in its touch event handlers -- does
|
||
|
nothing but pass the touches to its children::
|
||
|
|
||
|
def on_touch_down(self, touch): # This is the same for move/up
|
||
|
for child in reversed(self.children[:]):
|
||
|
if child.dispatch('on_touch_down', touch):
|
||
|
return True
|
||
|
|
||
|
This really is much easier than it first seems. Let's take a look at a
|
||
|
simple example. If you want to implement a line drawing program, you will
|
||
|
want to know when a touch starts, moves and ends. You keep track of the
|
||
|
touch's positions and draw a line through those points::
|
||
|
|
||
|
TODO PAINTER WIDGET
|
||
|
|
||
|
As you can see, this widget does not really care where the touch occurred.
|
||
|
Often times you will want to restrict the *area* on the screen that a
|
||
|
widget watches for touches. You can use a widget's collide_point() method
|
||
|
to achieve this. You simply pass it the touches position and it returns
|
||
|
True if the touch is within the 'watched area' or False otherwise. By
|
||
|
default, this checks the rectangular region on the screen that's described
|
||
|
by the widget's pos (for position; x & y) and size (width & height), but
|
||
|
you can override this behaviour in your own class.
|
||
|
|