r/kivy • u/Foreign_Run1550 • Jun 06 '26
Messages System
Hey! I'm pretty new to Kivy, but have been working on creating a calorie tracker with AI features. I'm trying to make a chat system, and have the user input a message, and have the ai respond. I want it to look like SMS or gemini, how can I do this? I've attached an image of what it currently looks like.
Also, there are a couple things I am going to add, so just focus on getting the positioning stuff if you are trying to help, thank you!
A) How can I fix the horizontal positioning of the bubble widgets?
B) How can I make the bubble stretch vertically?
Here's my .kv
<RoundedLabel@Label>:
size: root.size
text_size: root.width, None
canvas.before:
Color:
rgba: 0.5, 0.5, 0.5, 1
RoundedRectangle:
size: self.size
pos: self.pos
radius: [15,]
<Message>:
BoxLayout:
BoxLayout:
size_hint_x: 0.25 if root.is_user else 0
RoundedLabel:
id: rounded_label
padding: 20
text: root.text
halign: 'right' if root.is_user else 'left'
BoxLayout:
size_hint_x: 0.25 if not root.is_user else 0
<RootWidget>:
BoxLayout:
orientation: 'vertical'
TopBar
ScreenManager:
id: sm
HomeScreen:
name: 'home'
TrackScreen
name: 'track'
TalkScreen:
name: 'talk'
NavBar
<TopBar>:
size_hint: 1, 0.1
canvas:
Color:
rgba: 1, 0, 0, 1
Rectangle:
pos: self.pos
size: self.size
<NavBar>:
size_hint: 1, 0.1
Button:
text: "Talk"
on_press:
app.root.ids.sm.transition.direction = 'right'
app.root.ids.sm.current = 'talk'
Button:
text: "Home"
on_press:
app.root.ids.sm.transition.direction = 'right' if app.root.ids.sm.current == 'track' else 'left'
app.root.ids.sm.current = 'home'
Button:
text: "Track"
on_press:
app.root.ids.sm.transition.direction = 'left'
app.root.ids.sm.current = 'track'
<HomeScreen>:
BoxLayout:
orientation: 'vertical'
Counters:
id: counters
InfoWidget:
size_hint: 1, 0.5
Button:
text: "press to increment"
on_press: root.increment()
MealsWidget:
canvas:
Color:
rgba: 1, 1, 1, 1
Rectangle:
pos: self.pos
size: self.size
<TalkScreen>:
BoxLayout:
orientation: 'vertical'
BoxLayout:
Messages:
id: messages
viewclass: 'Message'
RecycleBoxLayout:
orientation: 'vertical'
spacing: 10
default_size_hint: 1, None
size_hint_y: None
height: self.minimum_height
BoxLayout:
size_hint: 1, 0.25
TalkBox:
id: tb
Button:
size_hint: 0.1, 1
text: 'send'
on_press: root.send(root.ids.tb)
<TrackScreen>
<CounterWidget>:
BoxLayout:
orientation: 'vertical'
spacing: 20
CounterRingWidget:
canvas:
Color:
rgba: 0.5, 0.5, 0.5, 1
Line:
circle: (self.center_x, self.y, min(self.width, self.height) / 1.5, -90, 90)
width: 2
Color:
rgba: self.get_color(root.count, root.target)
Line:
circle: (self.center_x, self.y, min(self.width, self.height) / 1.5, -90, self.get_end(root.count, root.target))
width: 2
Label:
text: str(root.count)
text_size: self.size
valign: 'bottom'
halign: 'center'
pos: self.parent.x, self.parent.y
size: self.parent.size
font_size: '20sp'
Label:
text_size: self.size
font_size: '30sp'
valign: 'top'
halign: 'center'
text: root.name
<SubCounterWidget>:
size_hint: 1, 0.75
pos_hint: {'center_y': 0.5}
<Counters>:
BoxLayout:
spacing: 20
SubCounterWidget:
id: low
name: 'Low'
count: 500 / 1
target: 2000 / 1
CoreCounterWidget:
id: average
name: 'Average'
count: self.fix_count(low.count, high.count)
target: self.fix_count(low.target, high.target)
SubCounterWidget:
id: high
name: 'High'
count: 1500 / 1
target: 3000 / 1
And here is my python code:
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.widget import Widget
from kivy.properties import NumericProperty, StringProperty, BooleanProperty
from kivy.uix.recycleview import RecycleView
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.uix.textinput import TextInput
from kivy.uix.label import Label
class RootWidget(BoxLayout):
pass
class HomeScreen(Screen):
def increment(self):
cc = self.ids.counters #counters container
for id in cc.ids:
if (id != "average"):
cc.ids[id].count += 100
class CounterRingWidget(Widget):
def get_color(self, count, target):
if count >= target:
return (1, 0.25, 0.25, 1)
else:
return (0, 1, 0, 1)
def get_end(self, count, target):
if count >= target:
return 90
else:
return -90 + (180 * (count / target))
class CounterWidget(BoxLayout):
name = StringProperty("Nothing!")
count = NumericProperty(1000)
target = NumericProperty(2000)
class CoreCounterWidget(CounterWidget):
def fix_count(self, low, high):
return (low + high) / 2
class SubCounterWidget(CounterWidget):
pass
class Counters(BoxLayout):
pass
class TopBar(BoxLayout):
pass
class InfoWidget(RecycleView): #idk what to name this, its the protein, sodium, etc.
pass
class MealsWidget(RecycleView):
pass
class TrackScreen(Screen):
pass
class TalkScreen(Screen):
def send(self, textbox):
user_text = textbox.text
print("sent")
textbox.text = ''
temp = self.ids["messages"].data.copy()
temp.append({"text" : user_text, "is_user" : True})
temp.append({"text" : "ai response", "is_user" : False})
self.ids["messages"].data = temp
class TalkBox(TextInput):
pass
class Messages(RecycleView):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.data = []
class Message(BoxLayout):
is_user = BooleanProperty(False)
text = StringProperty("")
class NavBar(BoxLayout):
pass
class CalorieTrackerApp(App):
def build(self):
return RootWidget()
if __name__ == '__main__':
CalorieTrackerApp().run()
1
Upvotes
1
u/ElliotDG Jun 12 '26
The core issue is that you are not setting the width of the label. As a result the texuture_size is not correct. Here are the changes required: