How to initialize attributes in widget class callable? - python

I have a custom toggle button (colored rectangle), when i press the button it show a border, if press again hide it.
How i can pass argument to this widget?
class ColorSwatch(ToggleButtonBehavior, Widget):
# Color Button
def __init__(self, **kwargs):
super(ColorSwatch, self).__init__(**kwargs)
#Defaults
self.up_border_color = self.border_color
self.down_border_color = self.background_color
self.border_color = self.down_border_color
def on_state(self, widget, value):
if value == 'down':
# On checked show border
self.border_color = self.up_border_color
else:
# On checked hide border
self.border_color = self.down_border_color
If i run this
ColorSwatch(
border_color=(1,1,1,1),
border_size=10,
background_color=(.32, 0.22, 0.55, 1),
)
i get the following error
TypeError: object.__init__() takes exactly one argument (the instance to initialize)

You need to add properties of border_color, border_size, and background_color to your ColorSwatch:
class ColorSwatch(ToggleButtonBehavior, Widget):
border_size = NumericProperty(0)
border_color = ListProperty([])
background_color = ListProperty([])

Related

How to resize Kivy TextInput form box while keeping the Text in the middle of the box?

I have the following Kivy TextInput:
but when I type into it via PhoneInput().text += {TEXT_ENTERED}, the text shows up in the bottom right corner of the screen, way away from the TextInput Box that I centered on screen with the Property pos=ListProperty([200,265]).
How do I keep that TextInput box in place and also get the Text to show inside that box? Below is my widget:
class PhoneInput(TextInput):
max_characters = NumericProperty(12)
pos = ListProperty([200,265])
font_size = NumericProperty(30)
size_hint=ListProperty([.21,.1])
color = ListProperty([0,0,0,1])
cursor_blink = BooleanProperty(True)
def __init__(self, **kwargs):
super().__init__(**kwargs)
# self._keyboard = Window.request_keyboard(self._keyboard_close(), self)
def on_parent(self, widget, parent):
self.unfocus_on_touch = False
phoneInput = PhoneInput()

Kivy: AttributeError: 'CreateButton' object has no attribute '_disabled_count'

I am trying to create a dashboard of buttons on the Raspberry Pi touchscreen as a home hobby and I want to create some buttons that you press and release, some that latch on and off and some that have multiple states.
I want to change the state of a button when it is pressed. So I can detect that the button is pressed via the CreateButton class, but I want to pass the button state information in at init. When I do so, I get
AttributeError: 'CreateButton' object has no attribute '_disabled_count'
If I remove the init in CreateButton the code runs, which I don't understand. Can you suggest where I'm going wrong?
Many thanks
Main.py
import pickle
from kivy.app import App
from kivy.uix.image import AsyncImage
from kivy.uix.gridlayout import GridLayout
from kivy.uix.button import Button
from typing import List
# Define the data structure of a button state
class StateDefinition(object):
def __init__(self, Label, Image, Colour, Action):
self.Label = Label
self.Image = Image
self.Colour = Colour
self.Action = Action
# Define the data structure of a button
class ButtonDefinition(object):
def __init__(self, buttonname: str, currentstate: int, buttonstates: List[StateDefinition]):
self.currentstate = currentstate
self.buttonname = buttonname
self.buttonstates = buttonstates
# Define the data structure of a dashboard - a collection of related buttons
class DashboardDefinition(object):
def __init__(self, dashboardname: str, dashboardbuttons: List[ButtonDefinition]):
self.dashboardname = dashboardname
self.dashboardbuttons = dashboardbuttons
# This class creates the kivy button and binds it to the action
class CreateButton(Button):
def __init__(self, **kwargs):
print("Got Here")
def on_touch_down(self, touch):
if self.collide_point(*touch.pos):
if touch.button == "right":
print(self.id, "right mouse clicked")
elif touch.button == "left":
print(self.id, "left mouse clicked")
else:
print(self.id)
return True
return super(CreateButton, self).on_touch_down(touch)
## This is the class that builds the Carousel of buttons
class BuildLayout(GridLayout):
def __init__(self, **kwargs):
super(BuildLayout, self).__init__(**kwargs)
self.build_dashboard()
def build_dashboard(self):
# Define all the test data
state1 = StateDefinition(Label = 'State 1', Image = 'Image1.png', Colour = '#FF0000', Action = 'A')
state2 = StateDefinition(Label = 'State 2', Image = 'Image2.png', Colour = '#00FF00', Action = 'B')
state3 = StateDefinition(Label = 'State 3', Image = 'Image3.png', Colour = '#0000FF', Action = 'C')
button1 = ButtonDefinition(buttonname = "Button 1", currentstate = 0, buttonstates = [state1])
button2 = ButtonDefinition(buttonname = 'Button 2', currentstate = 0, buttonstates = [state2, state1])
button3 = ButtonDefinition(buttonname = 'Button 3', currentstate = 0, buttonstates = [state3, state2, state1])
dashboard1 = DashboardDefinition(dashboardname = "Test Dashboard", dashboardbuttons = [button1, button2, button3])
for buttonID in range(0, len(dashboard1.dashboardbuttons)):
buttonwidget = CreateButton(id = str(buttonID))
buttonwidget.text = dashboard1.dashboardbuttons[buttonID].buttonname + "\nButton State: " + \
str(dashboard1.dashboardbuttons[buttonID].currentstate)
self.add_widget(buttonwidget)
# This is tha main class that sets up the app
class LayoutApp(App):
def build(self):
return BuildLayout()
if __name__ == '__main__':
LayoutApp().run()
Layout.kv
#:kivy 1.11.0
<CreateButton>:
font_size: 18
on_touch_down: self.on_touch_down
<BuildLayout>:
rows: 3
cols: 3
row_force_default: True
row_default_height: 150
col_force_default: True
col_default_width: 150
padding: [10]
spacing: [10]
When you override the __init__ method for Kivy elements, it won't do the inherited __init__ method unless you manually tell it to. If you don't do that, it won't get set up properly with all the properties Kivy expects (like _disabled_count).
I expect this will work if you just add super().__init__(**kwargs) (or super(<classname>, self).__init__(**kwargs) for Python 2 compatibility) as the first line of your new __init__ methods, like you did in BuildLayout. It's easy to forget!

Why is 'root' keyword treated differently when it is called in Kivy?

How to make circular progress bar in kivy?
I have found a source above for circular progress bar and noted something weird.
from kivy.app import App
from kivy.uix.progressbar import ProgressBar
from kivy.core.text import Label as CoreLabel
from kivy.lang.builder import Builder
from kivy.graphics import Color, Ellipse, Rectangle
from kivy.clock import Clock
class CircularProgressBar(ProgressBar):
def __init__(self, **kwargs):
super(CircularProgressBar, self).__init__(**kwargs)
# Set constant for the bar thickness
self.thickness = 40
# Create a direct text representation
self.label = CoreLabel(text="0%", font_size=self.thickness)
# Initialise the texture_size variable
self.texture_size = None
# Refresh the text
self.refresh_text()
# Redraw on innit
self.draw()
def draw(self):
with self.canvas:
# Empty canvas instructions
self.canvas.clear()
# Draw no-progress circle
Color(0.26, 0.26, 0.26)
Ellipse(pos=self.pos, size=self.size)
# Draw progress circle, small hack if there is no progress (angle_end = 0 results in full progress)
Color(1, 0, 0)
Ellipse(pos=self.pos, size=self.size,
angle_end=(0.001 if self.value_normalized == 0 else self.value_normalized*360))
# Draw the inner circle (colour should be equal to the background)
Color(0, 0, 0)
Ellipse(pos=(self.pos[0] + self.thickness / 2, self.pos[1] + self.thickness / 2),
size=(self.size[0] - self.thickness, self.size[1] - self.thickness))
# Center and draw the progress text
Color(1, 1, 1, 1)
Rectangle(texture=self.label.texture, size=self.texture_size,
pos=(self.size[0]/2 - self.texture_size[0]/2, self.size[1]/2 - self.texture_size[1]/2))
def refresh_text(self):
# Render the label
self.label.refresh()
# Set the texture size each refresh
self.texture_size = list(self.label.texture.size)
def set_value(self, value):
# Update the progress bar value
self.value = value
# Update textual value and refresh the texture
self.label.text = str(int(self.value_normalized*100)) + "%"
self.refresh_text()
# Draw all the elements
self.draw()
class Main(App):
def just_function(self):
print(self.root) # <----- this will print None
# Simple animation to show the circular progress bar in action
def animate(self, dt):
print(self.root) # <---- this prints CircularProgressBar object
if self.root.value < 80:
self.root.set_value(self.root.value + 1)
else:
self.root.set_value(0)
# Simple layout for easy example
def build(self):
container = Builder.load_string(
'''CircularProgressBar:
size_hint: (None, None)
height: 200
width: 200
max: 80''')
# Animate the progress bar
Clock.schedule_interval(self.animate, 0.1)
print(self.root) # <---- this prints None
self.just_function() # <---- this prints None
return container
if __name__ == '__main__':
Main().run()
When you take a look at Main(App)
In this source, self.root is considered as CircularProgressBar in here.
But, when I do print(self.root) it prints None.
It only recognize CircularProgressBar when self.root is in a function that is called by Clock.scheduled_interval(func, rate).
Does anyone know what is happening in here?
The explanation is very simple and is clearly explained in the docs:
root = None
The root widget returned by the build() method or by the load_kv()
method if the kv file contains a root widget.
From the above it is understood that the root is the element that is returned in the build() method, so before something returns that function the root will be None, so when you print self.root within build() or call a function that prints self.root before returning that function you will always get None. After returning the root it will be what you returned, that is, the container an object of the class CircularProgressBar.

How to make multiple fbo work in kivy?

I'm working on 3d with kivy.I wanted to make bloom effect by postprocessing shader.I dont know how to pass multiple fbo in kivy..i can make one pass but cant make multiple pass..so,can any one help me?
Here is my code:
class SergioGameEngine(Widget):
def __init__(self, **kwargs):
self.canvas = RenderContext(size = self.size,clear_color=(0.5,0.5,0.5,1.0))
with self.canvas:
self.viewport = Rectangle(size=(self.size), pos=self.pos)
self.fbo = Fbo(size=self.size,
with_depthbuffer=True,
compute_normal_mat=True,
clear_color=(0.5, 0.5, 0.5, 1.))
self.fbo.shader.source = resource_find('simple.glsl')
Loading meshes.....
with self.fbo:
self.cb = Callback(self.setup_gl_context)
PushMatrix()
self.setup_scene()
PopMatrix()
self.cb = Callback(self.reset_gl_context)
self.canvas.shader.fs = fsi #this is postprocessing shader
self.pm = Window.render_context['projection_mat']
self.mm = Window.render_context['modelview_mat']
self.canvas['projection_mat'] = self.pm
self.canvas['modelview_mat'] = self.mm
def on_pos(self, instance, value):
self.viewport.pos = (0.0,0.0)
print(value)
def on_texture(self, instance, value):
self.viewport.texture = self.fbo.texture
fsi = """postprocessing shader"""
Now,canvas viewport is updated with fbo texture...
and the viewport texture is used as sampler on canvas fragment shader and i can edit that screen texture ...but the thing is how can i take canvas outputed frametexture and pass again as sampler for reusing as screen shader?
:)

Anchoring widgets in kivy 1.9.0 RelativeLayout

I have created a RelativeLayout subclass that positions its children in a grid by supplying them positions in the code (Python, not kv file). It works, but items are placed some 25 pixels to the upper-right from the position of layout itself, as shown by the canvas block. Python code for Layout subclass:
class RLMapWidget(RelativeLayout):
def __init__(self, map=None, **kwargs):
super(FloatLayout, self).__init__(**kwargs)
# Connecting to map, factories and other objects this class should know about
self.tile_factory = TileWidgetFactory()
self.map = map
# Initializing tile widgets for BG layer and adding them as children
for x in range(self.map.size[0]):
for y in range(self.map.size[1]):
tile_widget = self.tile_factory.create_tile_widget(self.map.get_item(layer='bg',
location=(x, y)))
# tile_widget.pos = (50*x, 50*y)
tile_widget.pos = self._get_screen_pos((x, y))
self.add_widget(tile_widget)
# Initializing widgets for actor layers
for x in range(self.map.size[0]):
for y in range(self.map.size[1]):
if self.map.has_item(layer='actors', location=(x, y)):
actor_widget = self.tile_factory.create_actor_widget(self.map.get_item(layer='actors',
displayed location=(x, y)))
actor_widget.pos=(50*x, 50*y)
self.add_widget(actor_widget)
# Map background canvas. Used solely to test positioning
with self.canvas.before:
Color(0, 0, 1, 1)
self.rect = Rectangle(size = self.size, pos=self.pos)
self.bind(pos=self.update_rect, size=self.update_rect)
# Initializing keyboard bindings and key lists
self._keyboard = Window.request_keyboard(self._keyboard_closed, self)
self._keyboard.bind(on_key_down=self._on_key_down)
# The list of keys that will not be ignored by on_key_down
self.used_keys=['w', 'a', 's', 'd']
def redraw_actors(self):
for actor in self.map.actors:
actor.widget.pos = self._get_screen_pos(actor.location)
def _get_screen_pos(self, location):
"""
Return screen coordinates (in pixels) for a given location
:param location: int tuple
:return: int tuple
"""
return (location[0]*50, location[1]*50)
# Keyboard-related methods
def _on_key_down(self, keyboard, keycode, text, modifiers):
"""
Process keyboard event and make a turn, if necessary
:param keyboard:
:param keycode:
:param text:
:param modifiers:
:return:
"""
if keycode[1] in self.used_keys:
self.map.process_turn(keycode)
self.redraw_actors()
def _keyboard_closed(self):
self._keyboard.unbind(on_key_down=self._on_key_down)
self._keyboard = None
def update_rect(self, pos, size):
self.rect.pos = self.pos
self.rect.size = self.size
class CampApp(App):
def build(self):
root = FloatLayout()
map_factory = MapFactory()
map = map_factory.create_test_map()
map_widget = RLMapWidget(map=map,
size=(map.size[0]*50, map.size[1]*50),
size_hint=(None, None))
root.add_widget(map_widget)
return root
if __name__ == '__main__':
CampApp().run()
Factory class that makes tiles:
class TileWidgetFactory(object):
def __init__(self):
pass
def create_tile_widget(self, tile):
tile.widget = Image(source=tile.image_source,
size_hint=(None, None))
return tile.widget
def create_actor_widget(self, actor):
actor.widget = Image(source='Tmp_frame_black.png',
size_hint=(None, None))
return actor.widget
Okay, solved it myself. It turns out that if I supply the size to child widgets in factory, they are positioned properly. Although it solves the problem I have, I'd still be grateful if someone can explain where this quirk does come from.

Categories