first commit

This commit is contained in:
Yura 2024-09-15 15:12:16 +03:00
commit 417e54da96
5696 changed files with 900003 additions and 0 deletions

View file

@ -0,0 +1,98 @@
'''
Shuffled Camera Feed Puzzle
===========================
This demonstrates using Scatter widgets with a live camera.
You should see a shuffled grid of rectangles that make up the
camera feed. You can drag the squares around to see the
unscrambled camera feed or double click to scramble the grid
again.
'''
from kivy.app import App
from kivy.uix.camera import Camera
from kivy.uix.widget import Widget
from kivy.uix.slider import Slider
from kivy.uix.scatter import Scatter
from kivy.animation import Animation
from kivy.graphics import Color, Rectangle
from kivy.properties import NumericProperty
from random import randint, random
from functools import partial
class Puzzle(Camera):
blocksize = NumericProperty(100)
def on_texture_size(self, instance, value):
self.build()
def on_blocksize(self, instance, value):
self.build()
def build(self):
self.clear_widgets()
texture = self.texture
if not texture:
return
bs = self.blocksize
tw, th = self.texture_size
for x in range(int(tw / bs)):
for y in range(int(th / bs)):
bx = x * bs
by = y * bs
subtexture = texture.get_region(bx, by, bs, bs)
# node = PuzzleNode(texture=subtexture,
# size=(bs, bs), pos=(bx, by))
node = Scatter(pos=(bx, by), size=(bs, bs))
with node.canvas:
Color(1, 1, 1)
Rectangle(size=node.size, texture=subtexture)
self.add_widget(node)
self.shuffle()
def shuffle(self):
texture = self.texture
bs = self.blocksize
tw, th = self.texture_size
count = int(tw / bs) * int(th / bs)
indices = list(range(count))
childindex = 0
while indices:
index = indices.pop(randint(0, len(indices) - 1))
x = bs * (index % int(tw / bs))
y = bs * int(index / int(tw / bs))
child = self.children[childindex]
a = Animation(d=random() / 4.) + Animation(pos=(x, y),
t='out_quad', d=.4)
a.start(child)
childindex += 1
def on_touch_down(self, touch):
if touch.is_double_tap:
self.shuffle()
return True
super(Puzzle, self).on_touch_down(touch)
class PuzzleApp(App):
def build(self):
root = Widget()
puzzle = Puzzle(resolution=(640, 480), play=True)
slider = Slider(min=100, max=200, step=10, size=(800, 50))
slider.bind(value=partial(self.on_value, puzzle))
root.add_widget(puzzle)
root.add_widget(slider)
return root
def on_value(self, puzzle, instance, value):
value = int((value + 5) / 10) * 10
puzzle.blocksize = value
instance.value = value
PuzzleApp().run()

View file

@ -0,0 +1,15 @@
#:kivy 1.4
AnchorLayout:
anchor_x: "right"
anchor_y: "bottom"
Button:
text: "Button 1"
size_hint: .2, .4
Button:
text: "Button 2"
size_hint: .4, .2
Button:
text: "Button 3"
size_hint: .2, .2

View file

@ -0,0 +1,14 @@
#:kivy 1.4
BoxLayout:
orientation: 'vertical'
padding: 20
spacing: 10
Button:
text: "Button 1"
size_hint: 1, None
Button:
text: "Button 2"
size_hint: 1, 0.5
Button:
text: "Button 3"

View file

@ -0,0 +1,27 @@
#:kivy 1.4
GridLayout:
cols: 2
Button:
text: "Button 1"
Button:
text: "Button 2"
font_size: 24
Button:
text: "Button 3"
background_color: .7, .7, 1, 1
Button:
text: "Button 4"
on_press: self.text = 'pressed'
on_release: self.text = 'Button 4'
ToggleButton:
text: "A toggle button"
ToggleButton:
text: "a toggle button in a group"
group: "money"
ToggleButton:
text: "A toggle in the down state"
state: "down"
ToggleButton:
text: "another toggle button in a group"
group: "money"

View file

@ -0,0 +1,28 @@
#:kivy 1.4
GridLayout:
cols: 2
CheckBox:
Label:
text: "A checkbox"
CheckBox:
active: True
Label:
text: "Another checkbox"
CheckBox:
group: "money"
Label:
text: "A radio in a group"
CheckBox:
group: "money"
active: True
Label:
text: "Another radio in same group"
Switch:
Label:
text: "A Switch"
Switch:
active: True
Label:
text: "An active switch"

View file

@ -0,0 +1,19 @@
#:kivy 1.4
BoxLayout:
# Double as a Tabbed Panel Demo!
TabbedPanel:
tab_pos: "top_right"
default_tab_text: "List View"
default_tab_content: list_view_tab
TabbedPanelHeader:
text: 'Icon View'
content: icon_view_tab
FileChooserListView:
id: list_view_tab
FileChooserIconView:
id: icon_view_tab
show_hidden: True

View file

@ -0,0 +1,16 @@
#:kivy 1.4
FloatLayout:
Button:
text: "Button 1"
pos: 100, 100
size_hint: .2, .4
Button:
text: "Button 2"
pos: 200, 200
size_hint: .4, .2
Button:
text: "Button 3"
pos_hint: {'x': .8, 'y': .6}
size_hint: .2, .2

View file

@ -0,0 +1,20 @@
#:kivy 1.4
GridLayout:
cols: 2
Button:
text: "Button 1"
size_hint_x: None
width: 100
Button:
text: "Button 2"
Button:
text: "Button 3"
size_hint_x: None
Button:
text: "Button 4"
Button:
text: "Button 5"
size_hint_x: None
Button:
text: "Button 6"

View file

@ -0,0 +1,62 @@
#:kivy 1.4
GridLayout:
cols: 1
Label:
text: "Label crowded by size hint"
size_hint_y: .2
Label:
text: 'Label with\nmultiple\nlines'
size_hint_y: .4
Label:
font_size: '48sp'
text: "Label [color=ff3333][sub]with[/sub][/color] [color=3333ff][b]mark[sup]up[/sup][/b][/color]"
markup: True
Button:
text: 'Labels in buttons'
GridLayout:
cols: 2
size_hint_y: 3
Button:
text: 'Left aligned at middle'
padding: 10, 10
halign: 'left'
valign: 'middle'
text_size: self.size
Button:
text: 'right aligned at top'
padding: 10, 10
halign: 'right'
valign: 'top'
text_size: self.size
Button:
text: 'Left aligned with no padding'
halign: 'left'
valign: 'middle'
text_size: self.size
Button:
text: 'Multiple\nbold italic centered\nlines'
halign: 'center'
valign: 'middle'
bold: True
italic: True
text_size: self.size
Button:
text: 'multiple\nspaced lines\ncentered'
padding: 10, 10
halign: 'center'
valign: 'middle'
line_height: 1.5
text_size: self.size
Button:
text: 'button without\npadding\nor align'
Label:
text: "Label with [ref=reference]reference[/ref]"
markup: True
on_ref_press: self.text = "ref clicked"
Label:
text: "different font"
bold: True
font_name: "data/fonts/RobotoMono-Regular.ttf"
font_size: 32
valign: 'bottom'

View file

@ -0,0 +1,9 @@
#:kivy 1.4
BoxLayout:
orientation: "vertical"
Image:
source: "../../widgets/cityCC0.png"
Video:
source: "../../widgets/cityCC0.mpg"
state: "play"

View file

@ -0,0 +1,4 @@
#:kivy 1.4
RstDocument:
text: "Welcome\n---------------\nThis Kivy Catalog is an interactive showcase of Kivy Widgets defined with the Kivy (.kv) language. You can edit the .kv language description in the left pane and see your changes affect the widgets in the right pane. Your changes will update the widget within a few seconds though the impatient could type 'Ctrl-S' or click 'Render Now'.\n\nYou can explore most Kivy widgets from the menu in the upper left corner. You can also use this playground to test your Kivy language code and adding a new .kv file to the interface is easy.\n\nSome Kivy widgets are omitted from this catalog or could have more complete .kv representation. This is beta software; pull requests are welcome."

View file

@ -0,0 +1,39 @@
#:kivy 1.4
BoxLayout:
id: bl
orientation: "vertical"
popup: popup.__self__
canvas:
Color:
rgba: .18, .18, .18, .91
Rectangle:
size: self.size
pos: self.pos
Bubble:
size_hint: (None, None)
size: (150, self.content_height + self.arrow_margin_y)
pos_hint: {'center_x': .5, 'y': .6}
arrow_pos: 'bottom_mid'
BubbleContent:
orientation: 'horizontal'
size_hint_y: None
height: self.minimum_height
BubbleButton:
text: 'This is'
BubbleButton:
text: 'a'
BubbleButton:
text: 'Bubble'
Button:
text: 'press to show popup'
on_release: root.popup.open()
Popup:
id: popup
on_parent: if self.parent == bl: bl.remove_widget(self)
title: "An example popup"
content: popupcontent
Button:
id: popupcontent
text: "press to dismiss"
on_release: popup.dismiss()

View file

@ -0,0 +1,17 @@
#:kivy 1.4
BoxLayout:
orientation: 'vertical'
padding: 50
ProgressBar:
id: bar
value: 140
max: 300
Slider:
id: slider
max: 200
value: 140
on_value: slider.value = self.value
Slider:
orientation: 'vertical'
on_value: slider.value = self.value

View file

@ -0,0 +1,5 @@
#:kivy 1.4
BoxLayout:
RstDocument:
text: ".. _top:\n\nHello world\n===========\n\nThis is an **emphased text**, some ``interpreted text``.\nAnd this is a reference to top_::\n\n $ print('Hello world')\n"

View file

@ -0,0 +1,16 @@
#:kivy 1.4
FloatLayout:
Scatter:
size_hint: None, None
size: 100, 100
pos: 100, 100
Image:
source: "../../widgets/cityCC0.png"
Scatter:
size_hint: None, None
size: 100, 100
pos: 100, 100
do_rotation: False
Label:
text: "something"

View file

@ -0,0 +1,10 @@
#:kivy 1.4
BoxLayout:
orientation: 'vertical'
Spinner:
text: "Work"
values: "Work", "Home", "Mobile", "Skype"
size_hint: (None, None)
size: (100, 44)
# Wanted to put DropDown here, too, but it seems not to be working too well when loaded from .kv

View file

@ -0,0 +1,25 @@
#:kivy 1.4
StackLayout:
orientation: 'tb-lr'
padding: 10
spacing: 5
Button:
text: "Button 1"
size_hint: .2, .4
width: 100
Button:
text: "Button 2"
size_hint: .2, .4
Button:
text: "Button 3"
size_hint: .2, .4
Button:
text: "Button 4"
size_hint: .2, .4
Button:
text: "Button 5"
size_hint: .2, .4
Button:
text: "Button 6"
size_hint: .2, .4

View file

@ -0,0 +1,20 @@
#:kivy 1.4
BoxLayout:
orientation: "vertical"
TextInput:
text: "Single Line Input"
multiline: False
TextInput:
text: "Text Input, start typing here\nmultiline\nsupport"
background_color: .8, .8, 0, 1
size_hint: 1, 3
TextInput:
password: True
text: "Password (but you can't see it)"
multiline: False
on_text: viewer.text = self.text
TextInput:
id: viewer
readonly: True
text: "edit the password to see it here"

View file

@ -0,0 +1,144 @@
#:kivy 1.4
#:import KivyLexer kivy.extras.highlight.KivyLexer
<Container>:
canvas.before:
Color:
rgb: 0, 0, 0
Rectangle:
pos: self.pos
size: self.size
<Catalog>:
language_box: language_box
screen_manager: screen_manager
auto_reload: chkbx.active
info_label: info_lbl
orientation: 'vertical'
BoxLayout:
padding: '2sp'
canvas:
Color:
rgba: 1, 1, 1, .6
Rectangle:
size: self.size
pos: self.pos
size_hint: 1, None
height: '45sp'
Spinner:
size_hint: None, 1
width: '108sp'
text: 'Welcome'
values: [screen.name for screen in screen_manager.screens]
on_text: root.show_kv(*args)
Widget:
BoxLayout:
size_hint: None, 1
width: '150sp'
Label:
text: "Auto Reload"
CheckBox:
id: chkbx
active: True
size_hint_x: 1
Button:
size_hint: None, 1
width: '108sp'
text: 'Render Now'
on_release: root.change_kv(*args)
BoxLayout:
id: reactive_layout
orientation: 'vertical' if self.width < self.height else 'horizontal'
Splitter:
id: editor_pane
max_size: (reactive_layout.height if self.vertical else reactive_layout.width) - self.strip_size
min_size: sp(30) + self.strip_size
vertical: 1 if reactive_layout.width < reactive_layout.height else 0
sizable_from: 'bottom' if self.vertical else 'right'
size_hint: (1, None) if self.vertical else (None, 1)
size: 400, 400
on_vertical:
mid_size = self.max_size/2
if args[1]: self.height = mid_size
if not args[1]: self.width = mid_size
ScrollView:
id: kr_scroll
KivyRenderTextInput:
catalog: root
id: language_box
auto_indent: True
lexer: KivyLexer()
size_hint: 1, None
height: max(kr_scroll.height, self.minimum_height)
valign: "top"
text: "This box will display the kivy language for whatever has been selected"
on_text: root.schedule_reload()
on_cursor: root.schedule_reload()
ScreenManager:
id: screen_manager
Screen:
name: "Welcome"
PlaygroundContainer:
Screen:
name: "Float Layout"
FloatLayoutContainer
Screen:
name: "Box Layout"
BoxLayoutContainer:
Screen:
name: "Anchor Layout"
AnchorLayoutContainer:
Screen:
name: "Grid Layout"
GridLayoutContainer:
Screen:
name: "Stack Layout"
StackLayoutContainer:
Screen:
name: "Buttons"
ButtonContainer:
Screen:
name: "Labels"
LabelContainer:
Screen:
name: "Booleans"
CheckBoxContainer:
Screen:
name: "Progress Bar"
ProgressBarContainer:
Screen:
name: "Media"
MediaContainer:
Screen:
name: "Text"
TextContainer:
Screen:
name: "Popups"
PopupContainer:
Screen:
name: "Selectors"
SelectorsContainer:
Screen:
name: "File Choosers"
FileChooserContainer:
Screen:
name: "Scatter"
ScatterContainer:
Screen:
name: "ReST"
RestContainer:
FloatLayout:
size_hint: 1, None
height: 0
TextInput:
id:info_lbl
readonly: True
font_size: '14sp'
background_color: (0, 0, 0, 1)
foreground_color: (1, 1, 1, 1)
opacity:0
size_hint: 1, None
text_size: self.size
height: '150pt'
top: 0

View file

@ -0,0 +1,192 @@
'''
Kivy Catalog
============
The Kivy Catalog viewer showcases widgets available in Kivy
and allows interactive editing of kivy language code to get immediate
feedback. You should see a two panel screen with a menu spinner button
(starting with 'Welcome') and other controls across the top.The left pane
contains kivy (.kv) code, and the right side is that code rendered. You can
edit the left pane, though changes will be lost when you use the menu
spinner button. The catalog will show you dozens of .kv examples controlling
different widgets and layouts.
The catalog's interface is set in the file kivycatalog.kv, while the
interfaces for each menu option are set in containers_kvs directory. To
add a new .kv file to the Kivy Catalog, add a .kv file into the container_kvs
directory and reference that file in the ScreenManager section of
kivycatalog.kv.
Known bugs include some issue with the drop
'''
import kivy
kivy.require('1.4.2')
import os
import sys
from kivy.app import App
from kivy.factory import Factory
from kivy.lang import Builder, Parser, ParserException
from kivy.properties import ObjectProperty
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.codeinput import CodeInput
from kivy.animation import Animation
from kivy.clock import Clock
CATALOG_ROOT = os.path.dirname(__file__)
# Config.set('graphics', 'width', '1024')
# Config.set('graphics', 'height', '768')
'''List of classes that need to be instantiated in the factory from .kv files.
'''
CONTAINER_KVS = os.path.join(CATALOG_ROOT, 'container_kvs')
CONTAINER_CLASSES = [c[:-3] for c in os.listdir(CONTAINER_KVS)
if c.endswith('.kv')]
class Container(BoxLayout):
'''A container is essentially a class that loads its root from a known
.kv file.
The name of the .kv file is taken from the Container's class.
We can't just use kv rules because the class may be edited
in the interface and reloaded by the user.
See :meth: change_kv where this happens.
'''
def __init__(self, **kwargs):
super(Container, self).__init__(**kwargs)
self.previous_text = open(self.kv_file).read()
parser = Parser(content=self.previous_text)
widget = Factory.get(parser.root.name)()
Builder._apply_rule(widget, parser.root, parser.root)
self.add_widget(widget)
@property
def kv_file(self):
'''Get the name of the kv file, a lowercase version of the class
name.
'''
return os.path.join(CONTAINER_KVS, self.__class__.__name__ + '.kv')
for class_name in CONTAINER_CLASSES:
globals()[class_name] = type(class_name, (Container,), {})
class KivyRenderTextInput(CodeInput):
def keyboard_on_key_down(self, window, keycode, text, modifiers):
is_osx = sys.platform == 'darwin'
# Keycodes on OSX:
ctrl, cmd = 64, 1024
key, key_str = keycode
if text and key not in (list(self.interesting_keys.keys()) + [27]):
# This allows *either* ctrl *or* cmd, but not both.
if modifiers == ['ctrl'] or (is_osx and modifiers == ['meta']):
if key == ord('s'):
self.catalog.change_kv(True)
return
return super(KivyRenderTextInput, self).keyboard_on_key_down(
window, keycode, text, modifiers)
class Catalog(BoxLayout):
'''Catalog of widgets. This is the root widget of the app. It contains
a tabbed pain of widgets that can be displayed and a textbox where .kv
language files for widgets being demoed can be edited.
The entire interface for the Catalog is defined in kivycatalog.kv,
although individual containers are defined in the container_kvs
directory.
To add a container to the catalog,
first create the .kv file in container_kvs
The name of the file (sans .kv) will be the name of the widget available
inside the kivycatalog.kv
Finally modify kivycatalog.kv to add an AccordionItem
to hold the new widget.
Follow the examples in kivycatalog.kv to ensure the item
has an appropriate id and the class has been referenced.
You do not need to edit any python code, just .kv language files!
'''
language_box = ObjectProperty()
screen_manager = ObjectProperty()
_change_kv_ev = None
def __init__(self, **kwargs):
self._previously_parsed_text = ''
super(Catalog, self).__init__(**kwargs)
self.show_kv(None, 'Welcome')
self.carousel = None
def show_kv(self, instance, value):
'''Called when an a item is selected, we need to show the .kv language
file associated with the newly revealed container.'''
self.screen_manager.current = value
child = self.screen_manager.current_screen.children[0]
with open(child.kv_file, 'rb') as file:
self.language_box.text = file.read().decode('utf8')
if self._change_kv_ev is not None:
self._change_kv_ev.cancel()
self.change_kv()
# reset undo/redo history
self.language_box.reset_undo()
def schedule_reload(self):
if self.auto_reload:
txt = self.language_box.text
child = self.screen_manager.current_screen.children[0]
if txt == child.previous_text:
return
child.previous_text = txt
if self._change_kv_ev is not None:
self._change_kv_ev.cancel()
if self._change_kv_ev is None:
self._change_kv_ev = Clock.create_trigger(self.change_kv, 2)
self._change_kv_ev()
def change_kv(self, *largs):
'''Called when the update button is clicked. Needs to update the
interface for the currently active kv widget, if there is one based
on the kv file the user entered. If there is an error in their kv
syntax, show a nice popup.'''
txt = self.language_box.text
kv_container = self.screen_manager.current_screen.children[0]
try:
parser = Parser(content=txt)
kv_container.clear_widgets()
widget = Factory.get(parser.root.name)()
Builder._apply_rule(widget, parser.root, parser.root)
kv_container.add_widget(widget)
except (SyntaxError, ParserException) as e:
self.show_error(e)
except Exception as e:
self.show_error(e)
def show_error(self, e):
self.info_label.text = str(e).encode('utf-8')
self.anim = Animation(top=190.0, opacity=1, d=2, t='in_back') +\
Animation(top=190.0, d=3) +\
Animation(top=0, opacity=0, d=2)
self.anim.start(self.info_label)
class KivyCatalogApp(App):
'''The kivy App that runs the main root. All we do is build a catalog
widget into the root.'''
def build(self):
return Catalog()
def on_pause(self):
return True
if __name__ == "__main__":
KivyCatalogApp().run()

View file

@ -0,0 +1,117 @@
#:import os os
<GestureDatabaseItem>:
size_hint: None, None
size: 120, 130
on_pos: self._draw_trigger()
on_size: self._draw_trigger()
Label:
id: namelbl
text: root.name
size_hint: 1, None
height: 40
font_size: 14
color: 1, 0, 0, 1
Label:
id: stats
text:
( str(root.template_count) + " templates\nin " +
str(len(root.gesture_list)) + ' gestures' )
size_hint: 1, None
height: 60
ToggleButton:
id: select
text: 'Select'
size_hint: None, None
size: 120, 30
on_state: root.toggle_selected()
<GestureDatabase>:
rows: 1
spacing: 10
padding: 10
cols_minimum: {0: 200}
GridLayout:
id: menu
cols: 1
spacing: 10
padding: 10
size_hint: None, 1
width: 200
Button:
text: root.selected_count and 'Deselect all' or 'Select all'
size_hint_y: None
height: 100
on_press: root.mass_select()
Button:
text:
(root.selected_count
and 'Save ' + str(root.selected_count) + ' gestures'
or 'Save all')
size_hint_y: None
height: 100
on_press: root.export_popup.open()
Button:
text:
(root.selected_count
and 'Unload ' + str(root.selected_count) + ' gestures'
or 'Unload all')
size_hint_y: None
height: 100
on_press: root.unload_gestures()
Button:
text: 'Load from file'
size_hint_y: None
height: 100
on_press: root.import_popup.open()
ScrollView:
on_scroll_y: root.redraw_all()
StackLayout:
id: gesture_list
spacing: 10
padding: 10
size_hint: 1, None
height: self.minimum_height
<GestureExportPopup>:
title: 'Specify filename'
auto_dismiss: True
size_hint: None, None
size: 400, 400
GridLayout:
cols: 1
spacing: 10
padding: 10
rows_minimum: {0: 100}
Label:
text:
( 'The extension .kg will be appended automatically.\n' +
'The file is saved to the current working directory, unless\n' +
'you specify an absolute path')
TextInput:
id: filename
multiline: False
size_hint: 1, None
height: 40
Button:
id: save_btn
text: 'Save'
size_hint: 1, None
height: 45
Button:
id: cancel_btn
text: 'Cancel'
size_hint: 1, None
height: 45
on_press: root.dismiss()
<GestureImportPopup>:
auto_dismiss: True
size_hint: None, None
size: 450, 400
FileChooserListView:
id: filechooser
size_hint: None, None
size: 400, 380
filters: ['*.kg']
path: os.getcwd()

View file

@ -0,0 +1,180 @@
__all__ = ('GestureDatabase', 'GestureDatabaseItem')
from kivy.clock import Clock
from kivy.lang import Builder
from kivy.properties import NumericProperty, StringProperty
from kivy.properties import ListProperty, ObjectProperty
from kivy.uix.gridlayout import GridLayout
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.popup import Popup
from kivy.graphics import Rectangle, Color
from kivy.multistroke import Recognizer
# local libraries
from helpers import InformationPopup
Builder.load_file('gesturedatabase.kv')
class GestureExportPopup(Popup):
pass
class GestureImportPopup(Popup):
pass
class GestureDatabaseItem(FloatLayout):
name = StringProperty('(no name)')
template_count = NumericProperty(0)
gesture_list = ListProperty([])
def __init__(self, **kwargs):
super(GestureDatabaseItem, self).__init__(**kwargs)
self.rect = None
self._draw_trigger = Clock.create_trigger(self.draw_item, 0)
self.update_template_count()
self.bind(gesture_list=self.update_template_count)
self.register_event_type('on_select')
self.register_event_type('on_deselect')
def toggle_selected(self, *l):
self._draw_rect(clear=True)
if self.ids.select.state == 'down':
self.dispatch('on_select')
self.ids.select.text = 'Deselect'
else:
self.dispatch('on_deselect')
self.ids.select.text = 'Select'
def update_template_count(self, *l):
tpl_count = 0
for g in self.gesture_list:
tpl_count += len(g.templates)
self.template_count = tpl_count
def draw_item(self, *l):
self.ids.namelbl.pos = self.pos
self.ids.namelbl.y += 90
self.ids.stats.pos = self.pos
self.ids.stats.y += 40
self.ids.select.pos = self.pos
self._draw_rect()
def _draw_rect(self, clear=False, *l):
col = self.ids.select.state == 'down' and 1 or .2
with self.canvas:
Color(col, 0, 0, .15)
if self.rect or clear:
self.canvas.remove(self.rect)
self.rect = Rectangle(size=self.size, pos=self.pos)
def on_select(*l):
pass
def on_deselect(*l):
pass
class GestureDatabase(GridLayout):
selected_count = NumericProperty(0)
recognizer = ObjectProperty(None)
export_popup = ObjectProperty(GestureExportPopup())
import_popup = ObjectProperty(GestureImportPopup())
info_popup = ObjectProperty(InformationPopup())
def __init__(self, **kwargs):
super(GestureDatabase, self).__init__(**kwargs)
self.redraw_all = Clock.create_trigger(self._redraw_gesture_list, 0)
self.export_popup.ids.save_btn.bind(on_press=self.perform_export)
self.import_popup.ids.filechooser.bind(on_submit=self.perform_import)
def import_gdb(self):
self.gdict = {}
for gesture in self.recognizer.db:
if gesture.name not in self.gdict:
self.gdict[gesture.name] = []
self.gdict[gesture.name].append(gesture)
self.selected_count = 0
self.ids.gesture_list.clear_widgets()
for k in sorted(self.gdict, key=lambda n: n.lower()):
gitem = GestureDatabaseItem(name=k, gesture_list=self.gdict[k])
gitem.bind(on_select=self.select_item)
gitem.bind(on_deselect=self.deselect_item)
self.ids.gesture_list.add_widget(gitem)
def select_item(self, *l):
self.selected_count += 1
def deselect_item(self, *l):
self.selected_count -= 1
def mass_select(self, *l):
if self.selected_count:
for i in self.ids.gesture_list.children:
if i.ids.select.state == 'down':
i.ids.select.state = 'normal'
i.draw_item()
else:
for i in self.ids.gesture_list.children:
if i.ids.select.state == 'normal':
i.ids.select.state = 'down'
i.draw_item()
def unload_gestures(self, *l):
if not self.selected_count:
self.recognizer.db = []
self.ids.gesture_list.clear_widgets()
self.selected_count = 0
return
for i in self.ids.gesture_list.children[:]:
if i.ids.select.state == 'down':
self.selected_count -= 1
for g in i.gesture_list:
# if g in self.recognizer.db: # not needed, for testing
self.recognizer.db.remove(g)
self.ids.gesture_list.remove_widget(i)
def perform_export(self, *l):
path = self.export_popup.ids.filename.text
if not path:
self.export_popup.dismiss()
self.info_popup.text = 'Missing filename'
self.info_popup.open()
return
elif not path.lower().endswith('.kg'):
path += '.kg'
self.save_selection_to_file(path)
self.export_popup.dismiss()
self.info_popup.text = 'Gestures exported!'
self.info_popup.open()
def perform_import(self, filechooser, *l):
count = len(self.recognizer.db)
for f in filechooser.selection:
self.recognizer.import_gesture(filename=f)
self.import_gdb()
self.info_popup.text = ("Imported %d gestures.\n" %
(len(self.recognizer.db) - count))
self.import_popup.dismiss()
self.info_popup.open()
def save_selection_to_file(self, filename, *l):
if not self.selected_count:
self.recognizer.export_gesture(filename=filename)
else:
tmpgdb = Recognizer()
for i in self.ids.gesture_list.children:
if i.ids.select.state == 'down':
for g in i.gesture_list:
tmpgdb.db.append(g)
tmpgdb.export_gesture(filename=filename)
def _redraw_gesture_list(self, *l):
for child in self.ids.gesture_list.children:
child._draw_trigger()

View file

@ -0,0 +1,30 @@
__all__ = ('InformationPopup', )
from kivy.uix.popup import Popup
from kivy.properties import StringProperty
from kivy.factory import Factory
from kivy.lang import Builder
from kivy.clock import Clock
Builder.load_string('''
<InformationPopup>:
auto_dismiss: True
size_hint: None, None
size: 400, 200
on_open: root.dismiss_trigger()
title: root.title
Label:
text: root.text
''')
class InformationPopup(Popup):
title = StringProperty('Information')
text = StringProperty('')
def __init__(self, time=1.5, **kwargs):
super(InformationPopup, self).__init__(**kwargs)
self.dismiss_trigger = Clock.create_trigger(self.dismiss, time)
Factory.register('InformationPopup', cls=InformationPopup)

View file

@ -0,0 +1,164 @@
<GestureHistoryManager>:
rows: 1
spacing: 10
GridLayout:
cols: 1
size_hint_x: None
width: 150
canvas:
Color:
rgba: 1, 1, 1, .1
Rectangle:
size: self.size
pos: self.pos
Button:
text: 'Clear History'
size_hint_y: None
height: 50
on_press: root.clear_history()
ScrollView:
id: scrollview
scroll_type: ['bars', 'content']
bar_width: 4
GridLayout:
id: history
cols: 1
size_hint: 1, None
height: self.minimum_height
<GestureSettingsForm>:
orientation: 'vertical'
spacing: 10
GridLayout:
id: settings
cols: 1
top: root.top
Label:
text: '[b]Results (scroll for more)[/b]'
markup: True
size_hint_y: None
height: 30
halign: 'left'
valign: 'middle'
text_size: self.size
canvas:
Color:
rgba: 47 / 255., 167 / 255., 212 / 255., .4
Rectangle:
pos: self.x, self.y + 1
size: self.size
Color:
rgb: .5, .5, .5
Rectangle:
pos: self.x, self.y - 2
size: self.width, 1
GridLayout:
id: analysis
top: root.top
rows: 1
<GestureVisualizer>:
canvas:
Color:
rgba: 1, 1, 1, self.selected and .3 or .1
Rectangle:
pos: self.pos
size: self.size
<RecognizerResultDetails>:
canvas:
Color:
rgba: 1, 0, 0, .1
Rectangle:
size: self.size
pos: self.pos
ScrollView:
id: result_scrollview
scroll_type: ['bars', 'content']
bar_width: 4
GridLayout:
id: result_list
cols: 1
size_hint: 1, None
height: self.minimum_height
Button:
size_hint: None, None
width: 150
height: 70
text: 'Re-analyze'
on_press: root.dispatch('on_reanalyze_selected')
<RecognizerResultLabel>:
size_hint_y: None
height: 70
markup: True
halign: 'left'
valign: 'top'
text_size: self.size
<AddGestureSettings>:
MultistrokeSettingTitle:
title: 'New gesture settings'
desc: 'Affects how to future input is matched against new gesture'
MultistrokeSettingBoolean:
id: permute
title: 'Use Heap Permute algorithm?'
desc:
('This will generate all possible stroke orders from the ' +
'input. Only suitable for gestures with 1-3 strokes (or ' +
'the number of templates will be huge)')
button_text: 'Heap Permute?'
value: True
MultistrokeSettingBoolean:
id: stroke_sens
title: 'Require same number of strokes?'
desc:
('When enabled, the new gesture will only match candidates ' +
'with exactly the same stroke count. Enable if possible.')
button_text: 'Stroke sensitive?'
value: True
MultistrokeSettingBoolean:
id: orientation_sens
title: 'Is gesture orientation sensitive?'
desc:
('Enable to differentiate gestures that differ only by ' +
'orientation (d/p, b/q, w/m), disable for gestures that ' +
'look the same in any orientation (like a circle)')
button_text: 'Orientation\nsensitive?'
value: True
MultistrokeSettingSlider:
id: angle_sim
title: 'Angle similarity threshold'
type: 'float'
desc:
('Use a low number to distinguish similar gestures, higher ' +
'number to match similar gestures (with differing angle)')
value: 30.
min: 1.0
max: 179.0
MultistrokeSettingString:
id: name
title: 'Gesture name'
type: 'float'
desc:
('Name of new gesture (including all generated templates). ' +
'You can have as many gestures with the same name as you need')
Button:
size_hint_y: None
height: 40
text: 'Add to database'
on_press: root.parent.parent.parent.add_selected_to_database()

View file

@ -0,0 +1,276 @@
__all__ = ('GestureHistoryManager', 'GestureVisualizer')
from kivy.app import App
from kivy.clock import Clock
from kivy.lang import Builder
from kivy.uix.widget import Widget
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.gridlayout import GridLayout
from kivy.uix.label import Label
from kivy.graphics import Color, Line
from kivy.properties import ObjectProperty, BooleanProperty
from kivy.compat import PY2
# local libraries
from helpers import InformationPopup
from settings import MultistrokeSettingsContainer
# refuse heap permute for gestures with more strokes than 3
# (you can increase it, but 4 strokes = 384 templates, 5 = 3840)
MAX_PERMUTE_STROKES = 3
Builder.load_file('historymanager.kv')
class GestureHistoryManager(GridLayout):
selected = ObjectProperty(None, allownone=True)
def __init__(self, **kwargs):
super(GestureHistoryManager, self).__init__(**kwargs)
self.gesturesettingsform = GestureSettingsForm()
rr = self.gesturesettingsform.rrdetails
rr.bind(on_reanalyze_selected=self.reanalyze_selected)
self.infopopup = InformationPopup()
self.recognizer = App.get_running_app().recognizer
def reanalyze_selected(self, *l):
# recognize() can block the UI with max_gpf=100, show a message
self.infopopup.text = 'Please wait, analyzing ..'
self.infopopup.auto_dismiss = False
self.infopopup.open()
# Get a reference to the original GestureContainer object
gesture_obj = self.selected._result_obj._gesture_obj
# Reanalyze the candidate strokes using current database
res = self.recognizer.recognize(gesture_obj.get_vectors(),
max_gpf=100)
# Tag the result with the gesture object (it didn't change)
res._gesture_obj = gesture_obj
# Tag the selected item with the updated ProgressTracker
self.selected._result_obj = res
res.bind(on_complete=self._reanalyze_complete)
def _reanalyze_complete(self, *l):
self.gesturesettingsform.load_visualizer(self.selected)
self.infopopup.dismiss()
def add_selected_to_database(self, *l):
if self.selected is None:
raise Exception('add_gesture_to_database before load_visualizer?')
if self.gesturesettingsform.addsettings is None:
raise Exception('add_gesture_to_database missing addsetings?')
ids = self.gesturesettingsform.addsettings.ids
name = ids.name.value.strip()
if name == '':
self.infopopup.auto_dismiss = True
self.infopopup.text = 'You must specify a name for the gesture'
self.infopopup.open()
return
permute = ids.permute.value
sensitive = ids.orientation_sens.value
strokelen = ids.stroke_sens.value
angle_sim = ids.angle_sim.value
cand = self.selected._result_obj._gesture_obj.get_vectors()
if permute and len(cand) > MAX_PERMUTE_STROKES:
t = "Can't heap permute %d-stroke gesture " % (len(cand))
self.infopopup.text = t
self.infopopup.auto_dismiss = True
self.infopopup.open()
return
self.recognizer.add_gesture(
name,
cand,
use_strokelen=strokelen,
orientation_sensitive=sensitive,
angle_similarity=angle_sim,
permute=permute)
self.infopopup.text = 'Gesture added to database'
self.infopopup.auto_dismiss = True
self.infopopup.open()
def clear_history(self, *l):
if self.selected:
self.visualizer_deselect()
self.ids.history.clear_widgets()
def visualizer_select(self, visualizer, *l):
if self.selected is not None:
self.selected.selected = False
else:
self.add_widget(self.gesturesettingsform)
self.gesturesettingsform.load_visualizer(visualizer)
self.selected = visualizer
def visualizer_deselect(self, *l):
self.selected = None
self.remove_widget(self.gesturesettingsform)
def add_recognizer_result(self, result, *l):
'''The result object is a ProgressTracker with additional
data; in main.py it is tagged with the original GestureContainer
that was analyzed (._gesture_obj)'''
# Create a GestureVisualizer that draws the gesture on canvas
visualizer = GestureVisualizer(result._gesture_obj,
size_hint=(None, None), size=(150, 150))
# Tag it with the result object so AddGestureForm.load_visualizer
# has the results to build labels in the scrollview
visualizer._result_obj = result
visualizer.bind(on_select=self.visualizer_select)
visualizer.bind(on_deselect=self.visualizer_deselect)
# Add the visualizer to the list of gestures in 'history' screen
self.ids.history.add_widget(visualizer)
self._trigger_layout()
self.ids.scrollview.update_from_scroll()
class RecognizerResultLabel(Label):
'''This Label subclass is used to show a single result from the
gesture matching process (is a child of GestureHistoryManager)'''
pass
class RecognizerResultDetails(BoxLayout):
'''Contains a ScrollView of RecognizerResultLabels, ie the list of
matched gestures and their score/distance (is a child of
GestureHistoryManager)'''
def __init__(self, **kwargs):
super(RecognizerResultDetails, self).__init__(**kwargs)
self.register_event_type('on_reanalyze_selected')
def on_reanalyze_selected(self, *l):
pass
class AddGestureSettings(MultistrokeSettingsContainer):
pass
class GestureSettingsForm(BoxLayout):
'''This is the main content of the GestureHistoryManager, the form for
adding a new gesture to the recognizer. It is added to the widget tree
when a GestureVisualizer is selected.'''
def __init__(self, **kwargs):
super(GestureSettingsForm, self).__init__(**kwargs)
self.infopopup = InformationPopup()
self.rrdetails = RecognizerResultDetails()
self.addsettings = None
self.app = App.get_running_app()
def load_visualizer(self, visualizer):
if self.addsettings is None:
self.addsettings = AddGestureSettings()
self.ids.settings.add_widget(self.addsettings)
self.visualizer = visualizer
analysis = self.ids.analysis
analysis.clear_widgets()
analysis.add_widget(self.rrdetails)
scrollv = self.rrdetails.ids.result_scrollview
resultlist = self.rrdetails.ids.result_list
resultlist.clear_widgets()
r = visualizer._result_obj.results
if not len(r):
lbl = RecognizerResultLabel(text='[b]No match[/b]')
resultlist.add_widget(lbl)
scrollv.scroll_y = 1
return
if PY2:
d = r.iteritems
else:
d = r.items
for one in sorted(d(), key=lambda x: x[1]['score'],
reverse=True):
data = one[1]
lbl = RecognizerResultLabel(
text='Name: [b]' + data['name'] + '[/b]' +
'\n Score: ' + str(data['score']) +
'\n Distance: ' + str(data['dist']))
resultlist.add_widget(lbl)
# Make sure the top is visible
scrollv.scroll_y = 1
class GestureVisualizer(Widget):
selected = BooleanProperty(False)
def __init__(self, gesturecontainer, **kwargs):
super(GestureVisualizer, self).__init__(**kwargs)
self._gesture_container = gesturecontainer
self._trigger_draw = Clock.create_trigger(self._draw_item, 0)
self.bind(pos=self._trigger_draw, size=self._trigger_draw)
self._trigger_draw()
self.register_event_type('on_select')
self.register_event_type('on_deselect')
def on_touch_down(self, touch):
if not self.collide_point(touch.x, touch.y):
return
self.selected = not self.selected
self.dispatch(self.selected and 'on_select' or 'on_deselect')
# FIXME: This seems inefficient, is there a better way??
def _draw_item(self, dt):
g = self._gesture_container
bb = g.bbox
minx, miny, maxx, maxy = bb['minx'], bb['miny'], bb['maxx'], bb['maxy']
width, height = self.size
xpos, ypos = self.pos
if g.height > g.width:
to_self = (height * 0.85) / g.height
else:
to_self = (width * 0.85) / g.width
self.canvas.remove_group('gesture')
cand = g.get_vectors()
col = g.color
for stroke in cand:
out = []
append = out.append
for vec in stroke:
x, y = vec
x = (x - minx) * to_self
w = (maxx - minx) * to_self
append(x + xpos + (width - w) * .85 / 2)
y = (y - miny) * to_self
h = (maxy - miny) * to_self
append(y + ypos + (height - h) * .85 / 2)
with self.canvas:
Color(col[0], col[1], col[2], mode='rgb')
Line(points=out, group='gesture', width=2)
def on_select(self, *l):
pass
def on_deselect(self, *l):
pass

View file

@ -0,0 +1,144 @@
'''
Multistroke Recognition Database Demonstration
==============================================
This application records gestures and attempts to match them. You should
see a black drawing surface with some buttons across the bottom. As you
make a gesture on the drawing surface, the gesture will be added to
the history and a match will be attempted. If you go to the history tab,
name the gesture, and add it to the database, then similar gestures in the
future will be recognized. You can load and save databases of gestures
in .kg files.
This demonstration code spans many files, with this being the primary file.
The information pop-up ('No match') comes from the file helpers.py.
The history pane is managed in the file historymanager.py and described
in the file historymanager.kv. The database pane and storage is managed in
the file gesturedatabase.py and the described in the file gesturedatabase.kv.
The general logic of the sliders and buttons are in the file
settings.py and described in settings.kv. but the actual settings pane is
described in the file multistroke.kv and managed from this file.
'''
from kivy.app import App
from kivy.uix.gridlayout import GridLayout
from kivy.uix.gesturesurface import GestureSurface
from kivy.uix.screenmanager import ScreenManager, Screen, SlideTransition
from kivy.uix.label import Label
from kivy.multistroke import Recognizer
# Local libraries
from historymanager import GestureHistoryManager
from gesturedatabase import GestureDatabase
from settings import MultistrokeSettingsContainer
class MainMenu(GridLayout):
pass
class MultistrokeAppSettings(MultistrokeSettingsContainer):
pass
class MultistrokeApp(App):
def goto_database_screen(self, *l):
self.database.import_gdb()
self.manager.current = 'database'
def handle_gesture_cleanup(self, surface, g, *l):
if hasattr(g, '_result_label'):
surface.remove_widget(g._result_label)
def handle_gesture_discard(self, surface, g, *l):
# Don't bother creating Label if it's not going to be drawn
if surface.draw_timeout == 0:
return
text = '[b]Discarded:[/b] Not enough input'
g._result_label = Label(text=text, markup=True, size_hint=(None, None),
center=(g.bbox['minx'], g.bbox['miny']))
self.surface.add_widget(g._result_label)
def handle_gesture_complete(self, surface, g, *l):
result = self.recognizer.recognize(g.get_vectors())
result._gesture_obj = g
result.bind(on_complete=self.handle_recognize_complete)
def handle_recognize_complete(self, result, *l):
self.history.add_recognizer_result(result)
# Don't bother creating Label if it's not going to be drawn
if self.surface.draw_timeout == 0:
return
best = result.best
if best['name'] is None:
text = '[b]No match[/b]'
else:
text = 'Name: [b]%s[/b]\nScore: [b]%f[/b]\nDistance: [b]%f[/b]' % (
best['name'], best['score'], best['dist'])
g = result._gesture_obj
g._result_label = Label(text=text, markup=True, size_hint=(None, None),
center=(g.bbox['minx'], g.bbox['miny']))
self.surface.add_widget(g._result_label)
def build(self):
# Setting NoTransition breaks the "history" screen! Possibly related
# to some inexplicable rendering bugs on my particular system
self.manager = ScreenManager(transition=SlideTransition(
duration=.15))
self.recognizer = Recognizer()
# Setup the GestureSurface and bindings to our Recognizer
surface = GestureSurface(line_width=2, draw_bbox=True,
use_random_color=True)
surface_screen = Screen(name='surface')
surface_screen.add_widget(surface)
self.manager.add_widget(surface_screen)
surface.bind(on_gesture_discard=self.handle_gesture_discard)
surface.bind(on_gesture_complete=self.handle_gesture_complete)
surface.bind(on_gesture_cleanup=self.handle_gesture_cleanup)
self.surface = surface
# History is the list of gestures drawn on the surface
history = GestureHistoryManager()
history_screen = Screen(name='history')
history_screen.add_widget(history)
self.history = history
self.manager.add_widget(history_screen)
# Database is the list of gesture templates in Recognizer
database = GestureDatabase(recognizer=self.recognizer)
database_screen = Screen(name='database')
database_screen.add_widget(database)
self.database = database
self.manager.add_widget(database_screen)
# Settings screen
app_settings = MultistrokeAppSettings()
ids = app_settings.ids
ids.max_strokes.bind(value=surface.setter('max_strokes'))
ids.temporal_win.bind(value=surface.setter('temporal_window'))
ids.timeout.bind(value=surface.setter('draw_timeout'))
ids.line_width.bind(value=surface.setter('line_width'))
ids.draw_bbox.bind(value=surface.setter('draw_bbox'))
ids.use_random_color.bind(value=surface.setter('use_random_color'))
settings_screen = Screen(name='settings')
settings_screen.add_widget(app_settings)
self.manager.add_widget(settings_screen)
# Wrap in a gridlayout so the main menu is always visible
layout = GridLayout(cols=1)
layout.add_widget(self.manager)
layout.add_widget(MainMenu())
return layout
if __name__ in ('__main__', '__android__'):
MultistrokeApp().run()

View file

@ -0,0 +1,101 @@
<MainMenu>:
rows: 1
size_hint: (1, None)
height: 50
spacing: 5
padding: 5
ToggleButton:
group: 'mainmenu'
state: 'down'
text: 'Gesture Surface'
on_press:
app.manager.current = 'surface'
if self.state == 'normal': self.state = 'down'
ToggleButton:
group: 'mainmenu'
text: 'History'
on_press:
app.manager.current = 'history'
if self.state == 'normal': self.state = 'down'
ToggleButton:
group: 'mainmenu'
text: 'Database'
on_press:
app.goto_database_screen()
if self.state == 'normal': self.state = 'down'
ToggleButton:
group: 'mainmenu'
text: 'Settings'
on_press:
app.manager.current = 'settings'
if self.state == 'normal': self.state = 'down'
<MultistrokeAppSettings>:
pos_hint: {'top': 1}
MultistrokeSettingTitle:
title: 'GestureSurface behavior'
desc: 'Affects how gestures are detected and cleaned up'
MultistrokeSettingSlider:
id: max_strokes
title: 'Max strokes'
type: 'int'
desc:
('Max number of strokes for a single gesture. If 0, the ' +
'gesture will only be analyzed once the temporal window has ' +
'expired since the last strokes touch up event')
value: 4
min: 0
max: 15
MultistrokeSettingSlider:
id: temporal_win
title: 'Temporal Window'
type: 'float'
desc:
('Time to wait from last touch up in a gesture before analyzing ' +
'the input. If 0, only analyzed once Max Strokes is reached')
value: 2.
min: 0
max: 60.
MultistrokeSettingTitle:
title: 'Drawing'
desc: 'Affects how gestures are visualized on the GestureSurface'
MultistrokeSettingSlider:
id: timeout
title: 'Draw Timeout'
type: 'float'
desc:
('How long to display the gesture (and result label) on the ' +
'gesture surface once analysis has completed')
value: 2.
min: 0
max: 60.
MultistrokeSettingSlider:
id: line_width
title: 'Line width'
type: 'int'
desc:
('Width of lines on the gesture surface; 0 does not draw ' +
'anything; 1 uses OpenGL line, >1 uses custom drawing method.')
value: 2
min: 0
max: 10
MultistrokeSettingBoolean:
id: use_random_color
title: 'Use random color?'
desc: 'Use random color for each gesture? If disabled, white is used.'
button_text: 'Random color?'
value: True
MultistrokeSettingBoolean:
id: draw_bbox
title: 'Draw gesture bounding box?'
desc: 'Enable to draw a bounding box around the gesture'
button_text: 'Draw bbox?'
value: True

View file

@ -0,0 +1,110 @@
<MultistrokeSettingsContainer>:
cols: 1
spacing: 5
padding: 5
size_hint_y: None
height: self.minimum_height
<MultistrokeSettingItem>:
size_hint_y: None
height: 70
rows: 1
canvas:
Color:
rgba: 47 / 255., 167 / 255., 212 / 255., .1
Rectangle:
pos: self.x, self.y + 1
size: self.size
Color:
rgb: .2, .2, .2
Rectangle:
pos: self.x, self.y - 2
size: self.width, 1
Label:
size_hint_x: .6
id: labellayout
markup: True
text: u'{0}\n[size=13sp][color=999999]{1}[/color][/size]'.format(root.title or '', root.desc or '')
font_size: '15sp'
text_size: self.size
valign: 'top'
<MultistrokeSettingTitle>:
size_hint_y: None
height: 40
markup: True
text: u'{0}\n[size=13sp][color=999999]{1}[/color][/size]'.format(root.title or '', root.desc or '')
font_size: '15sp'
text_size: self.size
valign: 'top'
halign: 'right'
canvas:
Color:
rgba: 47 / 255., 167 / 255., 212 / 255., .4
Rectangle:
pos: self.x, self.y + 1
size: self.size
Color:
rgb: .5, .5, .5
Rectangle:
pos: self.x, self.y - 2
size: self.width, 1
<MultistrokeSettingBoolean>:
value: button.state == 'down' and True or False
ToggleButton:
id: button
text: root.button_text
size_hint_x: None
width: 150
pos: root.pos
state: root.value and 'down' or 'normal'
<MultistrokeSettingString>:
value: input.text
AnchorLayout:
size_hint_x: None
width: 150
TextInput:
id: input
size_hint_y: None
height: 30
pos: root.pos
multiline: True
text: root.value
<MultistrokeSettingSlider>:
value: slider.value
Label:
id: sliderlabel
size_hint_x: None
width: 50
text: str(root._to_numtype(slider.value))
Slider:
id: slider
min: root.min
max: root.max
pos: root.pos
size_hint_x: None
width: 150
value: root.value
<EditSettingPopup>:
size_hint: None, None
size: 300, 150
title: 'Edit setting'
BoxLayout:
orientation: 'vertical'
TextInput:
id: input
markup: False
multiline: False
on_text_validate: root.dispatch('on_validate', self.text)
BoxLayout:
orientation: 'horizontal'
Button:
text: 'OK'
on_press: root.dispatch('on_validate', input.text)
Button:
text: 'Cancel'
on_press: root.dismiss()

View file

@ -0,0 +1,97 @@
__all__ = ('MultistrokeSettingsContainer', 'MultistrokeSettingItem',
'MultistrokeSettingBoolean', 'MultistrokeSettingSlider',
'MultistrokeSettingString', 'MultistrokeSettingTitle')
from kivy.factory import Factory
from kivy.lang import Builder
from kivy.uix.gridlayout import GridLayout
from kivy.uix.label import Label
from kivy.properties import (StringProperty, NumericProperty, OptionProperty,
BooleanProperty)
from kivy.uix.popup import Popup
Builder.load_file('settings.kv')
class MultistrokeSettingsContainer(GridLayout):
pass
class MultistrokeSettingItem(GridLayout):
title = StringProperty('<No title set>')
desc = StringProperty('')
class MultistrokeSettingTitle(Label):
title = StringProperty('<No title set>')
desc = StringProperty('')
class MultistrokeSettingBoolean(MultistrokeSettingItem):
button_text = StringProperty('')
value = BooleanProperty(False)
class MultistrokeSettingString(MultistrokeSettingItem):
value = StringProperty('')
class EditSettingPopup(Popup):
def __init__(self, **kwargs):
super(EditSettingPopup, self).__init__(**kwargs)
self.register_event_type('on_validate')
def on_validate(self, *l):
pass
class MultistrokeSettingSlider(MultistrokeSettingItem):
min = NumericProperty(0)
max = NumericProperty(100)
type = OptionProperty('int', options=['float', 'int'])
value = NumericProperty(0)
def __init__(self, **kwargs):
super(MultistrokeSettingSlider, self).__init__(**kwargs)
self._popup = EditSettingPopup()
self._popup.bind(on_validate=self._validate)
self._popup.bind(on_dismiss=self._dismiss)
def _to_numtype(self, v):
try:
if self.type == 'float':
return round(float(v), 1)
else:
return int(v)
except ValueError:
return self.min
def _dismiss(self, *l):
self._popup.ids.input.focus = False
def _validate(self, instance, value):
self._popup.dismiss()
val = self._to_numtype(self._popup.ids.input.text)
if val < self.min:
val = self.min
elif val > self.max:
val = self.max
self.value = val
def on_touch_down(self, touch):
if not self.ids.sliderlabel.collide_point(*touch.pos):
return super(MultistrokeSettingSlider, self).on_touch_down(touch)
ids = self._popup.ids
ids.value = str(self.value)
ids.input.text = str(self._to_numtype(self.value))
self._popup.open()
ids.input.focus = True
ids.input.select_all()
Factory.register('MultistrokeSettingsContainer',
cls=MultistrokeSettingsContainer)
Factory.register('MultistrokeSettingTitle', cls=MultistrokeSettingTitle)
Factory.register('MultistrokeSettingBoolean', cls=MultistrokeSettingBoolean)
Factory.register('MultistrokeSettingSlider', cls=MultistrokeSettingSlider)
Factory.register('MultistrokeSettingString', cls=MultistrokeSettingString)

View file

@ -0,0 +1,3 @@
title=Pictures
author=Kivy team
orientation=landscape

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 298 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 220 KiB

View file

@ -0,0 +1,73 @@
'''
Basic Picture Viewer
====================
This simple image browser demonstrates the scatter widget. You should
see three framed photographs on a background. You can click and drag
the photos around, or multi-touch to drop a red dot to scale and rotate the
photos.
The photos are loaded from the local images directory, while the background
picture is from the data shipped with kivy in kivy/data/images/background.jpg.
The file pictures.kv describes the interface and the file shadow32.png is
the border to make the images look like framed photographs. Finally,
the file android.txt is used to package the application for use with the
Kivy Launcher Android application.
For Android devices, you can copy/paste this directory into
/sdcard/kivy/pictures on your Android device.
The images in the image directory are from the Internet Archive,
`https://archive.org/details/PublicDomainImages`, and are in the public
domain.
'''
import kivy
kivy.require('1.0.6')
from glob import glob
from random import randint
from os.path import join, dirname
from kivy.app import App
from kivy.logger import Logger
from kivy.uix.scatter import Scatter
from kivy.properties import StringProperty
class Picture(Scatter):
'''Picture is the class that will show the image with a white border and a
shadow. They are nothing here because almost everything is inside the
picture.kv. Check the rule named <Picture> inside the file, and you'll see
how the Picture() is really constructed and used.
The source property will be the filename to show.
'''
source = StringProperty(None)
class PicturesApp(App):
def build(self):
# the root is created in pictures.kv
root = self.root
# get any files into images directory
curdir = dirname(__file__)
for filename in glob(join(curdir, 'images', '*')):
try:
# load the image
picture = Picture(source=filename, rotation=randint(-30, 30))
# add to the main field
root.add_widget(picture)
except Exception as e:
Logger.exception('Pictures: Unable to load <%s>' % filename)
def on_pause(self):
return True
if __name__ == '__main__':
PicturesApp().run()

View file

@ -0,0 +1,57 @@
#:kivy 1.0
#:import kivy kivy
#:import win kivy.core.window
FloatLayout:
canvas:
Color:
rgb: 1, 1, 1
Rectangle:
source: 'data/images/background.jpg'
size: self.size
BoxLayout:
padding: 10
spacing: 10
size_hint: 1, None
pos_hint: {'top': 1}
height: 44
Image:
size_hint: None, None
size: 24, 24
source: 'data/logo/kivy-icon-24.png'
Label:
height: 24
text_size: self.width, None
color: (1, 1, 1, .8)
text: 'Kivy %s - Pictures' % kivy.__version__
<Picture>:
# each time a picture is created, the image can delay the loading
# as soon as the image is loaded, ensure that the center is changed
# to the center of the screen.
on_size: self.center = win.Window.center
size: image.size
size_hint: None, None
Image:
id: image
source: root.source
# create initial image to be 400 pixels width
size: 400, 400 / self.image_ratio
# add shadow background
canvas.before:
Color:
rgba: 1,1,1,1
BorderImage:
source: 'shadow32.png'
border: (36,36,36,36)
size:(self.width+72, self.height+72)
pos: (-36,-36)

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

View file

@ -0,0 +1,147 @@
'''
Live Shader Editor
==================
This provides a live editor for vertex and fragment editors.
You should see a window with two editable panes on the left
and a large kivy logo on the right.The top pane is the
Vertex shader and the bottom is the Fragment shader. The file shadereditor.kv
describes the interface.
On each keystroke to either shader, declarations are added and the shaders
are compiled. If there are no errors, the screen is updated. Otherwise,
the error is visible as logging message in your terminal.
'''
import sys
import kivy
kivy.require('1.0.6')
from kivy.app import App
from kivy.uix.floatlayout import FloatLayout
from kivy.core.window import Window
from kivy.factory import Factory
from kivy.graphics import RenderContext
from kivy.properties import StringProperty, ObjectProperty
from kivy.clock import Clock
from kivy.compat import PY2
fs_header = '''
#ifdef GL_ES
precision highp float;
#endif
/* Outputs from the vertex shader */
varying vec4 frag_color;
varying vec2 tex_coord0;
/* uniform texture samplers */
uniform sampler2D texture0;
/* custom one */
uniform vec2 resolution;
uniform float time;
'''
vs_header = '''
#ifdef GL_ES
precision highp float;
#endif
/* Outputs to the fragment shader */
varying vec4 frag_color;
varying vec2 tex_coord0;
/* vertex attributes */
attribute vec2 vPosition;
attribute vec2 vTexCoords0;
/* uniform variables */
uniform mat4 modelview_mat;
uniform mat4 projection_mat;
uniform vec4 color;
'''
class ShaderViewer(FloatLayout):
fs = StringProperty(None)
vs = StringProperty(None)
def __init__(self, **kwargs):
self.canvas = RenderContext()
super(ShaderViewer, self).__init__(**kwargs)
Clock.schedule_interval(self.update_shader, 0)
def update_shader(self, *args):
s = self.canvas
s['projection_mat'] = Window.render_context['projection_mat']
s['time'] = Clock.get_boottime()
s['resolution'] = list(map(float, self.size))
s.ask_update()
def on_fs(self, instance, value):
self.canvas.shader.fs = value
def on_vs(self, instance, value):
self.canvas.shader.vs = value
Factory.register('ShaderViewer', cls=ShaderViewer)
class ShaderEditor(FloatLayout):
source = StringProperty('data/logo/kivy-icon-512.png')
fs = StringProperty('''
void main (void){
gl_FragColor = frag_color * texture2D(texture0, tex_coord0);
}
''')
vs = StringProperty('''
void main (void) {
frag_color = color;
tex_coord0 = vTexCoords0;
gl_Position = projection_mat * modelview_mat * vec4(vPosition.xy, 0.0, 1.0);
}
''')
viewer = ObjectProperty(None)
def __init__(self, **kwargs):
super(ShaderEditor, self).__init__(**kwargs)
self.test_canvas = RenderContext()
s = self.test_canvas.shader
self.trigger_compile = Clock.create_trigger(self.compile_shaders, -1)
self.bind(fs=self.trigger_compile, vs=self.trigger_compile)
def compile_shaders(self, *largs):
print('try compile')
if not self.viewer:
return
# we don't use str() here because it will crash with non-ascii char
if PY2:
fs = fs_header + self.fs.encode('utf-8')
vs = vs_header + self.vs.encode('utf-8')
else:
fs = fs_header + self.fs
vs = vs_header + self.vs
print('-->', fs)
self.viewer.fs = fs
print('-->', vs)
self.viewer.vs = vs
class ShaderEditorApp(App):
def build(self):
kwargs = {}
if len(sys.argv) > 1:
kwargs['source'] = sys.argv[1]
return ShaderEditor(**kwargs)
if __name__ == '__main__':
ShaderEditorApp().run()

View file

@ -0,0 +1,39 @@
#:kivy 1.0
#: import GLShaderLexer pygments.lexers.GLShaderLexer
<ShaderEditor>:
viewer: viewer
BoxLayout:
BoxLayout:
orientation: 'vertical'
size_hint_x: None
width: 350
Label:
text: 'Fragment Shader'
size_hint_y: None
height: self.texture_size[1] + 10
CodeInput:
text: root.fs
lexer: GLShaderLexer()
on_text: root.fs = args[1]
Label:
text: 'Vertex Shader'
size_hint_y: None
height: self.texture_size[1] + 10
CodeInput:
text: root.vs
lexer: GLShaderLexer()
on_text: root.vs = args[1]
ShaderViewer:
id: viewer
canvas:
Color:
rgb: 1, 1, 1
Rectangle:
size: self.size
pos: self.pos
source: root.source

View file

@ -0,0 +1,11 @@
Showcase
========
Demonstrate all the possibilities of Kivy toolkit.
Android
-------
You can copy/paste this directory into /sdcard/kivy/showcase in your
android device.

View file

@ -0,0 +1,3 @@
title=Showcase
author=Kivy team
orientation=landscape

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 220 KiB

View file

@ -0,0 +1,4 @@
Icons adapted from the Open Iconic set of icons, which are licensed under MIT.
https://useiconic.com/
https://github.com/iconic/open-iconic

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 587 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 584 B

View file

@ -0,0 +1,39 @@
ShowcaseScreen:
name: 'Accordions'
fullscreen: True
BoxLayout:
size_hint_y: None
height: '48dp'
ToggleButton:
id: tbh
text: 'Horizontal'
group: 'accordion'
state: 'down'
ToggleButton:
text: 'Vertical'
group: 'accordion'
Accordion:
orientation: 'horizontal' if tbh.state == 'down' else 'vertical'
AccordionItem:
title: 'Panel 1'
Label:
text: 'This is a label fit to the content view'
text_size: self.width, None
AccordionItem:
title: 'Panel 2'
Button:
text: 'A button, what else?'
AccordionItem:
title: 'Panel 3'
Label:
text: 'This is a label fit to the content view'
text_size: self.width, None

View file

@ -0,0 +1,33 @@
ShowcaseScreen:
name: 'Bubbles'
Bubble:
size_hint_y: None
height: self.content_height + self.arrow_margin_y
BubbleContent:
size_hint_y: None
height: self.minimum_height
BubbleButton:
text: 'Cut'
BubbleButton:
text: 'Copy'
BubbleButton:
text: 'Paste'
Widget:
size_hint_y: None
height: '48dp'
BoxLayout:
size_hint_y: None
height: '48dp'
Label:
text: 'Hello'
Bubble:
arrow_pos: 'left_mid'
BubbleContent:
Label:
text: 'World'

View file

@ -0,0 +1,26 @@
ShowcaseScreen:
name: 'Buttons'
Button:
size_hint_y: None
height: '48dp'
text: 'Button normal'
Button:
size_hint_y: None
height: '48dp'
text: 'Button down'
state: 'down'
Button:
size_hint_y: None
height: '48dp'
text: 'Button disabled'
disabled: True
Button:
size_hint_y: None
height: '48dp'
text: 'Button down disabled'
state: 'down'
disabled: True

View file

@ -0,0 +1,51 @@
<ColoredLabel@Label>:
font_size: '48sp'
color: (.6, .6, .6, 1)
canvas.before:
Color:
rgb: (.9, .9, .9)
Rectangle:
pos: self.x + sp(2), self.y + sp(2)
size: self.width - sp(4), self.height - sp(4)
ShowcaseScreen:
name: 'Carousel'
fullscreen: True
BoxLayout:
size_hint_y: None
height: '48dp'
ToggleButton:
text: 'Loop'
id: btnloop
Label:
size_hint_x: None
width: self.height
text: '{}'.format(carousel.index)
Button:
size_hint_x: None
width: self.height
text: 'Prev'
on_release: carousel.load_previous()
Button:
size_hint_x: None
width: self.height
text: 'Next'
on_release: carousel.load_next()
Carousel:
id: carousel
loop: btnloop.state == 'down'
ColoredLabel:
text: 'Slide 0'
ColoredLabel:
text: 'Slide 1'
ColoredLabel:
text: 'Slide 2'

View file

@ -0,0 +1,33 @@
ShowcaseScreen:
name: 'CheckBoxes'
GridLayout:
cols: 3
spacing: '8dp'
size_hint: .5, None
height: self.minimum_height
Label:
text: 'Checkbox'
CheckBox:
size_hint_y: None
height: '48dp'
CheckBox:
size_hint_y: None
height: '48dp'
Label:
text: 'CheckBox with group'
CheckBox:
size_hint_y: None
height: '48dp'
group: 'g2'
CheckBox:
size_hint_y: None
height: '48dp'
group: 'g2'

View file

@ -0,0 +1,9 @@
ShowcaseScreen:
fullscreen: True
name: 'CodeInput'
CodeInput:
padding: '4dp'
text: 'class Hello(object):\n\tpass\n\nprint("Hello world")'
focus: True if root.parent else False

View file

@ -0,0 +1,40 @@
ShowcaseScreen:
fullscreen: True
name: 'DropDown'
# trick to not lost the Dropdown instance
# Dropdown itself is not really made to be used in kv.
__safe_id: [dropdown.__self__]
Button:
id: btn
text: '-'
on_release: dropdown.open(self)
size_hint_y: None
height: '48dp'
Widget:
on_parent: dropdown.dismiss()
DropDown:
id: dropdown
on_select: btn.text = 'Selected value: {}'.format(args[1])
Button:
text: 'Value A'
size_hint_y: None
height: '48dp'
on_release: dropdown.select('A')
Button:
text: 'Value B'
size_hint_y: None
height: '48dp'
on_release: dropdown.select('B')
Button:
text: 'Value C'
size_hint_y: None
height: '48dp'
on_release: dropdown.select('C')

View file

@ -0,0 +1,24 @@
ShowcaseScreen:
name: 'FileChoosers'
fullscreen: True
BoxLayout:
size_hint_y: None
height: '48dp'
ToggleButton:
text: 'Icon'
state: 'down'
group: 'filechooser'
on_release: filechooser.view_mode = 'icon'
ToggleButton:
text: 'List'
group: 'filechooser'
on_release: filechooser.view_mode = 'list'
FileChooser:
id: filechooser
FileChooserIconLayout
FileChooserListLayout

View file

@ -0,0 +1,17 @@
ShowcaseScreen:
popup: popup.__self__
fullscreen: True
name: 'Popups'
BoxLayout:
id: bl
Popup:
id: popup
title: "Hello World"
on_parent:
if self.parent == bl: self.parent.remove_widget(self)
Button:
text: 'press to dismiss'
on_release: popup.dismiss()
Button:
text: 'press to show Popup'
on_release: root.popup.open()

View file

@ -0,0 +1,15 @@
ShowcaseScreen:
name: 'ProgressBar'
Label:
text: 'Progression: {}%'.format(int(pb.value))
size_hint_y: None
height: '48dp'
ProgressBar:
id: pb
size_hint_x: .5
size_hint_y: None
height: '48dp'
value: (app.time * 20) % 100.

View file

@ -0,0 +1,24 @@
ShowcaseScreen:
name: 'RstDocument'
fullscreen: True
on_parent: if not args[1]: textinput.focus = False
GridLayout:
cols: 2 if root.width > root.height else 1
spacing: '8dp'
TextInput:
id: textinput
text:
('.. _top:\n'
'\n'
'Hello world\n'
'===========\n'
'\n'
'This is an **emphased text**, *italic text*, ``interpreted text``.\n'
'And this is a reference to top_::\n'
'\n'
' $ print("Hello world")\n')
RstDocument:
text: textinput.text

View file

@ -0,0 +1,14 @@
ShowcaseScreen:
name: 'Scatter'
Widget:
Scatter:
id: scatter
size_hint: None, None
size: image.size
Image:
id: image
source: 'data/faust_github.jpg'
size: self.texture_size

View file

@ -0,0 +1,46 @@
#:import Factory kivy.factory.Factory
ShowcaseScreen:
name: 'ScreenManager'
fullscreen: True
BoxLayout:
size_hint_y: None
height: '48dp'
Spinner:
text: 'Default transition'
values: ('SlideTransition', 'SwapTransition', 'FadeTransition', 'WipeTransition')
on_text: sm.transition = Factory.get(self.text)()
ScreenManager:
id: sm
Screen:
name: 'screen1'
canvas.before:
Color:
rgb: .8, .2, .2
Rectangle:
size: self.size
AnchorLayout:
Button:
size_hint: None, None
size: '150dp', '48dp'
text: 'Go to screen 2'
on_release: sm.current = 'screen2'
Screen:
name: 'screen2'
canvas.before:
Color:
rgb: .2, .8, .2
Rectangle:
size: self.size
AnchorLayout:
Button:
size_hint: None, None
size: '150dp', '48dp'
text: 'Go to screen 1'
on_release: sm.current = 'screen1'

View file

@ -0,0 +1,55 @@
ShowcaseScreen:
name: 'Sliders'
BoxLayout:
size_hint_y: None
height: '48dp'
Label:
text: 'Default'
Slider:
id: s1
Label:
text: '{}'.format(s1.value)
BoxLayout:
size_hint_y: None
height: '48dp'
Label:
text: 'Stepped'
Slider:
id: s2
step: 20
Label:
text: '{}'.format(s2.value)
AnchorLayout:
size_hint_y: None
height: '100dp'
GridLayout:
cols: 2
spacing: '8dp'
size_hint_x: None
width: self.minimum_width
Slider:
size_hint_x: None
width: '48dp'
orientation: 'vertical'
value: s1.value
on_value: s1.value = self.value
Slider:
size_hint_x: None
width: '48dp'
orientation: 'vertical'
step: 20
value: s2.value
on_value: s2.value = self.value

View file

@ -0,0 +1,11 @@
ShowcaseScreen:
name: 'Spinner'
fullscreen: True
Spinner:
text: 'Home'
values: ('Home', 'Work', 'Other', 'Not defined')
size_hint_y: None
height: '48dp'
Widget

View file

@ -0,0 +1,13 @@
ShowcaseScreen:
name: 'Splitter'
fullscreen: True
RelativeLayout:
id: rl
Splitter:
sizable_from: 'right'
min_size: 10
max_size: rl.width
Button:
text: 'Panel'

View file

@ -0,0 +1,39 @@
ShowcaseScreen:
name: 'Switches'
BoxLayout:
size_hint_y: None
height: '48dp'
Label:
text: 'Switch normal'
Switch:
BoxLayout:
size_hint_y: None
height: '48dp'
Label:
text: 'Switch active'
Switch:
active: True
BoxLayout:
size_hint_y: None
height: '48dp'
Label:
text: 'Switch off & disabled'
Switch:
disabled: True
active: False
BoxLayout:
size_hint_y: None
height: '48dp'
Label:
text: 'Switch on & disabled'
Switch:
disabled: True
active: True

View file

@ -0,0 +1,69 @@
#:import random random.random
ShowcaseScreen:
name: 'TabbedPanel + Layouts'
fullscreen: True
on_parent: if args[1] and tp.current_tab == tab_fl: app.showcase_floatlayout(fl)
TabbedPanel:
id: tp
do_default_tab: False
TabbedPanelItem:
id: tab_fl
text: 'FloatLayout'
on_release: app.showcase_floatlayout(fl)
FloatLayout:
CFloatLayout:
id: fl
TabbedPanelItem:
text: 'BoxLayout'
on_release: app.showcase_boxlayout(box)
FloatLayout
CBoxLayout:
id: box
TabbedPanelItem:
text: 'GridLayout'
on_release: app.showcase_gridlayout(grid)
FloatLayout
CGridLayout:
id: grid
rows: 3
TabbedPanelItem:
on_release: app.showcase_stacklayout(stack)
text: 'StackLayout'
FloatLayout
CStackLayout:
id: stack
TabbedPanelItem:
text: 'AnchorLayout'
on_release: app.showcase_anchorlayout(anchor)
FloatLayout
CAnchorLayout:
id: anchor
BoxLayout:
orientation: 'vertical'
size_hint: .4, .5
Button
Button
text: 'anchor_x: {}'.format(anchor.anchor_x)
Button
text: 'anchor_y: {}'.format(anchor.anchor_y)
Button
<CFloatLayout@FloatLayout+BackgroundColor>
<CBoxLayout@BoxLayout+BackgroundColor>
<CGridLayout@GridLayout+BackgroundColor>
<CStackLayout@StackLayout+BackgroundColor>
<CAnchorLayout@AnchorLayout+BackgroundColor>
<BackgroundColor@Widget>
pos_hint: {'center_x': .5, 'center_y': .5}
size_hint: .9, .9
canvas.before:
Color:
rgba: .2, .3, .4, 1
Rectangle:
size: self.size
pos: self.pos

View file

@ -0,0 +1,48 @@
ShowcaseScreen:
name: 'TextInputs'
focused: ti_default
on_parent:
if not args[1] and self.focused: self.focused.focus = False
if args[1]: ti_default.focus = True
CTextInput
size_hint_y: None
height: '32dp'
multiline: False
text: 'Monoline textinput'
CTextInput:
id: ti_default
size_hint_y: None
height: '32dp'
text: 'Focused textinput'
focus: True
CTextInput:
size_hint_y: None
height: '32dp'
text: 'Password'
password: True
CTextInput:
size_hint_y: None
height: '32dp'
text: 'Readonly textinput'
readonly: True
CTextInput:
size_hint_y: None
height: '48dp'
text: 'Multiline textinput\nSecond line'
multiline: True
CTextInput:
size_hint_y: None
height: '32dp'
disabled: True
text: 'Disabled textinput'
<CTextInput@TextInput>
on_focus:
screen = self.parent.parent.parent.parent
if screen.parent: screen.focused = self

View file

@ -0,0 +1,39 @@
ShowcaseScreen:
name: 'ToggleButton'
GridLayout:
cols: 3
spacing: '8dp'
size_hint_y: None
height: self.minimum_height
Label:
text: 'Choice 1'
ToggleButton:
size_hint_y: None
height: '48dp'
text: 'A'
group: 'g1'
ToggleButton:
size_hint_y: None
height: '48dp'
text: 'B'
group: 'g1'
Label:
text: 'Choice 2'
ToggleButton:
size_hint_y: None
height: '48dp'
text: 'A'
group: 'g2'
ToggleButton:
size_hint_y: None
height: '48dp'
text: 'B'
group: 'g2'

View file

@ -0,0 +1,243 @@
'''
Showcase of Kivy Features
=========================
This showcases many features of Kivy. You should see a
menu bar across the top with a demonstration area below. The
first demonstration is the accordion layout. You can see, but not
edit, the kv language code for any screen by pressing the bug or
'show source' icon. Scroll through the demonstrations using the
left and right icons in the top right or selecting from the menu
bar.
The file showcase.kv describes the main container, while each demonstration
pane is described in a separate .kv file in the data/screens directory.
The image data/background.png provides the gradient background while the
icons in data/icon directory are used in the control bar. The file
data/faust_github.jpg is used in the Scatter pane. The icons are
from `http://www.gentleface.com/free_icon_set.html` and licensed as
Creative Commons - Attribution and Non-commercial Use Only; they
sell a commercial license.
The file android.txt is used to package the application for use with the
Kivy Launcher Android application. For Android devices, you can
copy/paste this directory into /sdcard/kivy/showcase on your Android device.
'''
from time import time
from kivy.app import App
from os.path import dirname, join
from kivy.lang import Builder
from kivy.properties import (
NumericProperty,
StringProperty,
BooleanProperty,
ListProperty,
)
from kivy.clock import Clock
from kivy.animation import Animation
from kivy.uix.screenmanager import Screen
class ShowcaseScreen(Screen):
fullscreen = BooleanProperty(False)
def add_widget(self, *args, **kwargs):
if 'content' in self.ids:
return self.ids.content.add_widget(*args, **kwargs)
return super(ShowcaseScreen, self).add_widget(*args, **kwargs)
class ShowcaseApp(App):
index = NumericProperty(-1)
current_title = StringProperty()
time = NumericProperty(0)
show_sourcecode = BooleanProperty(False)
sourcecode = StringProperty()
screen_names = ListProperty([])
hierarchy = ListProperty([])
def build(self):
self.title = 'hello world'
Clock.schedule_interval(self._update_clock, 1 / 60.)
self.screens = {}
self.available_screens = sorted([
'Buttons', 'ToggleButton', 'Sliders', 'ProgressBar', 'Switches',
'CheckBoxes', 'TextInputs', 'Accordions', 'FileChoosers',
'Carousel', 'Bubbles', 'CodeInput', 'DropDown', 'Spinner',
'Scatter', 'Splitter', 'TabbedPanel + Layouts', 'RstDocument',
'Popups', 'ScreenManager'])
self.screen_names = self.available_screens
curdir = dirname(__file__)
self.available_screens = [join(curdir, 'data', 'screens',
'{}.kv'.format(fn).lower()) for fn in self.available_screens]
self.go_next_screen()
def on_pause(self):
return True
def on_resume(self):
pass
def on_current_title(self, instance, value):
self.root.ids.spnr.text = value
def go_previous_screen(self):
self.index = (self.index - 1) % len(self.available_screens)
screen = self.load_screen(self.index)
sm = self.root.ids.sm
sm.switch_to(screen, direction='right')
self.current_title = screen.name
self.update_sourcecode()
def go_next_screen(self):
self.index = (self.index + 1) % len(self.available_screens)
screen = self.load_screen(self.index)
sm = self.root.ids.sm
sm.switch_to(screen, direction='left')
self.current_title = screen.name
self.update_sourcecode()
def go_screen(self, idx):
self.index = idx
self.root.ids.sm.switch_to(self.load_screen(idx), direction='left')
self.update_sourcecode()
def go_hierarchy_previous(self):
ahr = self.hierarchy
if len(ahr) == 1:
return
if ahr:
ahr.pop()
if ahr:
idx = ahr.pop()
self.go_screen(idx)
def load_screen(self, index):
if index in self.screens:
return self.screens[index]
screen = Builder.load_file(self.available_screens[index])
self.screens[index] = screen
return screen
def read_sourcecode(self):
fn = self.available_screens[self.index]
with open(fn) as fd:
return fd.read()
def toggle_source_code(self):
self.show_sourcecode = not self.show_sourcecode
if self.show_sourcecode:
height = self.root.height * .3
else:
height = 0
Animation(height=height, d=.3, t='out_quart').start(
self.root.ids.sv)
self.update_sourcecode()
def update_sourcecode(self):
if not self.show_sourcecode:
self.root.ids.sourcecode.focus = False
return
self.root.ids.sourcecode.text = self.read_sourcecode()
self.root.ids.sv.scroll_y = 1
def showcase_floatlayout(self, layout):
def add_button(*t):
if not layout.get_parent_window():
return
if len(layout.children) > 5:
layout.clear_widgets()
layout.add_widget(Builder.load_string('''
#:import random random.random
Button:
size_hint: random(), random()
pos_hint: {'x': random(), 'y': random()}
text:
'size_hint x: {} y: {}\\n pos_hint x: {} y: {}'.format(\
self.size_hint_x, self.size_hint_y, self.pos_hint['x'],\
self.pos_hint['y'])
'''))
Clock.schedule_once(add_button, 1)
Clock.schedule_once(add_button)
def showcase_boxlayout(self, layout):
def add_button(*t):
if not layout.get_parent_window():
return
if len(layout.children) > 5:
layout.orientation = 'vertical'\
if layout.orientation == 'horizontal' else 'horizontal'
layout.clear_widgets()
layout.add_widget(Builder.load_string('''
Button:
text: self.parent.orientation if self.parent else ''
'''))
Clock.schedule_once(add_button, 1)
Clock.schedule_once(add_button)
def showcase_gridlayout(self, layout):
def add_button(*t):
if not layout.get_parent_window():
return
if len(layout.children) > 15:
layout.rows = 3 if layout.rows is None else None
layout.cols = None if layout.rows == 3 else 3
layout.clear_widgets()
layout.add_widget(Builder.load_string('''
Button:
text:
'rows: {}\\ncols: {}'.format(self.parent.rows, self.parent.cols)\
if self.parent else ''
'''))
Clock.schedule_once(add_button, 1)
Clock.schedule_once(add_button)
def showcase_stacklayout(self, layout):
orientations = ('lr-tb', 'tb-lr',
'rl-tb', 'tb-rl',
'lr-bt', 'bt-lr',
'rl-bt', 'bt-rl')
def add_button(*t):
if not layout.get_parent_window():
return
if len(layout.children) > 11:
layout.clear_widgets()
cur_orientation = orientations.index(layout.orientation)
layout.orientation = orientations[cur_orientation - 1]
layout.add_widget(Builder.load_string('''
Button:
text: self.parent.orientation if self.parent else ''
size_hint: .2, .2
'''))
Clock.schedule_once(add_button, 1)
Clock.schedule_once(add_button)
def showcase_anchorlayout(self, layout):
def change_anchor(self, *l):
if not layout.get_parent_window():
return
anchor_x = ('left', 'center', 'right')
anchor_y = ('top', 'center', 'bottom')
if layout.anchor_x == 'left':
layout.anchor_y = anchor_y[anchor_y.index(layout.anchor_y) - 1]
layout.anchor_x = anchor_x[anchor_x.index(layout.anchor_x) - 1]
Clock.schedule_once(change_anchor, 1)
Clock.schedule_once(change_anchor, 1)
def _update_clock(self, dt):
self.time = time()
if __name__ == '__main__':
ShowcaseApp().run()

View file

@ -0,0 +1,100 @@
#:kivy 1.8.0
#:import KivyLexer kivy.extras.highlight.KivyLexer
#:import Factory kivy.factory.Factory
<ActionSpinnerOptions@SpinnerOption>
background_color: .4, .4, .4, 1
<ActionSpinner@Spinner+ActionItem>
canvas.before:
Color:
rgba: 0.128, 0.128, 0.128, 1
Rectangle:
size: self.size
pos: self.pos
border: 27, 20, 12, 12
background_normal: 'atlas://data/images/defaulttheme/action_group'
option_cls: Factory.ActionSpinnerOptions
<ActionDropdown>:
on_size: self.width = '220dp'
<ShowcaseScreen>:
ScrollView:
do_scroll_x: False
do_scroll_y: False if root.fullscreen else (content.height > root.height - dp(16))
AnchorLayout:
size_hint_y: None
height: root.height if root.fullscreen else max(root.height, content.height)
GridLayout:
id: content
cols: 1
spacing: '8dp'
padding: '8dp'
size_hint: (1, 1) if root.fullscreen else (.8, None)
height: self.height if root.fullscreen else self.minimum_height
BoxLayout:
orientation: 'vertical'
canvas.before:
Color:
rgb: .6, .6, .6
Rectangle:
size: self.size
source: 'data/background.png'
ActionBar:
ActionView:
id: av
ActionPrevious:
with_previous: (False if sm.current_screen.name == 'button' else True) if sm.current_screen else False
title: 'Showcase' + ('' if not app.current_title else ' - {}'.format(app.current_title))
on_release: app.go_hierarchy_previous()
ActionSpinner:
id: spnr
important: True
text: 'Jump to Screen'
values: app.screen_names
on_text:
if sm.current != args[1]:\
idx = app.screen_names.index(args[1]);\
app.go_screen(idx)
ActionToggleButton:
text: 'Toggle sourcecode'
icon: 'data/icons/bug.png'
on_release: app.toggle_source_code()
ActionButton:
text: 'Previous screen'
icon: 'data/icons/chevron-left.png'
on_release: app.go_previous_screen()
ActionButton:
text: 'Next screen'
icon: 'data/icons/chevron-right.png'
on_release: app.go_next_screen()
important: True
ScrollView:
id: sv
size_hint_y: None
height: 0
CodeInput:
id: sourcecode
lexer: KivyLexer()
text: app.sourcecode
readonly: True
size_hint_y: None
font_size: '12sp'
height: self.minimum_height
ScreenManager:
id: sm
on_current_screen:
spnr.text = args[1].name
idx = app.screen_names.index(args[1].name)
if idx > -1: app.hierarchy.append(idx)

View file

@ -0,0 +1,12 @@
Touchtracer
===========
Touchtracer is a simple example that draws lines using all the touch events
detected by your hardware.
Android
-------
You can copy/paste this directory into /sdcard/kivy/touchtracer in your
android device.

View file

@ -0,0 +1,3 @@
title=Touchtracer
author=Kivy team
orientation=landscape

Binary file not shown.

After

Width:  |  Height:  |  Size: 576 B

View file

@ -0,0 +1,170 @@
'''
Touch Tracer Line Drawing Demonstration
=======================================
This demonstrates tracking each touch registered to a device. You should
see a basic background image. When you press and hold the mouse, you
should see cross-hairs with the coordinates written next to them. As
you drag, it leaves a trail. Additional information, like pressure,
will be shown if they are in your device's touch.profile.
.. note::
A function `calculate_points` handling the points which will be drawn
has by default implemented a delay of 5 steps. To get more precise visual
results lower the value of the optional keyword argument `steps`.
This program specifies an icon, the file icon.png, in its App subclass.
It also uses the particle.png file as the source for drawing the trails which
are white on transparent. The file touchtracer.kv describes the application.
The file android.txt is used to package the application for use with the
Kivy Launcher Android application. For Android devices, you can
copy/paste this directory into /sdcard/kivy/touchtracer on your Android device.
'''
__version__ = '1.0'
import kivy
kivy.require('1.0.6')
from kivy.app import App
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.label import Label
from kivy.graphics import Color, Rectangle, Point, GraphicException
from kivy.metrics import dp
from random import random
from math import sqrt
def calculate_points(x1, y1, x2, y2, steps=5):
dx = x2 - x1
dy = y2 - y1
dist = sqrt(dx * dx + dy * dy)
if dist < steps:
return
o = []
m = dist / steps
for i in range(1, int(m)):
mi = i / m
lastx = x1 + dx * mi
lasty = y1 + dy * mi
o.extend([lastx, lasty])
return o
class Touchtracer(FloatLayout):
def normalize_pressure(self, pressure):
print(pressure)
# this might mean we are on a device whose pressure value is
# incorrectly reported by SDL2, like recent iOS devices.
if pressure == 0.0:
return 1
return dp(pressure * 10)
def on_touch_down(self, touch):
win = self.get_parent_window()
ud = touch.ud
ud['group'] = g = str(touch.uid)
pointsize = 5
print(touch.profile)
if 'pressure' in touch.profile:
ud['pressure'] = touch.pressure
pointsize = self.normalize_pressure(touch.pressure)
ud['color'] = random()
with self.canvas:
Color(ud['color'], 1, 1, mode='hsv', group=g)
ud['lines'] = [
Rectangle(pos=(touch.x, 0), size=(1, win.height), group=g),
Rectangle(pos=(0, touch.y), size=(win.width, 1), group=g),
Point(points=(touch.x, touch.y), source='particle.png',
pointsize=pointsize, group=g)]
ud['label'] = Label(size_hint=(None, None))
self.update_touch_label(ud['label'], touch)
self.add_widget(ud['label'])
touch.grab(self)
return True
def on_touch_move(self, touch):
if touch.grab_current is not self:
return
ud = touch.ud
ud['lines'][0].pos = touch.x, 0
ud['lines'][1].pos = 0, touch.y
index = -1
while True:
try:
points = ud['lines'][index].points
oldx, oldy = points[-2], points[-1]
break
except IndexError:
index -= 1
points = calculate_points(oldx, oldy, touch.x, touch.y)
# if pressure changed create a new point instruction
if 'pressure' in ud:
old_pressure = ud['pressure']
if (
not old_pressure
or not .99 < (touch.pressure / old_pressure) < 1.01
):
g = ud['group']
pointsize = self.normalize_pressure(touch.pressure)
with self.canvas:
Color(ud['color'], 1, 1, mode='hsv', group=g)
ud['lines'].append(
Point(points=(), source='particle.png',
pointsize=pointsize, group=g))
if points:
try:
lp = ud['lines'][-1].add_point
for idx in range(0, len(points), 2):
lp(points[idx], points[idx + 1])
except GraphicException:
pass
ud['label'].pos = touch.pos
import time
t = int(time.time())
if t not in ud:
ud[t] = 1
else:
ud[t] += 1
self.update_touch_label(ud['label'], touch)
def on_touch_up(self, touch):
if touch.grab_current is not self:
return
touch.ungrab(self)
ud = touch.ud
self.canvas.remove_group(ud['group'])
self.remove_widget(ud['label'])
def update_touch_label(self, label, touch):
label.text = 'ID: %s\nPos: (%d, %d)\nClass: %s' % (
touch.id, touch.x, touch.y, touch.__class__.__name__)
label.texture_update()
label.pos = touch.pos
label.size = label.texture_size[0] + 20, label.texture_size[1] + 20
class TouchtracerApp(App):
title = 'Touchtracer'
icon = 'icon.png'
def build(self):
return Touchtracer()
def on_pause(self):
return True
if __name__ == '__main__':
TouchtracerApp().run()

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

View file

@ -0,0 +1,29 @@
#:kivy 1.0
#:import kivy kivy
<Touchtracer>:
canvas:
Color:
rgb: 1, 1, 1
Rectangle:
source: 'data/images/background.jpg'
size: self.size
BoxLayout:
padding: '10dp'
spacing: '10dp'
size_hint: 1, None
pos_hint: {'top': 1}
height: '44dp'
Image:
size_hint: None, None
size: '24dp', '24dp'
source: 'data/logo/kivy-icon-64.png'
mipmap: True
Label:
height: '24dp'
text_size: self.width, None
color: (1, 1, 1, .8)
text: 'Kivy %s - Touchtracer' % kivy.__version__
valign: 'middle'