Last active
September 2, 2023 18:44
-
-
Save Cheaterman/3295e5bcd380c3dcaf26083c98586295 to your computer and use it in GitHub Desktop.
Thinking in Kivy - "Thinking in React" example using Kivy
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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