Graphics not escaling correctly

39 views
Skip to first unread message

Barbarur

unread,
Sep 23, 2022, 10:56:02 PM9/23/22
to Kivy users support
Hello everyone,

I'm having some troubles with scaling an ellipse. I'm trying to get something like the picture attached when the screen loads.

The screen only contains a widget which is from by 2 buttons and a ellipse. I intend the ellipse to become a circle and stay centered behind the left button.

I know there are other ways to achieve this. The reason to do it on this way is because later I will make the ellipse moving from the left button to the right button with some animation.

To adjust the size and position of the Ellipse, I create a method, which should be triggered from the screen. I'm using different screen methods for testing.

The widget triggers each time, but it seems the size is not correct at any point. Only if moving to ScreenTwo, which is exactly as ScreenOne, the Ellipse gets the right size only during on_enter(). The last method to run.

Any idea on adjust the position of the Ellipse correctly?


Test code:

from kivy.app import App
from kivy.lang import Builder
from kivy.properties import ListProperty
from kivy.uix.anchorlayout import AnchorLayout
from kivy.uix.screenmanager import Screen, ScreenManager


Builder.load_string(
"""
<ButtonBubble>:
    left_button: left_button
    right_button: right_button

    RelativeLayout:
        canvas:
            Color:
                rgba: 1, 0, 0, 1
            Ellipse:
                size: root.bubble_size
                pos: root.bubble_position

    BoxLayout:
        orientation: 'horizontal'
        spacing: self.width * .1
        padding: self.width * .1

        Button:
            id: left_button
            background_color: 0, 1, 0, .5
            on_press: root.get_pos()

        Button:
            id: right_button
            background_color: 0, 1, 0, .5
            on_press: app.root.current = 'ScreenTwo'

<ScreenOne>:
    btns: btns
    ButtonBubble:
        id: btns

<ScreenTwo>:
    btns: btns
    ButtonBubble:
        id: btns
""")


class ButtonBubble(RelativeLayout):
    bubble_size = ListProperty([50, 50])
    bubble_position = ListProperty([0, 0])

    def get_pos(self):
        side = min(self.left_button.width, self.left_button.height)
        print(f'Side: {side}')
        self.bubble_size = (side, side)
        pos_x = (self.left_button.width - side) / 2 + self.left_button.pos[0]
        pos_y = (self.left_button.height - side) / 2 + self.left_button.pos[1]
        self.bubble_position = (pos_x, pos_y)
        print(f'Pos: {self.left_button.pos}')


class ScreenOne(Screen):

    def on_size(self, *args):
        print('ScreenOne.on_size')
        self.btns.get_pos()

    def on_pre_enter(self, *args):
        print('ScreenOne.on_pre_enter')
        self.btns.get_pos()

    def on_enter(self, *args):
        print('ScreenOne.on_enter')
        self.btns.get_pos()

    def on_kv_post(self, base_widget):
        print('ScreenOne.on_kv_post')
        self.btns.get_pos()


class ScreenTwo(Screen):

    def on_size(self, *args):
        print('ScreenTwo.on_size')
        self.btns.get_pos()

    def on_pre_enter(self, *args):
        print('ScreenTwo.on_pre_enter')
        self.btns.get_pos()

    def on_enter(self, *args):
        print('ScreenTwo.on_enter')
        self.btns.get_pos()

    def on_kv_post(self, base_widget):
        print('ScreenTwo.on_kv_post')
        self.btns.get_pos()


class MyApp(App):
    sm = None

    def build(self):
        self.sm = ScreenManager()
        self.sm.add_widget(ScreenOne(name='ScreenOne'))
        self.sm.add_widget(ScreenTwo(name='ScreenTwo'))
        self.sm.current = 'ScreenOne'
        return self.sm


if __name__ == '__main__':
    MyApp().run()
EllipsRightSize.png

Elliot Garbus

unread,
Sep 24, 2022, 5:18:49 PM9/24/22
to kivy-...@googlegroups.com

Looking to minimize changes to your code, I created a dummy screen as the first screen.  This forces a transition that makes the on_enter event fire when the screen is changed to ‘ScreenOne’.  I also set the initial size of the ellipse to 0,0

 

I recommend you change the button to a BoxLayout with ButtonBehavior.  The Button uses images that are a little smaller than the enclosing layout (a size effect of the subtle 3d button size).  Use the canvas to draw the desired background color.  This will provide a full size background button.

 

 

from kivy.app import App
from kivy.lang import Builder
from kivy.properties import ListProperty
from kivy.uix.relativelayout import RelativeLayout
from kivy.uix.screenmanager import Screen, ScreenManager


Builder.load_string(
"""
<ButtonBubble>:
    left_button: left_button
    right_button: right_button

    Widget:  # a widget not a relative layout
)



class ButtonBubble(RelativeLayout):
    bubble_size = ListProperty([
0, 0])
    bubble_position = ListProperty([
0, 0])

   
def get_pos(self):
        side =
min(self.left_button.width, self.left_button.height)
       
print(f'Side: {side}')
       
self.bubble_size = (side, side)
        pos_x = (
self.left_button.width - side) / 2 + self.left_button.pos[0]
        pos_y = (
self.left_button.height - side) / 2 + self.left_button.pos[1]
       
self.bubble_position = (pos_x, pos_y)
       
print(f'Pos: {self.left_button.pos}')


class DummyScreen(Screen):
   
pass


class ScreenOne(Screen):

   
# def on_size(self, *args):
    #     print('ScreenOne.on_size')
    #     self.btns.get_pos()

    # def on_pre_enter(self, *args):
    #     print('ScreenOne.on_pre_enter')
    #     self.btns.get_pos()

   
def on_enter(self, *args):
       
print('ScreenOne.on_enter')
       
self.btns.get_pos()

   
# def on_kv_post(self, base_widget):
    #     print('ScreenOne.on_kv_post')
    #     self.btns.get_pos()


class ScreenTwo(Screen):

   
# def on_size(self, *args):
    #     print('ScreenTwo.on_size')
    #     self.btns.get_pos()

    # def on_pre_enter(self, *args):
    #     print('ScreenTwo.on_pre_enter')
    #     self.btns.get_pos()

   
def on_enter(self, *args):
       
print('ScreenTwo.on_enter')
       
self.btns.get_pos()
   
#
    # def on_kv_post(self, base_widget):
    #     print('ScreenTwo.on_kv_post')
    #     self.btns.get_pos()


class MyApp(App):
    sm =
None

    def
build(self):
       
self
.sm = ScreenManager()
       
self.sm.add_widget(DummyScreen(name='dummy'))
       
self.sm.add_widget(ScreenOne(name='ScreenOne'))
       
self.sm.add_widget(ScreenTwo(name='ScreenTwo'))
       
self.sm.current = 'ScreenOne'
       
return self.sm


--
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/9e581d8f-340e-491f-81d0-5a9cecab3a4en%40googlegroups.com.

 

Barbarur

unread,
Sep 24, 2022, 11:39:34 PM9/24/22
to Kivy users support
I see. Having this dummy screen makes ScreenOne behaving similarly as ScreenTwo and on_enter() will trigger with the correct.

I was hopping I could use on_pre_enter() so I can have screen fully ready. Otherwise the Ellipse is still on the wrong place during the transition between screens.

I guess I can work around it by giving it a transparent color as default and change the color at the same time I resize it and move it to the right position.

Elliot Garbus

unread,
Sep 25, 2022, 12:12:34 AM9/25/22
to kivy-...@googlegroups.com
Can you say more about what you want to achieve?

Sent from my iPhone

On Sep 24, 2022, at 8:39 PM, Barbarur <alvaro.a...@gmail.com> wrote:



Elliot Garbus

unread,
Sep 25, 2022, 9:10:30 AM9/25/22
to kivy-...@googlegroups.com

Here is a reorganization of widgets that draws the ball.  The ball is now it’s own widget.  This will simplify animating the Ball.

 

from kivy.app import App
from kivy.lang import Builder
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.screenmanager import Screen
from kivy.uix.widget import Widget

kv =
"""
<Ball>:
    size_hint: None, None

    canvas:
        Color:
            rgba: 1, 0, 0, 1
        Ellipse:
            size: self.size
            pos: self.pos


<ButtonBubble>:

    orientation: 'horizontal'
    spacing: self.width * .1
    padding: self.width * .1

    Button:
        id: left_button
        background_color: 0, 1, 0, .5
        on_press:

    Button:
        id: right_button
        background_color: 0, 1, 0, .5
        on_press: app.root.current = 'ScreenTwo'

<BScreen>:
    ButtonBubble:
        id: btns
    Ball:
        size: self.set_size(btns.ids.left_button.width, btns.ids.left_button.height)
        pos: self.set_pos(btns.ids.left_button.size, btns.ids.left_button.pos)

ScreenManager:
    BScreen:
        name: 'ScreenOne'
    BScreen:
        name: 'ScreenTwo'
"""


class Ball(Widget):
   
def set_size(self, w, h):
       
return min(w, h), min(w, h)

   
def set_pos(self, b_size, b_pos): # button size and pos
        x = (b_size[
0] - self.height) / 2 + b_pos[0]
        y = (b_size[
1] - self.height) / 2 + b_pos[1]
       
return x, y


class ButtonBubble(BoxLayout):
   
pass


class
BScreen(Screen):
   
pass


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


Barbarur

unread,
Sep 25, 2022, 11:29:48 AM9/25/22
to Kivy users support
Hello Elliot,

Thank you for the sample.

The main intention is to have see the ball when the screen is still in transition. On the previous code, the ball was visible only after the screen had finished the transition.

I will try to implement the formulas directly on the .kv files.I haven't done it before, but it seems the way to go. I run your code and the ball is visible from the beginning as intended.

The code below is a prototype of the animation I'm trying to implement. At the moment I'm using only 2 Toggle Buttons. But later I should be able to increase the number of buttons. Later I also want to implement not only the left button would be the selected one by default. It would depend on the saved settings.


from kivy.animation import Animation

from kivy.app import App
from kivy.lang import Builder
from kivy.uix.relativelayout import RelativeLayout
from kivy.uix.screenmanager import Screen, ScreenManager

Builder.load_string("""
<ButtonBubble>:
    left_button: left_button
    right_button: right_button
    bubble: bubble

    RelativeLayout:
        id: bubble
        size_hint: None, None
        size: 0, 0
        pos: 0, 0

        canvas.after:
            Color:
                rgba: 1, 0, 0, 1
            Ellipse:
                size: self.size
                pos: self.pos

    BoxLayout:
        orientation: 'horizontal'
        spacing: self.width * .1
        padding: self.width * .1

        ToggleButton:
            id: left_button
            group: 'btns'
            state: 'down'
            text: 'Button #1'
            background_color: 0, 0, 0, 0
            on_press: root.move_ellipse(self)

        ToggleButton:
            id: right_button
            group: 'btns'
            text: 'Button #2'
            background_color: 0, 0, 0, 0
            on_press: root.move_ellipse(self)

<ScreenOne>:
    buttons_ellipse: buttons_ellipse
    ButtonBubble:
        id: buttons_ellipse""")


class ButtonBubble(RelativeLayout):

    def ellipse_start_pos(self):

        side = min(self.left_button.width, self.left_button.height)
        print(f'Side: {side}')
        self.bubble.size = (side, side)
        pos_x = (self.left_button.width - side) / 2 + self.left_button.pos[0]
        pos_y = (self.left_button.height - side) / 2 + self.left_button.pos[1]
        self.bubble.pos = (pos_x, pos_y)
        print(f'Pos: {self.left_button.pos}')

    def move_ellipse(self, button):

        side = min(button.width, button.height)

        animate = ''

        if button.pos[0] > self.bubble.pos[0]:
            mid_side_x = button.width + button.pos[0] - self.bubble.pos[0]
            mid_side_y = side / 2
            mid_pos_x = self.bubble.pos[0]
            mid_pos_y = (button.height - mid_side_y) / 2 + button.pos[1]
            animate = Animation(duration=.2,
                                                    size=(mid_side_x, mid_side_y),
                                                    pos=(mid_pos_x, mid_pos_y))

        elif button.pos[0] < self.bubble.pos[0]:
            mid_side_x = side + self.bubble.pos[0] - button.pos[0]
            mid_side_y = side / 2
            mid_pos_x = button.pos[0]
            mid_pos_y = (button.height - mid_side_y) / 2 + button.pos[1]
            animate = Animation(duration=.2,
                                                    size=(mid_side_x, mid_side_y),
                                                    pos=(mid_pos_x, mid_pos_y))

        final_pos_x = (button.width - side) / 2 + button.pos[0]
        final_pos_y = (button.height - side) / 2 + button.pos[1]

        animate += Animation(duration=.2,
                                                t='out_back',
                                                size=(side, side),
                                                pos=(final_pos_x, final_pos_y))

        animate.start(self.bubble)


class DummyScreen(Screen):
    pass


class ScreenOne(Screen):

    def on_enter(self, *args):
        print('ScreenOne.on_enter')
        self.buttons_ellipse.ellipse_start_pos()


class MyApp(App):
    sm = None

     def build(self):
        self.sm = ScreenManager()
        self.sm.add_widget(DummyScreen(name='dummy'))
        self.sm.add_widget(ScreenOne(name='ScreenOne'))
        self.sm.current = 'ScreenOne'
        return self.sm


if __name__ == '__main__':
    MyApp().run()
Reply all
Reply to author
Forward
0 new messages