197 lines
5.4 KiB
Python
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()
|