first commit
98
kivy_venv/share/kivy-examples/demo/camera_puzzle.py
Normal 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()
|
|
@ -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
|
|
@ -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"
|
|
@ -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"
|
|
@ -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"
|
|
@ -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
|
|
@ -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
|
|
@ -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"
|
|
@ -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'
|
|
@ -0,0 +1,9 @@
|
|||
#:kivy 1.4
|
||||
|
||||
BoxLayout:
|
||||
orientation: "vertical"
|
||||
Image:
|
||||
source: "../../widgets/cityCC0.png"
|
||||
Video:
|
||||
source: "../../widgets/cityCC0.mpg"
|
||||
state: "play"
|
|
@ -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."
|
|
@ -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()
|
|
@ -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
|
|
@ -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"
|
|
@ -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"
|
|
@ -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
|
|
@ -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
|
|
@ -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"
|
144
kivy_venv/share/kivy-examples/demo/kivycatalog/kivycatalog.kv
Normal 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
|
192
kivy_venv/share/kivy-examples/demo/kivycatalog/main.py
Normal 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()
|
|
@ -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()
|
|
@ -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()
|
30
kivy_venv/share/kivy-examples/demo/multistroke/helpers.py
Normal 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)
|
164
kivy_venv/share/kivy-examples/demo/multistroke/historymanager.kv
Normal 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()
|
276
kivy_venv/share/kivy-examples/demo/multistroke/historymanager.py
Normal 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
|
144
kivy_venv/share/kivy-examples/demo/multistroke/main.py
Normal 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()
|
101
kivy_venv/share/kivy-examples/demo/multistroke/multistroke.kv
Normal 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
|
110
kivy_venv/share/kivy-examples/demo/multistroke/settings.kv
Normal 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()
|
97
kivy_venv/share/kivy-examples/demo/multistroke/settings.py
Normal 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)
|
3
kivy_venv/share/kivy-examples/demo/pictures/android.txt
Normal file
|
@ -0,0 +1,3 @@
|
|||
title=Pictures
|
||||
author=Kivy team
|
||||
orientation=landscape
|
BIN
kivy_venv/share/kivy-examples/demo/pictures/images/Bubbles.jpg
Normal file
After Width: | Height: | Size: 84 KiB |
BIN
kivy_venv/share/kivy-examples/demo/pictures/images/Ill1.jpg
Normal file
After Width: | Height: | Size: 64 KiB |
BIN
kivy_venv/share/kivy-examples/demo/pictures/images/Wall.jpg
Normal file
After Width: | Height: | Size: 298 KiB |
After Width: | Height: | Size: 220 KiB |
73
kivy_venv/share/kivy-examples/demo/pictures/main.py
Normal 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()
|
57
kivy_venv/share/kivy-examples/demo/pictures/pictures.kv
Normal 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)
|
||||
|
||||
|
||||
|
BIN
kivy_venv/share/kivy-examples/demo/pictures/shadow32.png
Normal file
After Width: | Height: | Size: 3.5 KiB |
147
kivy_venv/share/kivy-examples/demo/shadereditor/main.py
Normal 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()
|
|
@ -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
|
11
kivy_venv/share/kivy-examples/demo/showcase/README.txt
Normal 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.
|
||||
|
3
kivy_venv/share/kivy-examples/demo/showcase/android.txt
Normal file
|
@ -0,0 +1,3 @@
|
|||
title=Showcase
|
||||
author=Kivy team
|
||||
orientation=landscape
|
BIN
kivy_venv/share/kivy-examples/demo/showcase/data/background.png
Normal file
After Width: | Height: | Size: 40 KiB |
After Width: | Height: | Size: 220 KiB |
|
@ -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
|
BIN
kivy_venv/share/kivy-examples/demo/showcase/data/icons/bug.png
Normal file
After Width: | Height: | Size: 1.7 KiB |
After Width: | Height: | Size: 587 B |
After Width: | Height: | Size: 584 B |
|
@ -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
|
|
@ -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'
|
|
@ -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
|
|
@ -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'
|
|
@ -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'
|
|
@ -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
|
|
@ -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')
|
|
@ -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
|
|
@ -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()
|
|
@ -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.
|
||||
|
|
@ -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
|
|
@ -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
|
|
@ -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'
|
|
@ -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
|
|
@ -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
|
|
@ -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'
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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'
|
243
kivy_venv/share/kivy-examples/demo/showcase/main.py
Normal 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()
|
100
kivy_venv/share/kivy-examples/demo/showcase/showcase.kv
Normal 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)
|
12
kivy_venv/share/kivy-examples/demo/touchtracer/README.txt
Normal 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.
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
title=Touchtracer
|
||||
author=Kivy team
|
||||
orientation=landscape
|
BIN
kivy_venv/share/kivy-examples/demo/touchtracer/icon.png
Normal file
After Width: | Height: | Size: 576 B |
170
kivy_venv/share/kivy-examples/demo/touchtracer/main.py
Normal 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()
|
BIN
kivy_venv/share/kivy-examples/demo/touchtracer/particle.png
Normal file
After Width: | Height: | Size: 1.7 KiB |
|
@ -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'
|
||||
|