How to build a responsive popup window with scrolling capability in Kivy

Πως να φτιάξετε ένα ευέλικτο αναδυόμενο παράθυρο με δυνατότητα κύλισης στην Kivy

· Coding Προγραμματισμός · kivy kivy python python

Kivy is a cross-platform Python framework for rapid development of applications that make use of innovative user interfaces, such as multi-touch etc. In this article, we show how to build a responsive popup window with scrolling capability.

To Kivy είναι ένα cross-platform Python για γρήγορη ανάπτυξη εφαρμογών σε πολλές διαφορετικές πλατφόρμες που χρησιμοποιεί πρωτοποριακές διεπαφές χρήστη, όπως πολλαπλή αφή κλπ. Σε αυτό το άρθρο, δείχνουμε πως φτιάχνεται ένα ευέλικτο αναδυόμενο παράθυρο με δυνατότητα κύλισης.

First, let's add the necessary imports:

Πρώτα από όλα, ας προσθέσουμε τα απαραίτητα import:

#!/usr/bin/env python
from kivy import platform, require
require('1.9.0')
from kivy.app import App
from kivy.metrics import dp
from kivy.core.window import Window
from kivy.graphics import Color, Line, Rectangle
from kivy.uix.widget import Widget
from kivy.uix.button import Button
from kivy.uix.label import Label
from kivy.uix.modalview import ModalView
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.scrollview import ScrollView

We define our class Pop which inherits from Modalview:

Ορίζουμε την κλάση μας Pop η οποία κληρονομεί από την Modalview:

class Pop(ModalView):

In the __init__ function, we define a BoxLayout object to put inside the various parts that constitute our popup window:

  • A title (a Label object)
  • A separator line (a second BoxLayout object in which we draw a thin Rectangle)
  • A scrolling view (a ScrollView object)
    • Inside the scrolling view, we put the text content of our window (a Label object).
  • A button (a Button object for closing the window).

Note the bindings we make with the functions that update the size of our window parts.

Στη συνάρτηση __init__, ορίζουμε ένα αντικείμενο BoxLayout για να βάλουμε μέσα τα διάφορα μέρη που συναποτελούν το αναδυόμενο παράθυρό μας:

  • Έναν τίτλο (ένα αντικείμενο Label)
  • Μια διαχωριστική γραμμή (ένα δεύτερο αντικείμενο BoxLayout μέσα στο οποίο ζωγραφίζουμε ένα λεπτό ορθογώνιο)
  • Μια κυλιομένη όψη (ένα αντικέιμενο ScrollView)
    • Μέσα στην κυλιόμενη όψη, βάζουμε το περιεχόμενο κείμενο του παραθύρου μας (ένα αντικείμενο Label).
  • Ένα κουμπί (ένα αντικείμενο Button για το κλείσιμο του παραθύρου).

Προσέξτε τις συνδέσεις (bindings) που κάνουμε με τις συναρτήσεις που ανανεώνουν το μέγεθος των τμημάτων του παραθύρου μας.

def __init__(self, title, txt, callback=None, alpha=0.5,
             width=None, height=None, **kwargs):
    super(Pop, self).__init__(**kwargs)
    
    self.callback = callback
    self.auto_dismiss = False
    self.background = "dlgback_green.png"
    self.background_color = (0, 0, 0, alpha)
    self.size_hint = (None, None)
    self.preferred_width = width
    self.preferred_height = height
    
    if self.preferred_width:
        self.width = self.preferred_width
    elif Window.width > 500:  # Big screen?
        self.width = 0.7*Window.width
    else:
        self.width = Window.width-2

    self.playout = BoxLayout(orientation='vertical',
                             padding=["2dp", "5dp",
                                      "2dp", "5dp"],
                             spacing="5dp")

    self.title = Label(size_hint_y=None,
                       text_size=(self.width-dp(20), None),
                       text=title,
                       halign='left',
                       font_size = "16sp",
                       color=(0, 1, 1, 1),
                       markup=True)

    self.separator = BoxLayout(size_hint_y=None, height="1dp")

    self.pscroll = ScrollView(do_scroll_x=False)

    self.content = Label(size_hint_y=None,
                        text=txt,
                        halign='justify',
                        font_size="16sp",
                        markup=True,
                        text_size=(self.width-dp(20), None))

    self.pbutton = Button(text='Close',
                          size_hint_y=None, height="25dp",
                          background_normal=
        "atlas://data/images/defaulttheme/vkeyboard_background")
    self.pbutton.bind(on_release=self.close)

    self.add_widget(self.playout)
    self.playout.add_widget(self.title)
    self.playout.add_widget(self.separator)
    self.playout.add_widget(self.pscroll)
    self.pscroll.add_widget(self.content)
    self.playout.add_widget(self.pbutton)
    
    self.title.bind(texture_size=self.update_height)
    self.content.bind(texture_size=self.update_height)

    with self.separator.canvas.before:
        Color(0, 0.7, 0, 1)
        self.rect = Rectangle(pos=self.separator.pos,
                              size=self.separator.size)

    self.separator.bind(pos=self.update_sep,
                        size=self.update_sep)

    Window.bind(size=self.update_width)

    self.open()

This is the "dlgback_green.png" image we use as background in our window:

Αυτή είναι η είκονα "dlgback_green.png" που χρησιμοποιούμε ως φόντο στο παράθυρο μας:

dlgback_green.png

The function update_width updates the width of the title area, of the content area and of the popup window. Note the hack needed for resizing the dark background of our window:

Η συνάρτηση update_width ανανεώνει το πλάτος της περιοχής του τίτλου, της περιοχής του περιεχομένου και του αναδυόμενου παραθύρου. Προσέξτε το αναγκαίο τέχνασμα στο τέλος για την προσαρμογή του μεγέθους του σκοτεινού φόντου του παραθύρου μας:

def update_width(self, *args):
    # hack to resize dark background on window resize
    self.center = Window.center
    self._window = None
    self._window = Window

    if self.preferred_width:
        self.width = self.preferred_width
    elif Window.width > 500:  # Big screen?
        self.width = 0.7*Window.width
    else:
        self.width = Window.width-2

    self.title.text_size = (self.width - dp(20), None)
    self.content.text_size = (self.width - dp(20), None)

The next function is called to update the height of the title area, of the content area and of the popup window:

H επόμενη συνάρτηση καλείται για να ανανεώσει το ύψος της περιοχής του τίτλου, της περιοχής του περιεχομένου και του αναδυόμενου παραθύρου:

def update_height(self, *args):
    self.title.height = self.title.texture_size[1]
    self.content.height = self.content.texture_size[1]
    temp = self.title.height+self.content.height+dp(56)
    if self.preferred_height:
        self.height = self.preferred_height
    elif temp > Window.height-dp(40):
        self.height = Window.height-dp(40)
    else:
        self.height = temp
    self.center = Window.center

The following function is called to update the position and the size of the separator line between the title and the content of the popup window:

H ακόλουθη συνάρτηση καλείται για να ανανεώσει τη θέση και το μέγεθος της διαχωριστικής γραμμής ανάμεσα στον τίτλο και το περιέχομενο του αναδυόμενου παραθύρου:

def update_sep(self, *args):
    self.rect.pos = self.separator.pos
    self.rect.size = self.separator.size

On close, we dismiss the popup window and we call the callback function (if one has been set).

Στο κλείσιμο, αποσύρουμε το αναδυόμενο παράθυρο και καλούμε την συνάρτηση επανάκλησης (εφόσον έχει ορισθεί κάποια).

def close(self, instance):
    self.dismiss(force=True)
    if self.callback:
        self.callback()

Finally, let's build a test app and run it. Note: you can 'lock' the width and/or height of the popup window yourself by passing the width and/or height argument or you can leave their management to our class. If the content does not fit inside the popup window a scrollbar will appear automatically. You can also set the alpha channel i.e. the opacity of the window.

Τέλος, ας φτιάξουμε μια δοκιμαστική εφαρμογή και ας την τρέκουμε. Σημείωση: μπορείτε να "κλειδώσετε" το πλάτος ή/και το ύψος του αναδυόμενου δίνοντας τα αντίστοιχα ορίσματα width ή/και height ή μπορείτε να αφήσετε τη διαχείρισή τους στην κλάση μας. Αν το περιέχομενο δεν χωράει μέσα στο αναδυόμενο παράθυρο μια μπάρα κύλισης θα εμφανιστεί αυτόματα. Μπορείτε επίσης να ρυθμίσετε το κανάλι άλφα δηλαδή την αδιαφάνεια του παραθύρου.

class TestApp(App):
    def build(self):
        return Pop("Title",
                   "Lorem ipsum dolor sit amet, "
                   "ad solum soleat civibus pri, "
                   "te natum ceteros sea. "
                   "Et his nonumy nonumes. "
                   "Diam cotidieque te has, nostro "
                   "epicurei maluisset est at. "
                   "Dicat scripserit at usu. "
                   "Ne homero labore signiferumque vim, "
                   "et qui petentium "
                   "persequeris, pri at erant epicurei. "
                   "Eu duo wisi causae, "
                   "eum te nullam causae. "
                   "Iudicabit scripserit id vim.",
                   self.callback, alpha=0.5,
                   width=None, height=None)

    def callback(self):
        exit()

TestApp().run()

Here is the result:

Εδώ βλέπετε το αποτέλεσμα:

Kivy popup window

If screen dimensions change (e.g. if the screen is rotated) the popup window will resize accordingly:

Αν οι διαστάσεις της οθόνης αλλάξουν (λ.χ. αν η οθόνη περιστραφεί) το αναδυόμενο παράθυρο θα προσαρμόσει το μέγεθός του ανάλογα:

Kivy popup window

I have created two cross-platform kivy games that make use of popup windows, the «Alamot's Matchstick Puzzles» and the «Retriever of Lost Dreams». If you would like to check them out, you can find both of them here:

Κλείνοντας, να αναφέρω ότι έχω δημιουργήσει με το Kivy δύο παιχνίδια για διαφορές πλατφόρμες που κάνουν χρήση αναδυόμενων παραθύρων, το «Alamot's Matchstick Puzzles» και το «Retriever of Lost Dreams». Αν θέλετε να τα δείτε, μπορείτε να βρείτε και τα δύο εδώ:

Alamot Software

Source codeΠηγαίος κώδικας

Here is the full code listing: Μπορείτε να δείτε τον πλήρη κώδικα εδώ: kivypopup.py

See also...

Δείτε επίσης...