Skip to content

Instantly share code, notes, and snippets.

@Cheaterman
Last active September 2, 2023 18:44
Show Gist options
  • Save Cheaterman/3295e5bcd380c3dcaf26083c98586295 to your computer and use it in GitHub Desktop.
Save Cheaterman/3295e5bcd380c3dcaf26083c98586295 to your computer and use it in GitHub Desktop.
Thinking in Kivy - "Thinking in React" example using Kivy
from kivy.app import App
from kivy.lang import Builder
from kivy.properties import (
BooleanProperty,
ListProperty,
StringProperty,
)
from kivy.uix.boxlayout import BoxLayout
KV = '''
FilterableProductTable:
products: app.products
size_hint_x: .5
<LabelButton@ButtonBehavior+Label>:
<FilterableProductTable>:
filter_text: searchbar.filter_text
in_stock_only: searchbar.in_stock_only
orientation: 'vertical'
pos_hint: {'top': 1}
SearchBar:
id: searchbar
ProductTable:
products: root.filtered_products
<SearchBar>:
orientation: 'vertical'
size_hint_y: None
height: self.minimum_height
TextInput:
text: root.filter_text
hint_text: 'Search...'
size_hint_y: None
height: self.minimum_height
on_text: root.filter_text = args[1]
BoxLayout:
size_hint_y: None
height: sp(32) # See data/style.kv in Kivy
CheckBox:
id: in_stock_only
size_hint_x: None
width: sp(32) # See data/style.kv in Kivy
state: 'down' if root.in_stock_only else 'normal'
on_state: root.in_stock_only = args[1] == 'down'
LabelButton:
text: 'Only show products in stock'
text_size: self.width, None
on_press: in_stock_only.trigger_action()
<ProductCategoryRow@BoxLayout>:
category: ''
Label:
text: root.category
bold: True
<ProductRow@BoxLayout>:
product: {}
Label:
text: (root.product).get('name', '')
color:
(
rgba('#FFFFFF') if (root.product).get('stocked')
else rgba('#FF0000')
)
Label:
text: '${}'.format((root.product).get('price', 0))
<ProductTable>:
orientation: 'vertical'
BoxLayout:
size_hint_y: None
height: 25
Label:
text: 'Name'
bold: True
Label:
text: 'Price'
bold: True
RecycleView:
data: root.data
key_viewclass: 'viewclass'
RecycleBoxLayout:
default_size_hint: 1, None
default_size: None, 25
orientation: 'vertical'
size_hint_y: None
height: self.minimum_height
'''
class FilterableProductTable(BoxLayout):
# Inputs
products = ListProperty()
filter_text = StringProperty()
in_stock_only = BooleanProperty()
# Output (to children components)
filtered_products = ListProperty()
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.bind(
products=self.filter_products,
filter_text=self.filter_products,
in_stock_only=self.filter_products,
)
def filter_products(self, *args):
filter_text = self.filter_text
in_stock_only = self.in_stock_only
self.filtered_products = [
product for product in self.products
if (
filter_text in product['name']
and (not in_stock_only or product['stocked'])
)
]
class SearchBar(BoxLayout):
# Outputs (to parent component)
filter_text = StringProperty('')
in_stock_only = BooleanProperty(False)
class ProductTable(BoxLayout):
# Input
products = ListProperty()
# Output (to children components)
data = ListProperty()
def on_products(self, _, products):
data = []
last_category = None
for product in products:
if product['category'] != last_category:
data.append({
'viewclass': 'ProductCategoryRow',
'category': product['category']
})
last_category = product['category']
data.append({
'viewclass': 'ProductRow',
'product': product,
})
self.data = data
PRODUCTS = [
{
'category': 'Fruits',
'price': 1,
'stocked': True,
'name': 'Apple',
},
{
'category': 'Fruits',
'price': 1,
'stocked': True,
'name': 'Dragonfruit',
},
{
'category': 'Fruits',
'price': 2,
'stocked': False,
'name': 'Passionfruit',
},
{
'category': 'Vegetables',
'price': 2,
'stocked': True,
'name': 'Spinach',
},
{
'category': 'Vegetables',
'price': 4,
'stocked': False,
'name': 'Pumpkin',
},
{
'category': 'Vegetables',
'price': 1,
'stocked': True,
'name': 'Peas',
},
]
class ProductsApp(App):
def build(self):
self.products = PRODUCTS
return Builder.load_string(KV)
app = ProductsApp()
if __name__ == '__main__':
app.run()
# See https://react.dev/learn/thinking-in-react
from kivy.app import App
from kivy.lang import Builder
from kivy.properties import (
BooleanProperty,
ListProperty,
StringProperty,
)
from kivy.uix.boxlayout import BoxLayout
KV = '''
FilterableProductTable:
products: app.products
size_hint_x: .5
<FilterableProductTable>:
orientation: 'vertical'
pos_hint: {'top': 1}
SearchBar:
filter_text: root.filter_text
in_stock_only: root.in_stock_only
on_filter_text: root.filter_text = args[1]
on_in_stock_only: root.in_stock_only = args[1]
ProductTable:
products: root.products
filter_text: root.filter_text
in_stock_only: root.in_stock_only
<ProductCategoryRow@BoxLayout>:
category: ''
Label:
text: root.category
bold: True
<ProductRow@BoxLayout>:
product: {}
Label:
text: (root.product).get('name', '')
color:
(
rgba('#FFFFFF') if (root.product).get('stocked')
else rgba('#FF0000')
)
Label:
text: (root.product).get('price', '')
<ProductTable>:
orientation: 'vertical'
BoxLayout:
size_hint_y: None
height: 25
Label:
text: 'Name'
bold: True
Label:
text: 'Price'
bold: True
RecycleView:
data: root.data
key_viewclass: 'viewclass'
RecycleBoxLayout:
default_size_hint: 1, None
default_size: None, 25
orientation: 'vertical'
size_hint_y: None
height: self.minimum_height
<SearchBar>:
orientation: 'vertical'
size_hint_y: None
height: self.minimum_height
TextInput:
text: root.filter_text
hint_text: 'Search...'
size_hint_y: None
height: self.minimum_height
on_text: root.filter_text = args[1]
BoxLayout:
size_hint_y: None
height: sp(32) # See data/style.kv in Kivy
CheckBox:
id: in_stock_only
size_hint_x: None
width: sp(32) # See data/style.kv in Kivy
state: 'down' if root.in_stock_only else 'normal'
on_state: root.in_stock_only = args[1] == 'down'
LabelButton:
text: 'Only show products in stock'
text_size: self.width, None
on_press: in_stock_only.trigger_action()
<LabelButton@ButtonBehavior+Label>:
'''
class FilterableProductTable(BoxLayout):
# Input
products = ListProperty()
# Outputs (to children components)
filter_text = StringProperty('')
in_stock_only = BooleanProperty(False)
class ProductTable(BoxLayout):
# Inputs
products = ListProperty()
filter_text = StringProperty('')
in_stock_only = BooleanProperty(False)
# Output (to children components)
data = ListProperty()
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.bind(
products=self.update_products,
filter_text=self.update_products,
in_stock_only=self.update_products,
)
def update_products(self, *args):
data = []
last_category = None
for product in self.products:
if self.filter_text not in product['name']:
continue
if self.in_stock_only and not product['stocked']:
continue
if product['category'] != last_category:
data.append({
'viewclass': 'ProductCategoryRow',
'category': product['category']
})
data.append({
'viewclass': 'ProductRow',
'product': product,
})
last_category = product['category']
self.data = data
class SearchBar(BoxLayout):
filter_text = StringProperty()
in_stock_only = BooleanProperty()
PRODUCTS = [
{
'category': 'Fruits',
'price': '$1',
'stocked': True,
'name': 'Apple',
},
{
'category': 'Fruits',
'price': '$1',
'stocked': True,
'name': 'Dragonfruit',
},
{
'category': 'Fruits',
'price': '$2',
'stocked': False,
'name': 'Passionfruit',
},
{
'category': 'Vegetables',
'price': '$2',
'stocked': True,
'name': 'Spinach',
},
{
'category': 'Vegetables',
'price': '$4',
'stocked': False,
'name': 'Pumpkin',
},
{
'category': 'Vegetables',
'price': '$1',
'stocked': True,
'name': 'Peas',
},
]
class ProductsApp(App):
def build(self):
self.products = PRODUCTS
return Builder.load_string(KV)
app = ProductsApp()
if __name__ == '__main__':
app.run()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment