test-kivy-app/kivy_venv/share/kivy-examples/widgets/recycleview/messenger.py
2024-09-15 15:12:16 +03:00

197 lines
5.4 KiB
Python

from kivy.app import App
from kivy.lang import Builder
from kivy.clock import Clock
from kivy.properties import ListProperty
from kivy.animation import Animation
from kivy.metrics import dp
KV = '''
#:import RGBA kivy.utils.rgba
<ImageButton@ButtonBehavior+Image>:
size_hint: None, None
size: self.texture_size
canvas.before:
PushMatrix
Scale:
origin: self.center
x: .75 if self.state == 'down' else 1
y: .75 if self.state == 'down' else 1
canvas.after:
PopMatrix
BoxLayout:
orientation: 'vertical'
padding: dp(5), dp(5)
RecycleView:
id: rv
data: app.messages
viewclass: 'Message'
do_scroll_x: False
RecycleBoxLayout:
id: box
orientation: 'vertical'
size_hint_y: None
size: self.minimum_size
default_size_hint: 1, None
# magic value for the default height of the message
default_size: 0, 38
key_size: '_size'
FloatLayout:
size_hint_y: None
height: 0
Button:
size_hint_y: None
height: self.texture_size[1]
opacity: 0 if not self.height else 1
text:
(
'go to last message'
if rv.height < box.height and rv.scroll_y > 0 else
''
)
pos_hint: {'pos': (0, 0)}
on_release: app.scroll_bottom()
BoxLayout:
size_hint: 1, None
size: self.minimum_size
TextInput:
id: ti
size_hint: 1, None
height: min(max(self.line_height, self.minimum_height), 150)
multiline: False
on_text_validate:
app.send_message(self)
ImageButton:
source: 'data/logo/kivy-icon-48.png'
on_release:
app.send_message(ti)
<Message@FloatLayout>:
message_id: -1
bg_color: '#223344'
side: 'left'
text: ''
size_hint_y: None
_size: 0, 0
size: self._size
text_size: None, None
opacity: min(1, self._size[0])
Label:
text: root.text
padding: 10, 10
size_hint: None, 1
size: self.texture_size
text_size: root.text_size
on_texture_size:
app.update_message_size(
root.message_id,
self.texture_size,
root.width,
)
pos_hint:
(
{'x': 0, 'center_y': .5}
if root.side == 'left' else
{'right': 1, 'center_y': .5}
)
canvas.before:
Color:
rgba: RGBA(root.bg_color)
RoundedRectangle:
size: self.texture_size
radius: dp(5), dp(5), dp(5), dp(5)
pos: self.pos
canvas.after:
Color:
Line:
rounded_rectangle: self.pos + self.texture_size + [dp(5)]
width: 1.01
'''
class MessengerApp(App):
messages = ListProperty()
def build(self):
return Builder.load_string(KV)
def add_message(self, text, side, color):
# create a message for the recycleview
self.messages.append({
'message_id': len(self.messages),
'text': text,
'side': side,
'bg_color': color,
'text_size': [None, None],
})
def update_message_size(self, message_id, texture_size, max_width):
# when the label is updated, we want to make sure the displayed size is
# proper
if max_width == 0:
return
one_line = dp(50) # a bit of hack, YMMV
# if the texture is too big, limit its size
if texture_size[0] >= max_width * 2 / 3:
self.messages[message_id] = {
**self.messages[message_id],
'text_size': (max_width * 2 / 3, None),
}
# if it was limited, but is now too small to be limited, raise the limit
elif texture_size[0] < max_width * 2 / 3 and \
texture_size[1] > one_line:
self.messages[message_id] = {
**self.messages[message_id],
'text_size': (max_width * 2 / 3, None),
'_size': texture_size,
}
# just set the size
else:
self.messages[message_id] = {
**self.messages[message_id],
'_size': texture_size,
}
@staticmethod
def focus_textinput(textinput):
textinput.focus = True
def send_message(self, textinput):
text = textinput.text
textinput.text = ''
self.add_message(text, 'right', '#223344')
self.focus_textinput(textinput)
Clock.schedule_once(lambda *args: self.answer(text), 1)
self.scroll_bottom()
def answer(self, text, *args):
self.add_message('do you really think so?', 'left', '#332211')
def scroll_bottom(self):
rv = self.root.ids.rv
box = self.root.ids.box
if rv.height < box.height:
Animation.cancel_all(rv, 'scroll_y')
Animation(scroll_y=0, t='out_quad', d=.5).start(rv)
if __name__ == '__main__':
MessengerApp().run()