Animation problems

223 views
Skip to first unread message

Dan Moormann

unread,
May 22, 2024, 3:51:55 PMMay 22
to Kivy users support
In the latest version of my grid game/app, I'm trying to create something that resembles a crawl like you see on tv news programs. (By the way I was able to get the app ported to my phone and tablet before I broke it)

Several problems: 
1. I tried various methods to get the crawl to repeat until stopped.  My intent was to stop and start until the user decided to quit or change the text.
a. on_complete doesn't seem to be working
b.  anim.repeat=True also not working (Docs say something about Sequential)

2, I started out with separate buttons for start and stop and it kind of worked.  I wanted to use a Switch instead as it was easier to press/click a single widget to start/stop the crawling.  The Switch is "hiding behind" the Back to Main button and I don't understand why.  I thought the BoxLayout would fall in line with the buttons, but it doesn't seem to work.  The Label for the Switch is barely visible above the Switch in the TextInput box.

A lot of things are not working right, but I would like to solve these problems first. 



Click on Flash then Crawl then click the barely visible Switch.


Square-Dot-Matrix.ttf
linenum.py
main.py
grid.kv

ElliotG

unread,
May 22, 2024, 8:18:09 PMMay 22
to Kivy users support
To fix the animation:
The way to think about Animation: Animation provides a way to change a property attributes from their current value to a new value.  I assume you want the text to move from right to left across the screen and repeat the same motion.  This requires the following steps.
Initial state: Label is on the far left of the screen
Animate the Label.x to move the label across the screen
When the animation is complete, set Label.x back to the initial state and repeat animation.

Change the code as below:
class Crawl(Screen):

    def set_animation(self):
        print('def set_animation')
        self.dd=self.width/200
        self.original_dd=self.dd
        self.labwidth = self.ids.crawl_label.width
        self.tail=self.ids.crawl_label.pos[0]+self.labwidth
        self.per=1
        self.dump_it()

        # self.anim = Animation(x=(-self.labwidth), d=self.dd) These do nothing - they are never started
        # self.anim.bind(on_complete=self.restore_label_pos)

    def crawl_animate(self):
        print()
        if self.ids.textin.text=='':
            self.pop_empty_textin()
            return

        self.per=1
        # self.tail=self.ids.crawl_label.pos[0]+self.labwidth
        self.tail = self.ids.crawl_label.right  # this is the same as above
        if self.ids.crawl_switch.active:    #start crawling
            print('start')
            self.anim = Animation(x=(-self.labwidth), d=self.dd)
            # self.anim.repeat = True
            self.anim.bind(on_complete=self.animation_completed)  # when the animation is done...
            self.anim.start(self.ids.crawl_label)
        else:   #stop crawling
            print('stop')
            # self.anim.stop(self.ids.crawl_label)
            self.anim.cancel(self.ids.crawl_label) # this will freeze the animation vs making move off screen
            self.per=self.tail/(self.labwidth+self.width)
            self.dd=self.per*self.original_dd
        self.dump_it()

    def animation_completed(self, anim, widget):
        widget.x = self.right # move the widget (crawl_label) off the right side of the screen.
        self.crawl_animate()

Also not that the attribute widget.right will give you the x pos on the right edge of the widget.
Sometimes the speed of the animation changes because you are manipulating self.dd, I assume this is desired.

Fixing the Layout:
The BoxLayout that holds the Label and Switch was not sized, but the elements in it were.  Instead, size the BoxLayout, and the let widgets fill the Layout.

                                   BoxLayout:
                orientation: 'vertical'
                size_hint: None, None  # size the Layout
                size: 75, 75
                Label:
                    text: 'Crawl'
#                    size_hint: None,None
#                    size: 75,75
                    font_size: 20

                Switch:
                    id: crawl_switch
                    text: 'No'
                    active: False
#                    size_hint: None,None
#                    size: 75,75
                    on_active:
                        root.crawl_animate()



Let me know how it goes!

ElliotG

unread,
May 22, 2024, 8:34:53 PMMay 22
to Kivy users support
Ooops small error:
Initial state: Label is on the far RIGHT (not left) of the screen

Dan Moormann

unread,
May 23, 2024, 12:33:13 AMMay 23
to Kivy users support
Thanks so much.  Making progress.  I actually caught your "small error".  

You're right.  I am manipulating self.dd in order to keep a constant speed.  After stopping, I need to decrease self.dd or the crawl will slow down (as it is trying to move less content in a fixed amount of time).  IE: its already moved x amount of the label across the screen, so I calculate the percent moved and use it to decrease self.dd.  I think I'm on the right track, but self.labwidth is not correct.

Maybe some kind of timing issue, as self.labwidth isn't being set on time (always 1 character late).  See kv file on_text.
grid.kv
main.py

ELLIOT GARBUS

unread,
May 23, 2024, 5:34:05 PMMay 23
to Kivy users support
There are a few things for you to look at:
  1. Before you use the Label to calculate sizes,  use the Label method texture_update().  see: https://kivy.org/doc/stable/api-kivy.uix.label.html#kivy.uix.label.Label.texture
  2. Initialize_for_crawl() is getting called when you enter the screen.  At this time the Label text has not been set.
  3. Initialize_for_crawl() is getting called in on_text_validate, this is only if the return is entered, this is in conflict with the switch behavior
  4. You are testing the text when the switch is thrown, I would suggest disabling the switch if the text == ''
  5. You only need to calculate the Label sizes when you are entering the animation routine.  You may want to restructure the code to so there  is a crawl_animate_start and a crawl_animate_continue.  Do the work you are doing now in initialize for crawl in in crawl_animate_start() and then start the animation.  In animation completed call amimate_continue().  Make the call to texture_update in crawl_animate_start and calculate the Label sizes, only when starting the animation. 

From: kivy-...@googlegroups.com <kivy-...@googlegroups.com> on behalf of Dan Moormann <dan...@gmail.com>
Sent: Wednesday, May 22, 2024 9:33 PM
To: Kivy users support <kivy-...@googlegroups.com>
Subject: [kivy-users] Re: Animation problems
 
--
You received this message because you are subscribed to the Google Groups "Kivy users support" group.
To unsubscribe from this group and stop receiving emails from it, send an email to kivy-users+...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/kivy-users/092da53a-f267-418d-8ee3-cb3db82aaddcn%40googlegroups.com.

Dan Moormann

unread,
May 26, 2024, 11:00:01 PMMay 26
to Kivy users support
I followed your instructions (mostly) and got it working.  I'm trying to use hints to make the app display appropriately on my tablet and phone.  The app ports to each device, but the display (especially on my phone) is awful (tablet - not so bad).  .  

The tablet is close enough for now.  Any help????
grid.kv
main.py
linenum.py
windows.jpg

ElliotG

unread,
May 27, 2024, 12:11:46 PMMay 27
to Kivy users support
Look at the modules and how to use the Screen module.  https://kivy.org/doc/stable/api-kivy.modules.html#module-kivy.modules
This will let you emulate the size of the screens and their pixel density on your computer.
Taking a quick look at your code for the crawl screen.  You have some widgets that are sized with very specific size_hints, and positioned with very specific pos_hints.  These are likely to cause trouble as the screen size changes.  Instead try to use combinations of Layouts and padding to size and position the widgets.  I find generally it is best to fix the sizes of buttons.  I would also use a fixed size for the height of the TextInput.
You are drawing the green box in python.  I would instead do it in kv and have it draw the outline of a Layout, this way it will scale appropriately with size changes.

I made a few changes in the attached kv, that should help you get there.
grid.kv

Dan Moormann

unread,
Jun 4, 2024, 12:14:55 AMJun 4
to Kivy users support
Progress and then not so much.  I've got the crawl going at a mostly consistent speed, but when I port the app to my tablet and phone, I get unusual layouts.  I tried the module/screen, you suggested, but the simulation is much different than the actual phone screen.  The tablet is not that far off for now.  I don't seem to get the crawl_label to a consistent position.  I tried the use pos_hint, but then I never see the label (see lines 291,292 kv file).  I'd like to get it just under the green line.  

My screen (laptop) is 800x600 my tablet is 120x800 and my phone is 2340x1080.

I used python grid.py -m screen:phone_oneplus_6t,landscape,scale=.4 (closest I could find)

I'm pretty sure I don't have all my bases covered with dp(10), etc. and using all hints 

Any suggestions would be appreciated.
grid.kv
laptop.jpg
linenum.py
simulation.jpg
grid.py

ELLIOT GARBUS

unread,
Jun 4, 2024, 10:17:07 PMJun 4
to Kivy users support
Just looking at your image - you need to fix the height of the widgets.  In general, when I am dealing with object that contain text, I set the heights.  The text will not scale so the button should not scale (that is how I view it). 

When you have the heights fixed,  position the scrolling text to be under the green box.

I should have some time in the next few days to take a closer look at the code.  Let me know if you get it working.  I'll give it a try when I get a chance.  (If you don't fix it first).





Sent: Monday, June 3, 2024 9:14 PM

To: Kivy users support <kivy-...@googlegroups.com>
Subject: Re: [kivy-users] Re: Animation problems
 

Dan Moormann

unread,
Jun 4, 2024, 11:53:33 PMJun 4
to Kivy users support

Making some progress - switched to a FloatLayout (allows me to place the textinpiut and label higher up the screen without sharing the space with the buttons. Can't seem to move the buttons any higher (of course it goes too high when I port to my phone).  I know the resolutions are really different, but I thought that was what hints were supposed to help with.  Is there any way to actually deal with the resolution differences?  I would have thought the percentages of hints would have been better.  But I guess my differences are just too extreme.  

Latest version:
linenum.py
grid.py
grid.kv

ElliotG

unread,
Jun 5, 2024, 1:59:21 PMJun 5
to Kivy users support
Here is how I changed to layout.  I have not looked at your latest version.
Key changes:  
  • I set the BoxLayout height to minimum height and adjusted the spacing and padding.
  • Set a fixed height for the widgets added padding, spacing 
  • Move the Label from a FloatLayout to under the Crawl Screen.  A Screen is a RelativeLayout, so will honor pos.
  • Changed initial pos of crawl_label, basd the y position on the green_box
  • Changed the debug values layout to a  GridLayout to improve readability.
Hope that helps!

<Crawl>
    on_enter:
        println('kv entered crawl screen')
        #root.draw_rectangle()
        root.dd=6  #self.width/200
        root.labwidth=0
        root.per=1
        root.tail=0
        root.crawling=False
        root.moved=0
        root.now=0
        root.set_focus()

    BoxLayout:
        #size and pos of working area
        #greeen rectangle
        id: green_box
        orientation: 'vertical'
        spacing: dp(5)
        #pos_hint: {'x':.0625,'y':.63}
        #size_hint: (.875,.35)
        padding: dp(10)
        pos_hint: {'top': 1}
        size_hint_y: None
        height: self.minimum_height
        canvas:
            Color:
                rgb: 0, 1, 0
            Line:
                rectangle: (*self.pos, *self.size)

        Label:
            id: crawl_text
            text: 'Enter Crawl Text'
            font_size: dp(20)
            size_hint_y: None
            height: dp(30)

        TextInput:
            id: textin
            text: ''
            #text: 'aaa'
            #text: 'ABCDFEFGHIJKLMNOPQRSTUVWXYZ'
            #size_hint: .850 , .35
            pos_hint: {'center_x': 0.5}
            font_size: dp(25)
            size_hint_y: None
            height: dp(50)
            padding: dp(10)

        BoxLayout:
            orientation: 'horizontal'
            size_hint_y: None
            height: dp(80)
            padding: dp(10)
            spacing: dp(2)

            Button:
                id:butstart
                text: ' Start\nCrawling'
                size_hint: None,1
                background_color: 'green'
                background_normal: ''
                color: 'black'
                on_release:
                    if root.crawling==False: root.crawlstart()

            Button:
                id:butstop
                text: ' Stop\nCrawling'
                size_hint: None,1
                background_color: 'yellow'
                background_normal: ''
                color: 'black'
                on_release: root.crawlstop()

            Button:
                id: butnew
                text: 'New\nText'
                size_hint: None,1
                background_color: 'blue'
                background_normal: ''
                color: 'black'
                on_release:
                    textin.text=''
                    crawl_label.text=''
                    #crawl_label.pos=800,680
                    #crawl_switch.active=False
                    root.set_focus()

            Button:
                id: butdone
                text: 'Back\n  to\nMain'
                size_hint: None,1
                background_color: 'red'
                background_normal: ''
                color: 'black'
                on_press:
                    root.stop_and_return()

            GridLayout:    # made a GridLayout so the labels fit more cleanly
#                orientation: 'vertical'
#                size: 100,320
                cols: 2

                Label:
                    id: tail
                Label:
                    id: labwid
                Label:
                    id: posnow
                Label:
                    id: moved
                Label:
                    id: per
                Label:
                    id: dd

#    FloatLayout:
    Label:               # The Screen is a Relative Layout
        id: crawl_label
        text: textin.text
        font_size: dp(60)
        size_hint: None,None
        size: self.texture_size
        padding: dp(10)
        # font_name: 'Square-Dot-Matrix.ttf' 
        #pos_hint: {'x': 1, 'y' : .55}
        pos: self.parent.right, green_box.y - self.height  # positioned based on the green_box


grid.kv

Dan Moormann

unread,
Jun 5, 2024, 11:40:13 PMJun 5
to Kivy users support
Everything's looking much better with your code fixes, except the version for my phone (Smasung s22) made the buttons so small, as to be nearly unusable.  I increased the width of the buttons and it's ok on all versions (laptop, simulation, tablet, cellphone).

Still need to do some fine tuning on the speed adjustments as sometimes it goes too fast or too slow.  I'm probably missing a setting somewhere, I'll keep looking. 

I have several concerns with buildozer.  Who should I direct them to?

Thanks again.

ElliotG

unread,
Jun 6, 2024, 10:11:37 AMJun 6
to Kivy users support
Review this excellent document on android and Buildozer: https://github.com/Android-for-Python/Android-for-Python-Users
I would suggest you post your message to a new thread and see who responds, or try the Kivy Discord, there is a section on Android.  

ElliotG

unread,
Jun 7, 2024, 4:14:07 PMJun 7
to Kivy users support
I put together an example that calculates the animation duration so the text moves with a constant velocity.  The distance and duration labels update when the start button is pressed.

from kivy.app import App
from kivy.lang import Builder
from kivy.animation import Animation
from kivy.properties import NumericProperty


kv = """
BoxLayout:
    orientation: 'vertical'
    RelativeLayout:
        Label:
            id: scroll_label
            pos_hint: {'center_y': 0.5}
            x: self.parent.right
            text: ti.text
            font_size: sp(40)
            size_hint: None, None
            size: self.texture_size
    BoxLayout:
        size_hint_y: None
        height: dp(30)
        Label:
            text: f'Distance: {app.distance:.2f}'
        Label:
            text: f'Duration {app.distance/app.velocity:.2f}'
    TextInput:
        id: ti
        size_hint_y: None
        height: dp(30)
        hint_text: 'Enter Scrolling Text'
        text: 'The scrolling text...'
    BoxLayout:
        size_hint_y: None
        height: dp(48)
        ToggleButton:
            id: start_button
            text: {'normal': 'Start', 'down': 'Stop'}[self.state]
            on_state:
                if self.state == 'normal': app.stop_animation()
                if self.state == 'down': app.start_animation()
        Label:
            text: 'Velocity:'
            padding: dp(5)
            size_hint_x: None
            width: self.texture_size[0]
        TextInput:
            id: velocity
            filter: 'float'
            hint_text: 'Velocity'
            text: '300'
            padding: dp(15)
            size_hint_x: None
            width: dp(100)
"""


class VelocityPlaygroundApp(App):
    velocity = NumericProperty(300)
    distance = NumericProperty(1)

    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.anim = None  # holds animation object

    def build(self):
        return Builder.load_string(kv)

    def duration(self, velocity):
        sl = self.root.ids.scroll_label
        self.velocity = velocity
        self.distance = sl.x + sl.width
        return self.distance / velocity

    def start_animation(self):
        sl = self.root.ids.scroll_label
        v = float(self.root.ids.velocity.text) if self.root.ids.velocity.text else 300
        self.anim = Animation(x=-sl.width, d=self.duration(v))
        self.anim.bind(on_complete=self.reset_sl)
        self.anim.start(sl)

    def stop_animation(self):
        sl = self.root.ids.scroll_label
        self.anim.cancel(sl)

    def reset_sl(self, *args):
        sl = self.root.ids.scroll_label
        sl.x = sl.parent.right
        self.root.ids.start_button.state = 'normal'


VelocityPlaygroundApp().run()

Dan Moormann

unread,
Jun 7, 2024, 5:51:46 PMJun 7
to Kivy users support
I read the doc you suggested.  A little bit over my head (at the moment).  I'll dig into it later.  

Your velocity/duration example is terrific.  Now to see how to integrate into my app.  -THANK YOU!

Dan Moormann

unread,
Jun 8, 2024, 12:22:51 AMJun 8
to Kivy users support
Thanks again for your example - it really helped.

Just when I thought I was making progress ...
Changed the two buttons start & stop to a single toggle with hint_text  in textin - works pretty well if I press the toggle.  
Added on_validate to the textin - but then the stop toggle doesn't work
I thought the on_validate and toggle press would be equivalent
Tried to change the background_color of the toggle to red - shows a very dark washed-out red.
Tried to halt the process after dump_It() with an input statement - then my debug stuff doesn't display
Also tried using debug (PyCharm) to stop/pause at the end of dump_it() - same result.
Is there any way to pause the program besides using debug?
linenum.py
grid.kv
grid.py

Tomek CEDRO

unread,
Jun 8, 2024, 5:40:34 AMJun 8
to kivy-...@googlegroups.com
you can debug (in terms of debug not logging) with pudb python package :-)

put this line where you want a breakpoint in your code: 

import pudb; pu.db

then launch your program with: 

pudb ./program_launcher_script

or

pudb ./main.py

when running from venv where both kivy and pudb is installed (with pip).

pudb has nice ncurses gui and is easy to use (press ? for manual).

just note that kivy blocks input (mouse) on some systems so you won't be able to control other applications during a debug break.

on macos it's not a problem but on xorg for instance that will be a problem. then you can run terminal in two windows with tmux(install system package) and switch to other window with keyboard (alt+tab) in order to debug.

pudb allows you to see all objects states and values and even alter them in runtime what is extremely useful in testing.

when in problem at first with pudb create a simple hello world application (not kivy) and debug it to get familiar with basics. 


have fun :-)

--
CeDeROM, SQ7MHZ, http://www.tomek.cedro.info

Tomek CEDRO

unread,
Jun 8, 2024, 5:44:16 AMJun 8
to kivy-...@googlegroups.com
one more thing with kivy + pudb on macos - you need to set logging to kivy or mixed mode otherwise pudb display gets messed up with debug messages: 


--
CeDeROM, SQ7MHZ, http://www.tomek.cedro.info

Dan Moormann

unread,
Jun 8, 2024, 5:03:17 PMJun 8
to Kivy users support
Cedro - Thanks for the info on pudb.  A bit over my head at the moment. I'll keep it in mind for the future.

Eliot - I got the on_text_validate to work - just needed to set ssb.state='down'
Still can't figure out how to change the color to red.

New problem - If I leave the crawl_text empty, I use a popup to say "You must enter something" but the toggle state has been changed to normal/stop and the animation hasn't started yet when crawlstop tries to cancel the animation. Can I flip the taggle state without actioning it?

Getting better at using debug.
On to applying your code to get velocity to work properly - thanks again.

Dan Moormann

unread,
Jun 8, 2024, 7:45:18 PMJun 8
to Kivy users support
Fixed the empty crawl_text problem by defining anim = Animation... as soon as the class Crawl is called.  Also added crawlstop() to the on_dismiss callback from the popup that shifts the toggle back to normal.
.
Now let's see if I can get velocity/speed working.  - Thanks again

Dan Moormann

unread,
Jun 8, 2024, 9:13:10 PMJun 8
to Kivy users support
Pretty sure I fixed the velocity.  Pretty much took your code.  Thanks again.

ElliotG

unread,
Jun 8, 2024, 9:52:30 PMJun 8
to Kivy users support
To change the button color to Red, you need to change remove the background_down image:
background_down: ''

Set the background_color based on the button state.


Dan Moormann

unread,
Jun 10, 2024, 9:25:32 PMJun 10
to Kivy users support
Once again - thanks for all your help.

I added 2 new functions to the app Velocity and Font Size.  They work pretty well.  I even added a font_too_big function to stop values of zero and values larger than the space available below the green box.  Also, a zero check for the velocity.  Also set up a PyCharm configuration for my cell/s22 (still some differences).  See screen shots.  

I also tried to add on_touch (up or down) (line 288 kv file) to the textinput for fontsize in order to cause anim stop before proceeding.  The behavior is weird.   Sometimes it responds to touches outside of the textinput.  It does stop the crawl, but it disables the stop button after starting up with the start button or on_text_validate.  Is there something like on_focus (couldn't find anything in the docs).

cells22.png
grid.py
linenum.py
grid.kv

Dan Moormann

unread,
Jun 10, 2024, 9:33:44 PMJun 10
to Kivy users support
Just remembered.  If you put a zero in the fontsize, it catches the error, but everything is except the pop-up is dim and the crawl continues.

ElliotG

unread,
Jun 11, 2024, 9:04:14 AMJun 11
to Kivy users support
I recommend you take a different approach.  You can make the input_filter of TextInput a callable.  Set it up so the user can not enter an invalid value.  I'll put together a separate standalone example. 
In general you do not want to use the on_touch_ methods from kv.  The on_touch_ methods receive all touches, you should use a collide_point() to test that the touch is in the widget.  There is an on_focus event you can use.  All kivy properties (ie focus, size, pos...) have an associated event that starts with "on_" 

ElliotG

unread,
Jun 11, 2024, 10:22:33 AMJun 11
to Kivy users support
Here is an example of using the input_filter and text_validate to create a TextInput that only accepts values in a specific range.
Also note in the kv code where you could put your call to stop the crawl when the focus is true.  You can use this class for both the Font size and velocity by setting different values in the instance.

from kivy.app import App
from kivy.lang import Builder
from kivy.uix.textinput import TextInput

from kivy.properties import NumericProperty

kv = """
BoxLayout:
    orientation: 'vertical'
    AnchorLayout:
        IntegerRangeTextInput:
            size_hint: None, None
            size: dp(100), dp(30)
            max_value: 568
            min_value: 20
            on_focus: print(f'Focus changed {self.focus=}')  # if self.focus: stop_crawl()
    Label:
        text: 'Input only accepts values from 20 to 568'
        size_hint_y: None
        height: dp(30)
"""

class IntegerRangeTextInput(TextInput):
    min_value = NumericProperty(0)
    max_value = NumericProperty(127)

    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.multiline = False
        self.write_tab = False
        self.input_filter = self.min_max_filter

    def min_max_filter(self, substring, from_undo):
        # no leading zeros and <= max
        if not substring.isdigit():  # only digits
            return
        if int(self.text + substring) <= self.max_value:
            self.text = (self.text + substring).lstrip('0')  # remove leading zeros
            return

    def on_text_validate(self):
        # must be greater than min value
        if not self.text:
            return super().on_text_validate()
        if int(self.text) < self.min_value:
            self.text = str(self.min_value)
        return super().on_text_validate()

    def on_focus(self, _, focus):
        if not focus:  # focus has been lost, validate the input
            self.on_text_validate()


class FilterTextInputApp(App):
    def build(self):
        return Builder.load_string(kv)


FilterTextInputApp().run()

ElliotG

unread,
Jun 11, 2024, 12:07:41 PMJun 11
to Kivy users support
The handling of leading zeros was not correct when the min value is zero... this fixes the issue.

    def min_max_filter(self, substring, from_undo):

        # no leading zeros and <= max
        if not substring.isdigit():  # only digits
            return
        if self.min_value == 0 and int(self.text + substring) == 0: # no repeating leading zeros
            self.text = '0'

            return
        if int(self.text + substring) <= self.max_value:
            self.text = (self.text + substring).lstrip('0')  # remove leading zeros
            return

Dan Moormann

unread,
Jun 12, 2024, 10:24:00 PMJun 12
to Kivy users support
Started working on your latest suggestions.  Thought I'd start with on_focus.  I had on_text_validate working to start crawling when it was invoked - and it worked just fine.  I added it to both Velocity and Font Size.  I then added on_focus to Font Size then the on_text_validate doesn't seem to be working.  I didn't add on_focus to Velocity and it still responds to on_text_validate.  What's wrong?

I made some changes to win32gui.MoveWindow and the size on monitor 2 is very close to my cell Samsung S22 with a few notable exceptions.  On the main screen the scrollview  buttons are significantly different in height.  How to I resolve this?

On the crawl screen - I don't understand why the label Velocity is bleeding into the Back button.  I'm guessing it has to do with -
width: self.texture_size[0] - which I can't find any understandable docs.

The S22 screenshots are from the apk installed on my cellphone
The tab4 screenshots are from the apk installed on the tab4
The laptop screenshots are from running the s22 configuration from PyCharm
The look of the screen using standard PyCharm configuration is close to tab4

linenum.py
grid.py
grid.kv

ElliotG

unread,
Jun 13, 2024, 11:35:23 AMJun 13
to Kivy users support
I do not see the screenshots.    

If you are seeing objects overlapping one another, It suggests the fixed sizes are larger that the available space.
The texture size of a Label is the size of the text.  The size of the text (texture_size) and the size of the Label (size) are two different things.

width: self.texture_size[0]
This means you are setting the width of the Label to match the width of the text.  

ElliotG

unread,
Jun 13, 2024, 11:43:32 AMJun 13
to Kivy users support
For both the velocity and font size labels, they are being sized with a hint.  If the hint is not set to None, the hint sets the size.
            Label:
                text: 'Velocity:'
                padding: dp(5)
                size_hint_x: None #.03
                font_size: dp(25)
                width: self.texture_size[0]  # must set size_hint to None for width to work.


You may want to consider a smaller font size for these labels if the buttons are getting too small, or change the layout to put the buttons on a seperate line.

ElliotG

unread,
Jun 13, 2024, 12:05:58 PMJun 13
to Kivy users support
Looking at on_focus in the Velocity and Font Size TextInputs. on_focus will be dispatched on any focus change.  It will be fired when focus changes from False to True, and True to False. Select the right behavior for each state change.  You can test self.focus to see the current state of focus.

The reason you buttons are different sizes is that you are not using dp() to set the size.
Your current code:
            xxbox = ColorButton(background_color=value,
                                background_normal='',
                                size_hint_y=(None),
                                height=75,
                                text=color,
                                color=('black'),
                                name=color)
You want to set the height as:
height=dp(75)

you can define all of the styling for ColorButton in kv, or simply add:
from kivy.metrics import dp
to be able to add dp to the python code.

Dan Moormann

unread,
Jun 18, 2024, 12:25:18 AMJun 18
to Kivy users support
Making pretty good progress (thanks again).

In trying to respond to invalid responses in textinputs, I'm working on a validation routine.  It checks all three textinputs (textin, velocity and fontsize) at one time.   

The first one (textin) does pretty much what I want - Stops crawling and pops up a message.  Except execution continues after the popup but before it's released.  Looks like it's crawling with empty crawl_label.
I guess I don't really understand what the on_complete (for anim) is doing.  Actually, when it fires.
Can execution be suspended until the popup is actually released?

I tried to use your input filter example but couldn't get it to integrate with my code.  Is there something a bit simpler like filter: 'integer'?
I found something on filtering in kivy docs but nothing on how to implement.

Why can I enter anything in textin but only numbers in velocity and fontsize.  Velocity is a numeric property but fontsize is not. 
grid.py
grid.kv
linenum.py

Dan Moormann

unread,
Jun 18, 2024, 12:59:03 PMJun 18
to Kivy users support
Now I'm thoroughly confused.  Why doesn't my popup fire immediately after popup.open()???

Delete the fontsize textinput (so it's blank) then hit return or click startcrawl button.

ElliotG

unread,
Jun 18, 2024, 3:07:23 PMJun 18
to Kivy users support
Here is an example that shows a number of different input filters.  The input filter gets passed the substring that was entered.  The substring will either be the char that was typed, or a string that was pasted.  The string the filter returns is appended to self.text.   

The  IntegerRangeTextInput class is the most complex.  The filter is used to ensure the max value is not exceeded, and to remove leading zeros.  The on_text_validate is used to ensure the value is greater than the min_value.  If it is not greater than the min_value, it is set to the min_value. The method on_text_validate is called when the user presses enter.  I have also added a method on_focus that calls on_text_validate when the focus is lost. 


from kivy.app import App
from kivy.lang import Builder
from kivy.uix.textinput import TextInput
from kivy.properties import NumericProperty

kv = """
<TextInput>:
    size_hint_y: None
    height: dp(30)
    write_tab: False  # tab between inputs

AnchorLayout:

    BoxLayout:
        orientation: 'vertical'
        size_hint: None, None
        height: self.minimum_height
        width: dp(300)
        TextInput:
            hint_text: 'Print Elements'
            input_filter: app.ti_filter_print_elements
        CharLimitTextInput:
            hint_text: 'only 5 chars'
            max_length: 5
            input_filter: self.ti_filter_length
        HexDigitsTextInput:
            hint_text: 'Enter up to 4 hex digits'
            max_length: 4
            input_filter: self.ti_filter_hex_digits
        LettersOnlyTextInput:
            hint_text: 'Only letters, up to 10'
            max_length: 10
            input_filter: self.ti_filter_letters
        IntegerRangeTextInput:
            hint_text: 'min 5, max 200'
            min_value: 5
            max_value: 200
"""


class CharLimitTextInput(TextInput):
    max_length = NumericProperty()

    def ti_filter_length(self, substring, from_undo):
        if len(substring) + len(self.text) <= self.max_length:
            return substring



class IntegerRangeTextInput(TextInput):
    min_value = NumericProperty(0)
    max_value = NumericProperty(127)

    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.multiline = False
        self.write_tab = False
        self.input_filter = self.min_max_filter

    def min_max_filter(self, substring, from_undo):
        # no leading zeros and <= max
        if not substring.isdigit():  # only digits
            return
        value = int(self.text + substring)
        if value <= self.max_value:
            self.text = '0' if value == 0 else (self.text + substring).lstrip('0')  # remove leading zeros

            return

    def on_text_validate(self):
        # must be greater than min value
        if not self.text:
            return super().on_text_validate()
        if int(self.text) < self.min_value:
            # if value greater than min, set to min value

            self.text = str(self.min_value)
        return super().on_text_validate()

    def on_focus(self, _, focus):
        if not focus:  # focus has been lost, validate the input
            self.on_text_validate()


class HexDigitsTextInput(TextInput):
    max_length = NumericProperty(2)

    def ti_filter_hex_digits(self, substring, from_undo):
        # if more that max_length... exit
        if len(substring) + len(self.text) > self.max_length:
            return
        # check that substring is hexdigits...
        valid_digits_set = {*'0123456789abcdefABCDEF'}
        if {*substring}.issubset(valid_digits_set):
            return substring


class LettersOnlyTextInput(TextInput):
    max_length = NumericProperty()

    def ti_filter_letters(self, substring, from_undo):
        # if more that max_length... exit
        if len(substring) + len(self.text) > self.max_length:
            return
        # check that substring is letters
        if substring.isalpha():
            return substring


class TiInputFiltersApp(App):
    def build(self):
        return Builder.load_string(kv)

    def ti_filter_print_elements(self, substring, from_undo):
        """
        substring is the string pasted or the key pressed
        undo is if the text came from an undo (True/False)
        """
        print(f'ti_filter_hex_digits: {substring=}, {from_undo=}')
        # in practice, I ignore from_undo, I have not had a need to handle it.
        # the substring that gets returned is appended to self.text
        return substring


TiInputFiltersApp().run()

ElliotG

unread,
Jun 18, 2024, 4:31:57 PMJun 18
to Kivy users support
I've made a few changes to your code.  The inputs do not allow invalid values.  The start button is not enabled unless there are valid values. 

In grid.kv - 
  • I've commented out the Main screen in the ScreenManager so it opens to the Crawl screen.
  • I changed the TextInputs.
    • textin is now a CharLimitTextInput, with the max_length set to 45
    • velocity and fontsize are now IntegerRangeTextInput(s) with max and min values set in kv
    • For all 3 inputs the scroll is controlled by the focus.
  • The ssb ToggleButton is disabled if the 3 text inputs do not have values.  The user can not start the scroll if all the inputs are not present. 
In grid.py
  • Added the classes CharLimitTextInput and IntegerRangeTextInput
  • commented out the body of validate_tvf
The pop_error() method is not longer required.  The approach I have used it to not allow the user to enter bad data, rather than providing a popup on error.

Hope that helps.
grid.py
grid.kv

Dan Moormann

unread,
Jun 21, 2024, 8:37:45 PM (11 days ago) Jun 21
to Kivy users support
Once again - thanks for all your help. Things work pretty well except:

I was having so many problems with the textinputs that I revamped the app so that it uses up/down buttons to change velocity and fontsize.  I'm still using your routine for validating textin and removed the routine for validating integer textinputs.  Still would like to warn user if they hit min or max values (other than a no op), but I'll tackle that later.

If the crawl is stopped and you press fontsize font_up_down() - Crawl_label disappears.  When I run debug, it runs _get_focus in __init__py.  Not sure who wants focus. If I then press the start button, the crawl emerges from the right side of the screen (as if animation_completed is called - doesn't print).  But I don't get the same behavior from velocity_up_down(), which is basically the same routine.

If the crawl is running, pressing either button changes the fontsize on the fly.  This is the behavior I want whether crawling or stopped.  

If I press either button for velocity (crawling or not), the velocity change doesn't happen until the next crawl cycle (animation_completed).   I was hoping the velocity change would on the fly.
grid.kv
linenum.py
grid.py

ElliotG

unread,
Jun 22, 2024, 7:39:56 PM (10 days ago) Jun 22
to Kivy users support
"If the crawl is stopped and you press fontsize font_up_down() - Crawl_label disappears."

In KV the pos of the label is set as:  
self.parent.right, green_box.y - self.height  # positioned based on the green_box
This creates a bind setting the position of the Label.  You need to set the x position of the label without a bind.  
Set the y position of the label in kv, and set the x position manually. 

In the attached kv code:
  • crawl_label.x is set in  on_enter; and in on_release of butnew
  • In crawl_label    y: green_box.y - self.height ; this sets the y positon of the crawl_lable with a bind
This fixes the issue.  The font size can be changed with the animation running or stopped and the size change is visible.   
I have not done anything with the x position of the Label when the window size is changed.  You could check that if the animation is not running that you position the label off screen when the screen size is changed.

To change the velocity on the fly:
See the code for some details but simply put,  call crawlstop() and crawlstart().  This uses the updated position to calculate the new duration. 
I created a new instance var self.anim_is_running to track the state of the animation. 
I moved changing the backgroud_color of the ToggleButton (ssb) to kv. 
background_color: {'normal': 'green', 'down': 'red'}[self.state]

I also commented out the on_focus behavior of the textin, it does not seem to be required, and was causing some erroneous behavior.

I also noticed this code:
    def clock_self_select_all(self):
        Clock.schedule_once(lambda dt: TextInput.select_all(self))
I'm not sure what you intended, the code is never called.  There is a problem here, the method select_all() needs to be used with a TextInput instance.  
grid.py
grid.kv

Dan Moormann

unread,
Jun 24, 2024, 12:01:08 AM (9 days ago) Jun 24
to Kivy users support
Everything's looking pretty good.  I added several new features.  Min/Max buttons for velocity and fontsize.  Also added min= and max= to the labels above velocity and fontsize.  

I had to add text: ' \n60' etc. to get velocity and fontsize labels to not crowd up against the labels.  Is there a better way to do this?

Having an issue with butnew.  If I click New Text while the crawl is active the crawl stops, and the toggle (ssb) says stop/red.  Then if I click the toggle again, it switches to Start/green.  If the crawl is stopped, crawl_label is blanked but it remains in place (as intended - because I commented out crawl_label.x=root.right)

The def clock_self_select_all(self): left over code from an attempt to get your original Integer_range_input to work (didn't work for me).

Last thing: I added a label to track the hopefully current value of ssb.state and anim_is_running.  It starts out as normal /False.  Which I think is correct even if the toggle is disabled.  When I type something into textin the toggle still says normal/False.  When I click the start button it switches to down/False (it is moving).  Click again and it changes to normal /True (it isn't moving).  Is the content of the label just not syncing?

Anyway - thanks for all your help.
linenum.py
grid.kv
grid.py

ElliotG

unread,
Jun 24, 2024, 2:55:17 PM (8 days ago) Jun 24
to Kivy users support
"I had to add text: ' \n60' etc. to get velocity and fontsize labels to not crowd up against the labels.  Is there a better way to do this?"
An alternative would be to use a separate label for each line,  and place them in a vertical BoxLayout.

"Having an issue with butnew."
You had commented out a line that set ssb.state to 'normal', restoring the line fixed the issue.

"I added a label to track the hopefully current value of ssb.state and anim_is_running."
The variable anim_is_running is an instance variable (defined in __init__()) to track this variable and have it automatically update the label, it needs to be a kivy property.  I have made the change in the attached code.
I also changed the label to use a f-string that shows the variable name... 

Label:
    text: f'{ssb.state=} / {root.anim_is_running=}'


What problem did you have with the IntegerRangeInput?   Anything I can help with - or explain?
grid.kv
grid.py

Dan Moormann

unread,
Jun 24, 2024, 10:38:35 PM (8 days ago) Jun 24
to Kivy users support
Got everything working well.  

I decided against the IntegerRange option as I wanted to notify the user for unusable input.  When I couldn't get the popup to work, I compromised and decided on the up/down min/max approach with a pre statement of min/max.  I think it works well enough for now.  I will keep IntegerRange in mind for the future.  Haven't decided what direction to go with the app (thinking of better graphics).

Thanks again for all your help.  I'm sure we'll be communicating again soon.

ELLIOT GARBUS

unread,
Jun 24, 2024, 10:48:39 PM (8 days ago) Jun 24
to Kivy users support
Glad to hear you have things working!

Sent: Monday, June 24, 2024 7:38 PM

To: Kivy users support <kivy-...@googlegroups.com>
Subject: Re: [kivy-users] Re: Animation problems
--
You received this message because you are subscribed to the Google Groups "Kivy users support" group.
To unsubscribe from this group and stop receiving emails from it, send an email to kivy-users+...@googlegroups.com.
Reply all
Reply to author
Forward
0 new messages