i just discovered a bug and am trying to find a solution. This is my folder structure:
|- root
|- GUI.py
|- GUI.ini
|- layouts
|- root.kv
|- style.kv
|- lib
|- kivy_utils.py
|- static
|- icon.ico
|- Barlow-Regular.ttf
|- Barlow-Bold.ttf
The GUI.py is my starting point for launching the GUI and looks like this:
# %% standard python library imports
import os
# %% kivy imports
# import kivy
from kivy.app import App
from kivy.core.window import Window
from kivy.config import Config
from kivy.lang import Builder
from kivy.uix.boxlayout import BoxLayout
# %% lib imports
from lib.kivy_utils import TTLabel
# read & overrule config
Config.read(os.path.join(os.path.dirname(os.path.abspath(__file__)), "GUI.ini"))
Window.size = Config.getint("graphics", "width"), Config.getint("graphics", "height")
# read kv language files
for kv in ["root.kv", "style.kv"]:
Builder.load_file("layouts" + os.sep + kv)
class Root(BoxLayout):
""" Main Root class for callbacks, UI building etc."""
def __init__(self):
super().__init__()
def test_function(self):
print("K TEST")
class Launcher(App):
"""Supplementary Launcher class"""
def build(self):
return Root()
def on_pause(self):
return True
if __name__ == "__main__":
Launcher().run()
I want to use the kivy_utils.py for custom classes, such as scalable widgets, tooltips etc.:
from kivy.properties import BooleanProperty, ObjectProperty, NumericProperty
from kivy.core.window import Window
from kivy.uix.button import Button
from kivy.uix.label import Label
from kivy.uix.slider import Slider
from kivy.uix.switch import Switch
from kivy.clock import Clock
class Tooltip(Label):
pass
class HoverBehavior(object):
"""Hover behavior.
:Events:
`on_enter`
Fired when mouse enter the bbox of the widget.
`on_leave`
Fired when the mouse exit the widget
"""
hovered = BooleanProperty(False)
border_point = ObjectProperty(None)
def __init__(self, **kwargs):
self.register_event_type('on_enter')
self.register_event_type('on_leave')
Window.bind(mouse_pos=self.on_mouse_pos) # for recognizing tooltips
super(HoverBehavior, self).__init__(**kwargs)
def on_mouse_pos(self, *args):
if not self.get_root_window():
return
pos = args[1]
inside = self.collide_point(*self.to_widget(*pos)) # compensate for relative layout
if self.hovered == inside:
return
self.border_point = pos
self.hovered = inside
if inside:
self.dispatch('on_enter')
else:
self.dispatch('on_leave')
def on_enter(self):
pass
def on_leave(self):
pass
class TTLabel(HoverBehavior, Label):
"""Resizable Label with Tooltip on top of Label and HoverBehaviour class"""
def __init__(self, **kwargs):
super().__init__()
self.tooltip = None # Attribute set in kv file
self.header = None # Attribute set in kv file
self.tooltip_wdg = Tooltip()
Window.bind(on_resize=self.on_window_resize) # binds font_size rescaling function to on_resize event
Clock.schedule_once(self.on_window_resize, 1.5) # called once at init cuz widget hasnt final size yet
def on_enter(self):
"""Event fires when entering widget"""
if self.tooltip: # only binds event if tooltip variable is set
Window.bind(mouse_pos=lambda w, p: setattr(self.tooltip_wdg, 'pos', p)) # binds position to cursor
self.tooltip_wdg.text = self.tooltip # sets text to tooltip variable
Window.add_widget(self.tooltip_wdg)
def on_leave(self):
"""Event fires when leaving widget"""
if self.tooltip:
Window.remove_widget(self.tooltip_wdg)
def on_window_resize(self, *args):
"""Event fires when window is rescaled"""
fire_refresh = True
# # Fires when horizontal size is too small
if self.size[0] < self._label.size[0]:
fire_refresh = False
self.texture_size[0] = self.size[0] # reduce texture size to widget size
if self.size[1] < self._label.size[1]: # additionally, if vertical size is too small, reduce aswell
self.texture_size[1] = self.size[1]
return
# Fires when vertical size is too small
if self.size[1] < self._label.size[1]:
fire_refresh = False
self.texture_size[1] = self.size[1]
if self.size[0] < self._label.size[0]:
self.texture_size[0] = self.size[0]
return
# Fires when widget size > texture size # TODO: is there another way not to fire all the time?
if fire_refresh:
self.texture_update()
And finally, my root.kv looks like this:
<Root>:
BoxLayout:
orientation: 'horizontal'
Button:
text: "Test button"
on_release: root.test_function()
TTLabel:
text: "Test label with tooltip"
tooltip: "Also WOW!"
So now to my actual problem: The code works, however(!) the Config.read
command in my GUI.py
file is not "refreshing" the current Config - the load works, i can access the attributes via Config.get
, use Config.write
etc - the .ini file gets updated, but the changes are not realized. Now i was thinking to manually refresh the parameters from the config (e.g. Window.size = Config.getint("graphics", "width"), Config.getint("graphics", "height")
), but i want to use the Options menu (F1) and there must be another way i am sure. The problem occures as soon as i use Window.bind
from within my kivy_utils.py
. I tried loading the Config there, use self.get_root_window
instead of Window
, putting the Config.read
command in various places in the kivy_utils.py
etc. What am i missing?
I figured it out - i just had to import and read the config before the Window
import:
from kivy.config import Config
Config.read("GUI.ini")
from kivy.app import App
from kivy.core.window import Window
from kivy.lang import Builder
from kivy.uix.boxlayout import BoxLayout
Like this you can use the configurator (F1) and the affected changes are recognized properly (after a restart of the UI, however). Also i figured out a weird behaviour of environmental variables: the kivy documentation states:
If you don't want to map any environment variables, you can disable the behavior::
os.environ["KIVY_NO_ENV_CONFIG"] = "1"
They probably flipped a bool there, because 0 actually disables environmental variables while 1 enables them. Will report to kivy repository.