Snippet kv : IconTextCounter

Présentation

Pour aller plus loin avec le widget IconText cité dans un article de ce site, j’ai ajouté un indicateur avec un compteur, tel qu’on peut le voir dans des applications. Kivy ne proposant que des widgets basiques, voici une construction qui répond au problème.

 

Composition

Ce widget hérite de FloatLayout pour placer les éléments de manière relative, l’un au dessus de l’autre. Il contient :

  • un widget IconText
  • un widget Label, avec en image de fond le bleu avec des coins arrondis

 

Code kv

<IconTextCounter>:
	_icontext: _icontext_id
	_counter: _counter_id
	IconText:
		id: _icontext_id
		text: "dummy"
		icon: "images/ic_action_star.png"
		pos_hint: {'x':0, 'top':1}

	Label:
		canvas.before:
			Color:
				rgba: 1,1,1, 1
			Rectangle:
				pos: self.pos
				size: self.size
				source: "kvx_widgets/images/counter_bg.png"
		id: _counter_id
		text: "x"
		pos_hint: {'center_x':0.95, 'top':0.65}
		size_hint: (None, None)
		size: (self.texture_size[0]+sp(16), self.texture_size[1]+sp(8))
		font_size: sp(12)

Une astuce pour avoir un fond qui prend toute la taille du libellé : définir la taille à partir de la texture + un petit espace.

size: (self.texture_size[0]+sp(16), self.texture_size[1]+sp(8))

 

Code Python

class IconTextCounter(FloatLayout):
    counter = StringProperty("")
    counter_position = StringProperty("")
    counter_background = StringProperty("")

    text = StringProperty("")
    icon = StringProperty("")
    icon_size = NumericProperty(sp(80))
    font_size = NumericProperty(sp(18))
    text_color = VariableListProperty([0,0,0,1])
    forced_width = NumericProperty(sp(80))

    def __init__(self, **kwargs):
        super(IconTextCounter, self).__init__(**kwargs)
        self.bind(counter = IconTextCounter.set_counter,
                counter_position = IconTextCounter.set_counter_position,
                counter_background = IconTextCounter.set_counter_background,
                text = IconTextCounter.set_text,
                icon = IconTextCounter.set_icon,
                icon_size = IconTextCounter.set_icon_size,
                font_size = IconTextCounter.set_font_size,
                text_color = IconTextCounter.set_text_color,
                forced_width = IconTextCounter.set_forced_width
                )

    def set_counter(self, aValue):
        self._counter.text = aValue

    def set_counter_position(self, aPosition):
        delta = 0.15
        center_x = 0.5
        top = 0.80
        if aPosition == 'top-left':
            center_x -= delta
            top += delta
        elif aPosition == 'top-right':
            center_x += delta
            top += delta
        elif aPosition == 'bottom-left':
            center_x -= delta
            top -= delta
        elif aPosition == 'bottom-right':
            center_x += delta
            top -= delta

        self._counter.pos_hint = {'center_x':center_x, 'top':top}

    def set_counter_background(self, aSourceImage):
        self._counter.canvas.before.children[1].source = aSourceImage

    ## define all IconText methods
    def set_text(self, aText):
        self._icontext.text = aText

    def set_icon(self, aSourceImage):
        self._icontext.icon = aSourceImage

    def set_icon_size(self, aWidthHeight):
        self._icontext.icon_size = aWidthHeight

    def set_font_size(self, aFontSize):
        self._icontext.font_size = aFontSize

    def set_text_color(self, aColor):
        self._icontext.text_color = aColor

    def set_forced_width(self, aWidth):
        self._icontext.forced_width = aWidth

 

Ce widget est utilisé dans une grille :

picto = IconTextCounter()
picto.text = aCategory.catlblib
picto.icon = 'images/category_%s.png' % aCategory.catcdcode
picto.counter = "%s" % nb_pois
picto.counter_position = 'top-right'
self.grid_widget.add_widget( picto )

 

Snippet kv : ListIconItemButton

Présentation

Kivy propose un widget qui sert d’item dans une liste : « ListItemButton« . Cet item est cliquable, mais son utilisation est relativement complexe. Surtout si on désire ajouter des icones à gauche ou à droite du libellé, ca commence à devenir un casse tête.

Voici une autre implémentation du même besoin, avec des icones de chaque côté du label :

 

Composition

Ce widget hérite de GridLayout, avec, en une ligne :

  • Un Label pour le padding à gauche
  • Un Label pour l’image de gauche
  • Un Label pour le libellé
  • Un Label pour l’image de droite
  • Un Label pour le padding à droite

Si l’image de gauche est spécifiée à None, aucune icône n’apparaît (1er exemple de cet article).

Ce Widget doit être inséré dans un GridLayout, lui même dans un ScrollView:

	ScrollView:
		GridLayout:
			id: list_items_id
			cols: 1
			size_hint_y: None

 

Code kv

<ListIconItemButton>:
	_label_text: _label_text_id
	_padding_right: _padding_right_id
	_padding_left: _padding_left_id
	_left_icon: _left_icon_id
	_right_icon: _right_icon_id
	rows: 1
	size_hint_y: None
	height: sp(50)

	Label:
		id: _padding_left_id
		text: ""
		size_hint_x: None
		width: sp(4)
	Label:
		canvas.before:
			Color:
				rgba: 1,1,1, 1
			Rectangle:
				pos: self.pos
				size: self.size
				source: "images/ic_action_star.png"
		id: _left_icon_id
		text: ""
		size_hint_x: None
		width: sp(50)

	Label:
		id: _label_text_id
		text: "dummy"
		size_hint_x: 1
		halign: 'left'
		valign: 'middle'
		text_size: self.size

	Label:
		canvas.before:
			Color:
				rgba: 1,1,1, 1
			Rectangle:
				pos: self.pos
				size: self.size
				source: "images/ic_action_next.png"
		id: _right_icon_id
		text: ""
		size_hint_x: None
		width: sp(25)

	Label:
		id: _padding_right_id
		text: ""
		size_hint_x: None
		width: sp(4)

 

Code Python

class ListIconItemButton(ButtonBehavior, GridLayout):
	text = StringProperty("")
	text_color = VariableListProperty([0,0,0,1])
	font_size = NumericProperty(sp(18))
	index = NumericProperty(0)
	padding = VariableListProperty([sp(2),sp(2)])
	left_icon_width = NumericProperty(sp(50))
	right_icon_width = NumericProperty(sp(50))
	left_icon = StringProperty("")
	right_icon = StringProperty("")

	def __init__(self, **kwargs):
		super(ListIconItemButton, self).__init__(**kwargs)
		self.bind(text = ListIconItemButton.set_text,
				text_color = ListIconItemButton.set_text_color,
				font_size = ListIconItemButton.set_font_size,
				index = ListIconItemButton.set_index,
				padding = ListIconItemButton.set_padding,
				left_icon_width = ListIconItemButton.set_left_icon_width,
				right_icon_width = ListIconItemButton.set_right_icon_width,
				left_icon = ListIconItemButton.set_left_icon,
				right_icon = ListIconItemButton.set_right_icon
				)

	def set_text(self, aText):
		self._label_text.text = aText

	def set_index(self, aNumber):
		self.item_index = aNumber

	def get_index(self):
		return self.item_index

	def set_padding(self, aPadding):
		if isinstance(aPadding, (float,int,long,float)):
			self._padding_left.width = aPadding
			self._padding_right.width = aPadding
		else:
			self._padding_left.width = aPadding[0]
			self._padding_right.width = aPadding[1]

	def set_font_size(self, aSize):
		self._label_text.font_size = aSize

	def set_text_color(self, aColor):
		self._label_text.color = aColor

	def set_left_icon_width(self, aWidth):
		self._left_icon.width = aWidth

	def set_right_icon_width(self, aWidth):
		self._right_icon.width = aWidth

	def set_left_icon(self, aSourceImage):
		if aSourceImage == "" or aSourceImage is None:
			self.remove_widget(self._left_icon)
		else:
			self._left_icon.canvas.before.children[1].source = aSourceImage

	def set_right_icon(self, aSourceImage):
		if aSourceImage == "" or aSourceImage is None:
			self.remove_widget(self._right_icon)
		else:
			self._right_icon.canvas.before.children[1].source = aSourceImage

Snippet kv : Scrollable text

Présentation

Kivy permet d’implémenter un texte trop long et qui nécessite de scroller vers le bas, mais avec une composition de beaucoup de widgets pour l’intégrer à une page ainsi qu’une configuration assez difficile.

 

Composition

Ce widget hérite de la classe ScrollView qui permet le scrolling et contient un GridLayout, sur 3 lignes :

  • 1ère ligne : un label pour faire le padding-top
  • 2ème ligne : un autre GridLayout, sur 3 colonne :
    • 1ère colonne : un label pour faire le padding-left
    • 2ème colonne : le texte sous forme d’un Label
    • 3ème colonne : un label pour faire le padding-right
  • 3ème ligne un label pour faire le padding-bottom

 

Code kv

<ScrollableText>:
	_text_widget: _text_widget_id
	_padding_top: _padding_top_id
	_padding_left: _padding_left_id
	_padding_right: _padding_right_id
	_padding_bottom: _padding_bottom_id
	canvas.before:
		Color:
			rgba: self.background_color
		Rectangle:
			pos: self.pos
			size: self.size
	GridLayout:
		cols: 1
		height: _text_widget_id.height + _padding_top_id.height + _padding_bottom_id.height
		size_hint_y: None

		Label:
			id: _padding_top_id
			text: " "
			size_hint_y: None
			height: sp(4)

		GridLayout:
			rows: 1
			Label:
				id: _padding_left_id
				text: " "
				size_hint_x: None
				width: sp(8)
			Label:
				id: _text_widget_id
				text: "no text yet"
				font_size: '18sp'
				color: (0.1,0.1,0.1, 1)
				valign: 'top'
				# make it scrollable
				text_size: (self.width, None)
				size_hint_y: None
				size: (self.parent.width, self.texture_size[1] )
			Label:
				id: _padding_right_id
				text: " "
				size_hint_x: None
				width: sp(8)
		Label:
			id: _padding_bottom_id
			text: " "
			size_hint_y: None
			height: sp(4)

Astuce : la propriété « background_color » est directement utilisée dans le KV et non dans le python (comme la taille du texte par exemple).

 

Code Python

class ScrollableText(ScrollView):
    text = StringProperty("")
    background_color = VariableListProperty([1,1,1,0])
    text_color = VariableListProperty([0,0,0,1])
    padding = VariableListProperty([sp(8), sp(8), sp(8), sp(8)])
    font_size = NumericProperty(sp(18))

    def __init__(self, **kwargs):
        super(ScrollableText, self).__init__(**kwargs)
        self.bind(text = ScrollableText.set_text,
                text_color = ScrollableText.set_text_color,
                padding = ScrollableText.set_padding,
                font_size = ScrollableText.set_font_size
                )

    def set_text(self, aText):
        self._text_widget.text = aText

    def set_text_color(self, aColor):
        self._text_widget.color = aColor

    def set_padding(self, aPadding):
        """Top, Right, Bottom, Left
        """
        if isinstance(aPadding, (float,int,long,float)):
            self._padding_top.height = aPadding
            self._padding_right.width = aPadding
            self._padding_bottom.height = aPadding
            self._padding_left.width = aPadding
        if len(aPadding) == 4:
            self._padding_top.height = aPadding[0]
            self._padding_right.width = aPadding[1]
            self._padding_bottom.height = aPadding[2]
            self._padding_left.width = aPadding[3]
        elif len(aPadding) == 2:
            self._padding_top.height = aPadding[0]
            self._padding_right.width = aPadding[1]
            self._padding_bottom.height = aPadding[0]
            self._padding_left.width = aPadding[1]

    def set_font_size(self, aSize):
        self._text_widget.font_size = aSize

 

Snippet kv : icone et texte

Présentation

L’affichage d’une icône, comme représentée sur le bureau Windows n’est pas si simple à réaliser avec kivy.

 

Composition

Ce composant hérite de BoxLayout pour définir une série de widgets disposés verticalement :

  • ligne 1 : l’icône
  • ligne 2 : le texte

Attention, avec un BoxLayout, l’image risque de perdre son ratio (hauteur / largeur) d’origine pour être déformée.

Pour l’icone, le redimensionnement de l’image en conservant le ratio (pour les écrans HD), il faut utiliser un Label avec un background et forcer la taille hauteur et largeur.

 

Code kv

<Icon_Text>:
	size_hint_y: None
	height: sp(100)
	_text_widget: _text_widget_id
	_icon_widget: _icon_widget_id
	Label:
		canvas.before:
			Color:
				rgba: 1,1,1, 1
			Rectangle:
				pos: self.pos
				size: self.size
				source: "images/dummy.png"
		id: _icon_widget_id
		text: " "
		size_hint: (None, None)
		size: (sp(80), sp(80))
		pos_hint: {'center_x':0.5, 'top':0}

	Label:
		id: _text_widget_id
		text: "dummy"
		font_size: '18sp'
		color: (0.2,0.2,0.2, 1)

Le fichier kv contient donc 2 labels : un pour l’icône et un pour le texte.

 

Code Python

class Icon_Text(BoxLayout):
	text = StringProperty("")
	icon = StringProperty("")
	icon_size = NumericProperty(sp(80))
	font_size = NumericProperty(sp(18))
	text_color = VariableListProperty([0,0,0,1])

	def __init__(self, **kwargs):
		super(Icon_Text, self).__init__(**kwargs)
		self.orientation = 'vertical'
		self.bind(text=self.set_text,
				icon=self.set_icon,
				icon_size=self.set_icon_size,
				font_size=self.set_font_size,
				text_color=self.set_text_color)

	def set_text(self, aText):
		self._text_widget.text = aText

	def set_icon(self, aSourceImage):
		self._icon_widget.canvas.before.children[1].source = aSourceImage

	def set_icon_size(self, aWidthHeight):
		self._icon_widget.size = (aWidthHeight, aWidthHeight)

	def set_font_size(self, aFontSize):
		self._text_widget.font_size = aFontSize

	def set_text_color(self, aColor):
		self._text_widget.color = aColor

 

Ce widget est utilisable de la même façon que ceux fournis dans Kivy :

picto = Icon_Text()
picto.set_text( "%s : x %s" % (aCategory_name, nb_pts) )
picto.set_icon( 'images/category_%s.png' % aCategory_code )
self.grid_widget.add_widget( picto )

 

Snippet kv : ActionBar

Présentation

Le Widget « ActionBar » a été ajouté dans la version 1.8 de Kivy et nécessite une petite configuration pour ressembler aux jolies applications iOS ou Android.

 

Composition

 

Pour réaliser ce composant, il faut :

  • Ajouter une image de fond sur l’ActionBar « PNG noire avec une transparence de 25% » (elle peut faire 1×1 pixel). Elle nous permettra de voir l’image de fond (en dessous) et de faire une séparation nette de l’entête.
  • Ajouter les widgets dans l’ActionView :
    • ActionPrevious
    • ActionOverflow
    • ActionButton

Le widget « ActionOverflow » prendra toute la place nécessaire pour pousser le dernier widget « ActionButton » le plus à droite possible.

 

Code kv

ActionBar:
	background_image: "images/empty-25b.png"
	pos_hint: {'top':1}
	ActionView:
		use_separator: True
		ActionPrevious:
			title: 'Localité'
			previous_image: "images/ic_action_previous_item.png"
			app_icon: "images/app-icon.png"
			on_press: root.go_back()

		ActionOverflow:

		ActionButton:
			text: 'Refresh'
			icon: "images/ic_action_refresh.png"
			on_press: root.refresh()