How do I keep KivyMD bottom app toolbar on multiple screens - kivy

I am trying to keep the KivyMD toolbar on multiple screens, I have searched on stackoverflow before, and found out I may need to create a class and inherit it. Question: Is this right, if yes I would be very grateful is you could make a sample of the class, and if no I would equally be grateful for your opinion.. Thanks.
Here's my .py file
from kivymd.app import MDApp
from kivy.uix.screenmanager import Screen, ScreenManager, NoTransition
from kivy.core.window import Window
Window.size = (300, 500)
class Screen1(Screen):
pass
class Screen2(Screen):
pass
class TwoScreenApp(MDApp):
def build(self):
self.theme_cls.primary_palette = 'Gray'
self.sm = ScreenManager(transition=NoTransition())
self.sm.add_widget(Screen1(name='screen1'))
self.sm.add_widget(Screen2(name='screen2'))
return self.sm
def change_screen(self, screen):
self.sm.current = screen
"""I am using this self.sm method to link
icon to screen2 until I find a better way"""
TwoScreenApp().run()
and Here's my .kv file
ScreenManager:
Screen1:
Screen2:
<Screen1>
name: 'screen1'
MDBoxLayout:
md_bg_color: (240/255, 240/255, 240/255, 1)
orientation: 'vertical'
MDLabel:
halign: 'center'
text:
"""With the production of the Model T automobile,
Henry Ford had an unforeseen and tremendous
impact on American life. He became regarded
as an apt symbol of the transition from an
agricultural to an industrial America."""
MDBottomAppBar:
MDToolbar:
icon: "account-circle"
type: "bottom"
left_action_items: [["arrow-right", lambda x: app.change_screen("screen2")], ["alpha-x-circle", lambda x: x]]
right_action_items: [["alpha-y-circle", lambda x: x], ["arrow-left", lambda x: app.change_screen("screen1")]]
elevation: 10
<Screen2>
name: 'screen2'
md_bg_color: (240/255, 240/255, 240/255, 1)
MDBoxLayout:
md_bg_color: (255/255, 255/255, 255/255, 1)
orientation: 'vertical'
MDLabel:
halign: 'center'
text:
"""The development of mass-production techniques, which
enabled the company eventually to turn out a Model T
every 24 seconds; the frequent reductions in the price
of the car made possible by economies of scale; and
the payment of a living wage that raised workers
above subsistence and made them potential customers
for, among other things, automobiles—these innovations
changed the very structure of society."""
MDBottomAppBar:
MDToolbar:
title: "Title"
icon: "account-circle"
type: "bottom"
left_action_items: [["menu", lambda x: app.change_screen("screen1")]]

Related

How to disable a widget when a button from a different class is pressed?

Im making a Button which disables a TopAppBar menu form my user interface, the problem is that the Button is on one class an the TopAppBar in another, because one belongs to the screen and the other is shared between multiple screens.
code:
from kivymd.app import MDApp
from kivy.lang import Builder
from kivy.core.window import Window
from kivymd.uix.screen import MDScreen
Window.size = (300, 500)
navigation_helper = """
<UserScreen>
name: 'user_screen'
MDBoxLayout:
orientation: "vertical"
pos_hint: {"top": 1}
adaptive_height: True
padding: (0, "80dp" , 0, 0)
FloatLayout:
MDRaisedButton:
id: save_btn_user
text: "Guardar"
md_bg_color: "white"
text_color: "black"
font_size: 16.2
pos_hint: {"left":1, "bottom": 1}
size_hint: (.2,.2)
disabled: True
on_press: root.save_btn_press()
MDRaisedButton:
id: edit_btn_user
text: "Editar"
md_bg_color: "white"
text_color: "black"
font_size: 16.2
pos_hint: {"right":1, "bottom": 1}
size_hint: (.2,.2)
disabled: False
on_press:
root.disable_top_bar()
<MainScreen>:
name: "main_screen"
BoxLayout:
orientation: 'vertical'
MDTopAppBar:
id: title_bar
title: 'Dietas'
md_bg_color: 0.08,0.07,0.45
specific_text_color: 1,1,1
left_action_items: [["menu", lambda x: nav_drawer.set_state('toggle')]]
Widget:
MDNavigationLayout:
ScreenManager:
id: scr
UserScreen:
MDNavigationDrawer:
id: nav_drawer
BoxLayout:
orientation: 'vertical'
spacing: '8dp'
ScrollView:
MDList:
OneLineIconListItem:
text: 'Usuario'
on_press:
scr.current= 'user_screen'
title_bar.title = "Usuario"
nav_drawer.set_state('close')
IconLeftWidgetWithoutTouch:
icon: 'account'
on_press:
scr.current= 'user_screen'
title_bar.title = "Usuario"
nav_drawer.set_state('close')
MainScreen:
"""
class UserScreen(MDScreen):
def disable_top_bar(self):
self.a=MainScreen()
self.a.disable_top_barr()
class MainScreen(MDScreen):
def disable_top_barr(self):
self.ids.title_bar.disabled = True
print("testmsg")
class DemoApp(MDApp):
def build(self):
self.theme_cls.theme_style = ("Dark")
screen = Builder.load_string(navigation_helper)
return screen
DemoApp().run()
As you may have seen, I tried creating two method, one in the TopAppBar class disabling it and printing a test message(that its shown) and the other in the Button class calling that function.
With the following approach
def disable_top_bar(self):
self.a=MainScreen()
self.a.disable_top_barr()
you will see no change in your GUI because each time you call that method, it will create a new instance of MainScreen (which is not related to your GUI) and call the asked method on it.
In order to access that specific instance you can use the method get_running_app of MDApp as follows,
def disable_top_bar(self):
# First access the running app instance.
app = MDApp.get_running_app()
# Access the target screen that you set as the root widget.
main_screen = app.root
# Call the method.
main_screen.disable_top_barr()
As a note avoid using predefined names like a, b, g etc. when working with KivyMD widgets.

KivyMD, MDFloatingActionButton: correct placement

I've been trying to use floating action buttons alongside FileChooser:
import kivy
kivy.require("2.1.0")
from kivy.utils import platform
from kivy.lang import Builder
from kivy.uix.floatlayout import FloatLayout
from kivymd.app import MDApp
from kivymd.uix.tab import MDTabsBase
from kivymd.icon_definitions import md_icons
import os
USERPATH = os.path.expanduser("~")
if platform == "android":
from android.storage import primary_external_storage_path
from android.storage import secondary_external_storage_path
from android.permissions import request_permissions, Permission
USERPATH = primary_external_storage_path()
request_permissions(
[Permission.WRITE_EXTERNAL_STORAGE, Permission.READ_EXTERNAL_STORAGE]
)
KV = '''
#:kivy 2.1.0
#:set dark_gray (.5, .5, .5, 1)
MDBoxLayout:
orientation: "vertical"
MDTabs:
id: ps_tabs
TabList:
id: ps_tab_list
icon: "folder"
FileChooserListView:
id: ps_filechooser
canvas.before:
Color:
rgba: dark_gray
Rectangle:
size: self.size
pos: self.pos
MDFloatingActionButton:
id: fc_playdir
icon: "folder"
pos_hint: {"center_x": .4, "center_y": .5}
MDFloatingActionButton:
id: fc_playfile
icon: "file"
pos_hint: {"center_x": .6, "center_y": .5}
TabDetails:
id: ps_tab_details
icon: "book"
MDScrollView:
'''
class TabList(FloatLayout, MDTabsBase):
"""The engaged power supplies tab."""
class TabDetails(FloatLayout, MDTabsBase):
"""The engaged power supply details tab."""
class Ron(MDApp):
def build(self):
return Builder.load_string(KV)
def on_start(self):
self.theme_cls.primary_palette = "Gray"
self.theme_cls.material_style = "M3"
if platform != "android":
self.root.ids.ps_tabs.lock_swiping = True
self.root.ids.ps_filechooser.rootpath = USERPATH
if __name__ == "__main__":
Ron().run()
Mouse clicks, as well as taps, shoot right through the buttons and into the FileChooser, no matter what I do. I tried placing the buttons into their own box layout, tried moving them up and down the widget tree. Nothing helps, while the buttons remain visible. Besides, on visiting the second tab, one can see two thick black button footprints. Something's very wrong and I can't think of anything else to try.

Kivy how to create dropdownlist within a GridLayout

I have 2 files one of the py and the other kv, well as the title says, I do not know how to add a dropdown.
---> main.py
from kivy.app import App
from kivy.uix.dropdown import DropDown
from kivy.uix.gridlayout import GridLayout
class MyGridLayout(GridLayout):
pass
class LayoutsApp(App):
def build(self):
return MyGridLayout()
if __name__ == '__main__':
LayoutsApp().run()
---> layouts.kv
<MyGridLayout>:
rows: 2
id: main_win
Dropdown:
id: dropdown
Button:
id: btn1
text: 'option 1'
Button:
id: btn2
text: 'option 2'
BoxLayout:
BoxLayout:
when compiling, it generates error for that part. How is the correct way to make the call for dropdown-list?
Question 3
call the Button id: btn2, how would I do it? - I mean:class
MyGridLayout(GridLayout):
Solution 3
Python script
Add import statements: from kivy.uix.dropdown import DropDown; from kivy.properties import ObjectProperty
Implement class CustomDropDown(DropDown):
In class MyGridLayout, declare an ObjectProperty at class level (e.g. dropdown = ObjectProperty(None); implement a method dropdown_open(self):; instantiate the class CustomDropDown and assigned it to self.dropdown, etc. Please refer to snippets for detail.
kv file
Remove self.dismiss()
Replace dynamic class, <CustomDropdown#DropDown>: with class rule, <CustomDropDown>:
Replace rows: 2 with rows: 3
Replace Factory.CustomDropdown().open(self) with root.dropdown_open(self)
Example 3
main.py - Snippets
from kivy.uix.dropdown import DropDown
from kivy.properties import ObjectProperty
class CustomDropDown(DropDown):
pass
class MyGridLayout(GridLayout):
dropdown = ObjectProperty(None)
def dropdown_open(self, instance):
self.dropdown = CustomDropDown()
self.dropdown.open(instance)
print(self.dropdown.ids.btn2.text)
layouts.kv - Snippets
<CustomDropDown>:
id: dropdown
on_select:
app.root.ids.btn.text = '{}'.format(args[1])
...
<MyGridLayout>:
rows: 3
id: main_win
Button:
id: btn
text: 'Press'
size_hint_y: None
height: '48dp'
on_release:
root.dropdown_open(self)
Output 3
Question 2
when I added these commands, it does not work: BoxLayout: orientation:
'vertical' size_hint: (.9,.9) BoxLayout: orientation: 'vertical'
size_hint_y: 0.5
Note
You might need to increase the rows from 2 to 3.
Example #2
Added the config for the two BoxLayout
For illustration, added colour to the canvas for both BoxLayout
layouts.kv
#:kivy 1.10.1
#:import Factory kivy.factory.Factory
<CustomDropdown#DropDown>:
id: dropdown
on_select:
app.root.ids.btn.text = '{}'.format(args[1])
self.dismiss()
Button:
id: btn1
text: 'option 1'
size_hint_y: None
height: '48dp'
on_release:
dropdown.select(btn1.text)
Button:
id: btn2
text: 'option 2'
size_hint_y: None
height: '48dp'
on_release:
dropdown.select(btn2.text)
<MyGridLayout>:
rows: 2
id: main_win
Button:
id: btn
text: 'Press'
on_release: Factory.CustomDropdown().open(self)
size_hint_y: None
height: '48dp'
BoxLayout:
orientation: 'vertical'
size_hint: (.9,.9)
canvas.before:
Color:
rgba: 1, 0, 0, 1 # red
Rectangle:
pos: self.pos
size: self.size
BoxLayout:
orientation: 'vertical'
size_hint_y: 0.5
canvas.before:
Color:
rgba: 0, 0, 1, 1 # blue
Rectangle:
pos: self.pos
size: self.size
Output #2
Solution #1
DropDown widget is like Popup widget i.e. they are special widget.
Popup / DropDown is a special widget. Don’t try to add it as a child to any other
widget. If you do, Popup / DropDown will be handled like an ordinary widget and
won’t be created hidden in the background.
Therefore, create a dynamic class with inheritance of DropDown widget, and use Factory to instantiate the class.
Example
main.py
from kivy.app import App
from kivy.uix.gridlayout import GridLayout
class MyGridLayout(GridLayout):
pass
class LayoutsApp(App):
def build(self):
return MyGridLayout()
if __name__ == '__main__':
LayoutsApp().run()
layouts.kv
#:kivy 1.10.1
#:import Factory kivy.factory.Factory
<CustomDropdown#DropDown>:
id: dropdown
on_select:
app.root.ids.btn.text = '{}'.format(args[1])
self.dismiss()
Button:
id: btn1
text: 'option 1'
size_hint_y: None
height: '48dp'
on_release:
dropdown.select(btn1.text)
Button:
id: btn2
text: 'option 2'
size_hint_y: None
height: '48dp'
on_release:
dropdown.select(btn2.text)
<MyGridLayout>:
rows: 2
id: main_win
Button:
id: btn
text: 'Press'
on_release: Factory.CustomDropdown().open(self)
size_hint_y: None
height: '48dp'
BoxLayout:
BoxLayout:
Output

Kivy Layout Issue

I am trying to create a GUI using Kivy. However, I cannot resolve some formatting issues.
Here is a slimmed down version of my KV file:
BoxLayout:
MainCanvas:
size_hint: 1,1
size: (root.width,root.height*.9)
DoubleEllipseWidget:
ActionBar:
id: _action
size_hint: 1,0.1
size: (root.width,root.height*.1)
pos_hint: {'bottom':1}
ActionView:
use_separator: True
ActionPrevious:
title: 'Test App:'
with_previous: False
ActionOverflow:
disabled: True
ActionButton:
important: True
text: 'Button 1'
#on_release: some_function
ActionButton:
text: 'Button 2'
#on_release: some_function
ActionButton:
text: 'Button 3'
#on_release: some_function
<DoubleEllipseWidget>
size: [200, 200]
canvas:
Color:
rgba: 0, 0, 0, 1
Ellipse
size: [198, 198]
pos: [600-200-100, 800-200-100]
Color:
rgba: 1, 1, 1, 1
Ellipse
size: [200, 200]
pos: [600-200-100, 800-200-100]
TextInput:
on_parent:self.focus = True
text: 'center of circle'
background_color: (0,0,0,0)
foreground_color: (0,0,0,1)
What I am trying to get to is very easily explained.
Essentially, there should be a menu bar running horizontally along the screen window (10% of total height and 100% of width). I believe I have done this.
The remaining 95% height should be the main canvas - I believe I have also done this.
The final part is getting a particular widget to be placed into the center of the main canvas. This is where I am stuck and would appreciate some help.
The widget I need to center is made up of two circles (one centered on top of the other, with one being slightly smaller than the other). Then, on top of the top-most circle should be a TextInput.
After taking the advice from this community, I stripped all of the logic back until I was left with just the basics (class definitions and layout), and was still having issues.
However, I have now learnt that what I assumed was a layout issue was likely not. A Kivy bug report last commented on in Nov 2018 (TextInput focus problem #3863), suggests there are unexplained instances where textinputs can lose focus. Below is the suggested workaround which also worked for me. Thanks for those that tried to help me.
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.popup import Popup
from kivy.uix.textinput import TextInput
from kivy.lang import Builder
Builder.load_string('''
<Base>:
Button:
text: 'Press for Popup'
on_press: root.edit_text()
<TextInputX>
id: texter
keyboard_mode: 'managed'
is_focusable: True
focus: True
keyboard_on_key_down:
texter.show_keyboard()
''')
class Base(BoxLayout):
def __init__(self, **kwargs):
super(Base, self).__init__(**kwargs)
def edit_text(self, *args):
self.clear_widgets()
content = TextInputX()
# content.focus = True
popup = Popup(content=content)
popup.open()
class TextInputX(TextInput):
def __init__(self, **kwargs):
super(TextInputX, self).__init__(**kwargs)
# self.focus = True
class SampleApp(App):
def build(self):
return Base()
if __name__ == '__main__':
SampleApp().run()

Combining image and text within a button in kivy

What's the preferred way to combine an image/icon and text within a button? For example, how would you create a button with text = 'my button', and a graphical icon to the left of that text?
Regarding to question #2.
The way Kivy works is embedding Widget instances. Since Image and Button are subclasses of Widget, then all you have to do is embed an Image inside a the Button. Notice that the positioning inside a widget is fixed. You have to give explicit coordinates.
That said, you can always embed a Layout to organize the stuff you are putting inside the Button.
Here is the simple ex
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.lang import Builder
Builder.load_string("""
<ButtonsApp>:
orientation: "vertical"
Button:
text: "B1"
Image:
source: 'kivy.png'
y: self.parent.y + self.parent.height - 200
x: self.parent.x
Label:
text: "A label"
""")
class ButtonsApp(App, BoxLayout):
def build(self):
return self
if __name__ == "__main__":
ButtonsApp().run()
EDIT: An example of how a relative layout can be embedded inside a button
In this case I am using a StackLayout to organize an Image and a Label inside. As I said, Button is a Widget and Kivy works embedding widgets inside widgets. It doesn't matter if they are labels, buttons or layouts.
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.lang import Builder
Builder.load_string("""
<ButtonsApp>:
orientation: "vertical"
Button:
StackLayout:
pos: self.parent.pos
size: self.parent.size
orientation: 'lr-tb'
Image:
source: 'kivy.png'
size_hint_x: None
width: 74
Label:
size_hint_x: None
width: 100
text: "The text"
Label:
text: "A label"
""")
class ButtonsApp(App, BoxLayout):
def build(self):
return self
if __name__ == "__main__":
ButtonsApp().run()
Meanwhile, there is another way.
You can use icon fonts such as Font Awesome and combine them with text.
Either import the font directly and wrap the text in their font tag, or simply use some of the libraries that take care of that.
#: import icon ...
Button:
markup: True
text: "%s Comment" % icon('comment', 32)
size_hint_x: None
width: 100
Kivy-iconfonts converts css/tff combinations that are distributed for web sites into a json format that it loads during runtime using the import statement as shown in the example above.
I extended this in my fork to fetch Font Awesome icons during runtime and put them into the working directory of your application. That gives you the advantage of not having to distribute the fonts with the application.
Define a new Button class or modify the one in Kivy.uix.button
class MyButton(Button):
#add these three properties in the class
icon = ObjectProperty(None)
icon_size = (0,0)
icon_padding = NumericProperty(0) #Enter any default value like 50 if you will
#always use an icon, or specify this field
#while creating the button
def __init__(self, **kwargs):
#no changes here, just for reference
return super(MyButton, self).__init__(**kwargs)
KV file:
<MyButton>:
state_image: self.background_normal if self.state == 'normal' else self.background_down
disabled_image: self.background_disabled_normal if self.state == 'normal' else self.background_disabled_down
canvas:
Color:
rgba: self.background_color
BorderImage:
border: self.border
pos: self.pos
size: self.size
source: self.disabled_image if self.disabled else self.state_image
Color:
rgba: (1, 1, 1, 1) if root.icon != None else (1,1,1,0)
Rectangle:
source: root.icon
size: (self.texture_size[1],self.texture_size[1]) if self.icon_size == (0,0) else self.icon_size
pos: int(self.center_x - self.texture_size[0] / 2.)-dp(root.icon_padding), int(self.center_y - self.texture_size[1] / 2.)
Color:
rgba: 1, 1, 1, 1
Rectangle:
texture: self.texture
size: self.texture_size
pos: int(self.center_x - self.texture_size[0] / 2.)+dp(root.icon_padding), int(self.center_y - self.texture_size[1] / 2.)
Now just create a button widget
MyButton(text = 'Hello',icon = 'icon.png', icon_padding = 50)

Resources