Reputation: 295
I'm working myself into kivy and want to embed a picture into a displayed text. The text is loaded a simple string, which is then displayed which is easy to do, but I somehow can not find a clue how to display an image in said string.
An answer to this question was to build a new layout for these which in this case is the TextWrapper.
Example code:
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Label, Button
from kivy.lang import Builder
from kivy.properties import BooleanProperty, StringProperty
from kivy.uix.behaviors import FocusBehavior
from kivy.uix.image import Image
from kivy.uix.recycleboxlayout import RecycleBoxLayout
from kivy.uix.recycleview import RecycleView
from kivy.uix.recycleview.layout import LayoutSelectionBehavior
from kivy.uix.recycleview.views import RecycleDataViewBehavior
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.uix.scrollview import ScrollView
Builder.load_string("""
<ScreenSpecies>:
BoxLayout:
orientation: 'vertical'
Label:
pos_hint: {"x": .45, "top": 1}
size_hint: .1, .1
text: "The Species"
GridLayout:
id: species_layout
rows: 1
cols: 2
padding: dp(10)
spacing: dp(10)
orientation: 'horizontal'
SpeciesView:
id: species_list_view
SpeciesLabel:
id: species_text
text_selected: "Text" if not species_list_view.text_selected else species_list_view.text_selected
name_selected: "" if not species_list_view.name_selected else species_list_view.name_selected
<SpeciesView>:
viewclass: 'SelectableLabel'
text_selected: ""
name_selected: ""
SelectableRecycleBoxLayout:
orientation: 'vertical'
default_size: None, dp(32)
default_size_hint: .6, None
size_hint: 1, .9
multiselect: False
touch_multiselect: False
<SpeciesLabel>:
size_hint_y: .85
Label:
halign: 'left'
valign: 'middle'
size_hint_y: None
height: self.texture_size[1]
text_size: self.width, None
text: root.text_selected
<SelectableLabel>:
canvas.before:
Color:
rgba: (.05, 0.5, .9, .8) if self.selected else (.5, .5, .5, 1)
Rectangle:
pos: self.pos
size: self.size
""")
class TextWrapper(BoxLayout):
def __init__(self, text="", **kwargs):
super(TextWrapper, self).__init__(**kwargs)
self.content = self.wrap_text(text)
def wrap_text(self, source):
text = source.split("|")
for i in range(0, len(text)):
if "img=" in text[i]:
self.add_widget(Image(source=text[i][4:]))
else:
self.add_widget(Label(text=text[i]))
return text
class SelectableRecycleBoxLayout(FocusBehavior, LayoutSelectionBehavior,
RecycleBoxLayout):
pass
class SelectableLabel(RecycleDataViewBehavior, Label):
''' Add selection support to the Label '''
index = None
selected = BooleanProperty(False)
selectable = BooleanProperty(True)
def refresh_view_attrs(self, rv, index, data):
''' Catch and handle the view changes '''
self.index = index
return super(SelectableLabel, self).refresh_view_attrs(
rv, index, data)
def on_touch_down(self, touch):
''' Add selection on touch down '''
if super(SelectableLabel, self).on_touch_down(touch):
return True
if self.collide_point(*touch.pos) and self.selectable:
return self.parent.select_with_touch(self.index, touch)
def apply_selection(self, rv, index, is_selected):
''' Respond to the selection of items in the view. '''
self.selected = is_selected
if is_selected:
print("selection changed to {0}".format(rv.data[index]))
rv.name_selected = rv.data[index]['text']
rv.text_selected = rv.data[index]['description']
else:
print("selection removed for {0}".format(rv.data[index]))
class ScreenSpecies(Screen):
pass
class SpeciesView(RecycleView):
def __init__(self, **kwargs):
super(SpeciesView, self).__init__(**kwargs)
self.species_data = [
{"text": "Test1", "description": "Test1.textbf |img=serveimage.png| Test1.textaf"},
{"text": "Test2", "description": "Test2.text"},
{"text": "Test3", "description": "Test3.text"}
]
for species in self.species_data:
species["description"] = TextWrapper(species["description"])
# clean keywords out
self.data = self.species_data
class SpeciesLabel(ScrollView):
text_selected = StringProperty("")
name_selected = StringProperty("")
screen_manager = ScreenManager()
screen_manager.add_widget(ScreenSpecies(name="screen_species"))
class TestApp(App):
def build(self):
return screen_manager
test_app = TestApp()
test_app.run()
This code does not work! As you can not click on a label without breaking the program, which is the problem. The text_selected only accepts str
ValueError: SpeciesView.text_selected accept only str
I do not know how to display the TextWrapper that is in the description of each element of the view.
The purpose of it all is, that I can change the text depending on e.g. a selectable list of options and still have Images in each text.
The result should look similar to this:
Again, the question is how to dynamically change the text on the right with changing images in them depending on the button that was pressed last.
Thanks for your help!
Upvotes: 5
Views: 2763
Reputation: 1347
I haven't found any specific wrapping tool in kivy to solve your problem, so I've written a temporary function for that. Ideally I believe there should be a TextMarkup option to allow embedding images within the text, or even a separate widget, but for now this will be better than nothing.
def wrap_text(target, source):
text = source.split("|")
for i in range(0, len(text)):
if "img=" in text[i]:
target.add_widget(Image(source=text[i][4:]))
else:
target.add_widget(Label(text=text[i]))
Provided code samples works under the assumption that you put your image references in your string in following format - |img=path|
. Example:
class Main(App):
def build(self):
base = Builder.load_file("Main.kv")
text_field = "This is text, below it should be the first image|img=example_1.png|" \
"Whereas this text should appear in between the images|img=example_2.png|" \
"And this text under the second image"
wrap_text(base, text_field)
return base
Main.kv contents
BoxLayout:
orientation: 'vertical'
Naturally, you can introduce number of improvements if you'd like, such as instead of adding general Label
, you can make your own custom one. Or you could make the images to be always added horizontal, unless the \n
character was in the string right before the image reference. Or you could try expanding existing kivy tools to include your desired functionality (as people do in kivy's Garden).
rv.text_selected = rv.data[index]['description']
does not work because rv.data[index]['description']
is a TextWrapper
widget, not a string. Note that you specify it to be the wrapper in the following line: species["description"] = TextWrapper(species["description"])
If you want to retrieve text fields from within your widget (which in fact is text within labels within the widget), use following code:
# Initialise a new, empty string, to add to it later
text = ""
# Iterate over widget's children (might as well dynamically assign them an id to find them faster)
for widget in rv.data[index]['description'].walk():
# Add text if a label is found
if isinstance(widget, Label):
text += widget.text
# Assign the text to the field
rv.text_selected = text
Output:
Similarly, you can retrieve the image in the same way. However, note that if you want to display both the text and the image, you don't have to retrieve the text or the images manually, just display the TextWrapper
instead. You can add it by calling add_widget()
for example to a BoxLayout
.
Upvotes: 1
Reputation: 651
To archieve this you will have to create a custom widget, First create a Button
and then add a FloatLayout
to the Button
.In the FloatLayout
add an Image
and a Label
here is an example
.py
from kivy.app import App
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.button import Button
from kivy.properties import ObjectProperty
from kivy.uix.image import Image
#the TextImage control
class TextImage(Button):
lbl = ObjectProperty()
img = ObjectProperty()
box = ObjectProperty()
def __init__(self, **kwargs):
super(TextImage, self).__init__(**kwargs)
#function to change specific widget position
def orient1(self):
children = self.ids.box.children[:]
self.ids.box.clear_widgets()
for child in sorted(children):
wid.add_widget(child)
.... # you can create your own funtions for arrangement
class Form(FloatLayout):
def __init__(self, **kwargs):
super(Form, self).__init__(**kwargs)
Textimg = TextImage()
Textimg.orient1() #for index arrangement
#an image
self.Img = Image(source='pic.png', size_hint=(None,None),size=(100,100))
# change the widget organization
Textimg.ids.box.orientation='vertical'
Textimg.ids.box.add_widget(self.Img)
#change your picture
Textimg.ids.img.source='myexample.png'
#Change the text
Textimg.ids.lbl.text='Picture1'
self.add_widget(Textimg)
class MainApp(App):
def build(self):
return Form()
if __name__=='__main__':
MainApp().run()
.kv file
<TextImage>:
lbl: lbl
img: img
box: box
FloatLayout:
BoxLayout: # add more Image widget and change there orientation
id: box
Image:
id: img
source: 'myexample.png'
pos_hint: {'center_x':.5,'center_y':.5}
size_hint: 1,.9
Label:
id: lbl
size_hint: 1,.1
pos_hint: {'center_x':.5,'center_y':.5}
halign:'center'
Upvotes: 1