วันพุธที่ 30 กันยายน พ.ศ. 2558

Kivy Project : Kivy: Interactive Applications in Python Chapter 5

ลิงค์

http://bss-i.blogspot.com/2015/09/chapter-5-invaders-revenge-interactive.html

Kivy Project : Kivy: Interactive Applications in Python Chapter 4

Improving the User Experience

ใน chapter นี้ จะพูดถึงการนำความรู้ตั้งแต่บทแรก มาใช้

Screen manager – selecting colors for the figures

Screen manager เป็น class ที่ช่วยในการสร้างหน้าจอโปรแกรมอีกจอนึง ซึ่ง screen นี่จะอยู่บน canvas เดี๋ยวกัน มี size เท่ากันกับหน้าต่างวาดรูป โดยในหนังสือจะนำเป็น ColorPicker เป็นหน้าจอที่ใช้สำหรับเลือกสีของตัว Stickman

ColorPicker

ขั้นตอนแรกให้ทำการสร้างไฟล์ .kv ไฟล์ขึ้นมา

โค้ดของ comicscreenmanager.kv

# File name: comicscreenmanager.kv
#:kivy 1.7.0
#:import FadeTransition kivy.uix.screenmanager.FadeTransition
<ComicScreenManager>:
    transition: FadeTransition()
    color_picker: _color_picker
    ComicCreator:
    Screen:
        name: 'colorscreen'
        ColorPicker:
            id: _color_picker
            Button:
                text: "Select"
                pos_hint: {'center_x': .75, 'y': .05}
                size_hint: None, None
                size: 150, 50
                on_press: root.current = 'comicscreen'

จากนั้นให้ไปเปลี่ยน  ไฟล์ comiccreator.kv

# File name: comiccreator.kv
#:kivy 1.7.0
<ComicCreator@Screen>:
    name: 'comicscreen'
    AnchorLayout:
        anchor_x: 'left'
        anchor_y: 'top'
        ToolBox:
            id: _tool_box
            drawing_space: _drawing_space
            comic_creator: root
            size_hint: None,None
            width: 100
    AnchorLayout:
        anchor_x: 'right'
        anchor_y: 'top'
        DrawingSpace:
            id: _drawing_space
            status_bar: _status_bar
            general_options: _general_options
            tool_box: _tool_box
            size_hint: None,None
            width: root.width - _tool_box.width
            height: root.height - _general_options.height - _status_bar.height
    AnchorLayout:
        anchor_x: 'center'
        anchor_y: 'bottom'
        BoxLayout:
            orientation: 'vertical'
            GeneralOptions:
                id: _general_options
                drawing_space: _drawing_space
                comic_creator: root
                size_hint: 1,None
                height: 48
            StatusBar:
                id: _status_bar
                size_hint: 1,None
                height: 24

Color Control on the canvas – coloring figures

StencilView – limiting the drawing space

Scatter – multitouching to drag, rotate, and scale

Simple gestures – drawing with the finger

วันพุธที่ 23 กันยายน พ.ศ. 2558

Kivy Project : Kivy: Interactive Applications in Python Chapter 3

Attributes, id and root

ใน chapter นี้ จะพูดถึง 4 ส่วนประกอบ นี้ เป็นหลัก คือ toolbox, drawing space, general options, and status bar ทั้ง 4 ส่วนประกอบนี้จะมีการโต้ตอบกัน ซึ่งเรามีความจำเป็นต้องเพิ่ม attributes ไปที่ class ของ project สามารถแสดงความสัมพันธ์ในไฟล์ comiccreator ได้ดังนี้

ภาพจากหนังสือ Kivy : Interactive Applications in Python 

การสร้าง ids ด้วยตัวเอง ไม่ควรนำไปใช้ภายนอกภาษา Kivy ควรสร้าง attributes ไว้เรียกใช้ใน Python Code

จากโค้ด comiccreator.kv เรานำมาดัดแปลงดังนี้

# File name: comiccreator.kv
#:kivy 1.7.0
<ComicCreator>:
    AnchorLayout:
        anchor_x: 'left'
        anchor_y: 'top'
        ToolBox:
            id: _tool_box
            drawing_space: _drawing_space
            comic_creator: root
            size_hint: None,None
            width: 100
    AnchorLayout:
        anchor_x: 'right'
        anchor_y: 'top'
        DrawingSpace:
            id: _drawing_space
            status_bar: _status_bar
            general_options: _general_options
            tool_box: _tool_box
            size_hint: None,None
            width: root.width - _tool_box.width
            height: root.height - _general_options.height - _status_bar.height
    AnchorLayout:
        anchor_x: 'center'
        anchor_y: 'bottom'
        BoxLayout:
            orientation: 'vertical'
            GeneralOptions:
                id: _general_options
                drawing_space: _drawing_space
                comic_creator: root
                size_hint: 1,None
                height: 48
            StatusBar:
                id: _status_bar
                size_hint: 1,None
                height: 24

IDs ที่ highlight ด้วยสีเหลือง คือการประกาศตัวแปร ส่วนที่ hightlight ด้วยสีฟ้า คือการใช้ ids สร้าง attributes มีความสัมพันธ์ดัง comiccreator diagram ก่อนหน้านี้  

ชื่อของ attributes ไม่จำเป็นต้องเปลี่ยนแปลงมาก แค่เพิ่ม _ข้างหน้า ก็พอ เช่น _status_bar 

Basic widget events – dragging the stickman

ตัวอย่าง basic widget events 
    
mouse event , finger event , pen event

ตัวอย่าง event 3 แบบ

on_touch_down : เมื่อกดคลิกบนหน้าจอ หรือ ทัชบนหน้าจอโปรแกรม
on_touch_move : เมื่อกดคลิกแล้วย้าย เช่น ลากเมาส์
on_touch_up      : เมื่อปล่อยเมาส์ หรือ เอานิ้วออกจากหน้าจอ

on_touch_down กับ on_touch_up จะไม่ค่อยมีปัญหาในการแสดงผล แต่ on_touch_move จะไม่แสดงผลที่ไม่มี dragging action วิธีการ add ความสามารถในการ drag สามารถทำได้โดย ดัดแปลงโค้ดดังนี้

โค้ด comicwidgets.kv (add drag capability) 

# File name: comicwidgets.kv
#:kivy 1.7.0
#:import comicwidgets comicwidgets
<DraggableWidget>:
    size_hint: None, None

<StickMan>:
    size: 48,48
    ...

ส่วน comicwidgets.py ให้ดัดแปลงโค้ดดังนี้

# File name: comicwidgets.py
import kivy
kivy.require('1.7.0')
from kivy.uix.relativelayout import RelativeLayout
from kivy.graphics import Line

class DraggableWidget(RelativeLayout):
    def __init__(self,  **kwargs):
        self.selected = None
        self.touched = False
        super(DraggableWidget, self).__init__(**kwargs)

ตรงส่วน __init__ เป็น constructor คลาสนี้จะมี 3 overload method on_touch_down , on_touch_move , on_touch_up

เริ่มที่ on_touch_down

    def on_touch_down(self, touch):
        if self.collide_point(touch.x, touch.y):
            self.touched = True
            self.select()
            return True
        return super(DraggableWidget, self).on_touch_down(touch)

ใช้ method colide_point เพื่อเช็คพิกัดของการ touch หรือ คลิก

event ต่อมา

    def select(self):
        if not self.selected:
            self.ix = self.center_x
            self.iy = self.center_y
            with self.canvas:
                self.selected = Line(rectangle=(0,0,self.width,self.height), dash_offset=2)

event นี้คือจะเช็คว่าถ้าคลิิกตรงกลางของ object ต่างๆ จะให้สร้าง กรอบสี่เหลี่มที่เป็นเส้นประล้อมรอบขึ้นมา

ภาพจากหนังสือ Kivy : Interactive Applications in Python 

ต่อด้วย on_touch_move

    def on_touch_move(self, touch):
        (x,y) = self.parent.to_parent(touch.x, touch.y)
        if self.selected and self.touched and self.parent.collide_point(x - self.width/2, y -self.height/2):
            go = self.parent.general_options
            go.translation=(touch.x-self.ix,touch.y-self.iy)
            return True
        return super(DraggableWidget, self).on_touch_move(touch)

ตรงส่วนการ drag หรือ การลาก จะมีการเช็ค collide_point ไม่ให้ลาก object ออกไปนอก drawing space ถ้าเงื่อนไขเป็น True ก็จะสั่งให้เรียกใช้ translate method

    def translate(self, x, y):
        self.center_x = self.ix = self.ix + x
        self.center_y = self.iy = self.iy + y

สุดท้าย on_touch_up

   def on_touch_up(self, touch):
        self.touched = False
        if self.selected:
            if not self.parent.general_options.group_mode:
                self.unselect()
        return super(DraggableWidget, self).on_touch_up(touch)

on_touch_up event เป็นการคืนค่าสถานะของ on_touch_down ถ้าเช็คแล้วมีการ selected อยู่ ก็จะเรียกใช้ method unselected()

    def unselect(self):
        if self.selected:
            self.canvas.remove(self.selected)
            self.selected = None

มีโค้ดอีกนิดหน่อยใน comicwidgets.py

class StickMan(DraggableWidget):
    pass


Localizing coordinates – adding stickmen

หัวข้อนี้จะพูดถึงการทำปุ่มตรงส่วน toolbox ให้มี event 

# File name: toolbox.py
import kivy
kivy.require('1.7.0')
import math
from kivy.uix.togglebutton import ToggleButton
from kivy.graphics import Line
from comicwidgets import StickMan, DraggableWidget

class ToolButton(ToggleButton):
    def on_touch_down(self, touch):
        ds = self.parent.drawing_space
        if self.state == 'down' and ds.collide_point(touch.x, touch.y):
            (x,y) = ds.to_widget(touch.x, touch.y)
            self.draw(ds, x, y)
            return True
        return super(ToolButton, self).on_touch_down(touch)

    def draw(self, ds, x, y):
        pass

บรรทัดที่ highlight คือ ส่วนที่ใช้ในการเรียก method ในการวาด  ToolStickMan , ToolCircle , และ ToolLine

ตัวอย่างเช่น ToolStickMan  เป็น method ในการวาด stickman ขนาด 48x48 pixel บนหน้า drawingspace

class ToolStickman(ToolButton):
    def draw(self, ds, x, y):
        sm = StickMan(width=48, height=48)
        sm.center = (x,y)
        ds.add_widget(sm)


Binding and unbinding events – sizing limbs and heads

หัวข้อนี้จะพูดถึงการ ย่อขยายต่างๆ เช่น วงกลม กับ เส้น เพื่อสร้าง stickman ในขนาดที่แตกต่างออกไป

class toolfigure จะมีทั้งหมด 6 medthod

class ToolFigure(ToolButton):

    def draw(self, ds, x, y):
        (self.ix, self.iy) = (x,y)
        with ds.canvas:
            self.figure=self.create_figure(x,y,x+1,y+1)
        ds.bind(on_touch_move=self.update_figure)
        ds.bind(on_touch_up=self.end_figure)

    def update_figure(self, ds, touch):
        if ds.collide_point(touch.x, touch.y):
            (x,y) = ds.to_widget(touch.x, touch.y)
            ds.canvas.remove(self.figure)
            with ds.canvas:
                self.figure = self.create_figure(self.ix, self.iy,x,y)

    def end_figure(self, ds, touch):
        ds.unbind(on_touch_move=self.update_figure)
        ds.unbind(on_touch_up=self.end_figure)
        ds.canvas.remove(self.figure)
        (fx,fy) = ds.to_widget(touch.x, touch.y)
        self.widgetize(ds,self.ix,self.iy,fx,fy)

    def widgetize(self,ds,ix,iy,fx,fy):
        widget = self.create_widget(ix,iy,fx,fy)
        (ix,iy) = widget.to_local(ix,iy,relative=True)
        (fx,fy) = widget.to_local(fx,fy,relative=True)
        widget.canvas.add(self.create_figure(ix,iy,fx,fy))
        ds.add_widget(widget)

    def create_figure(self,ix,iy,fx,fy):
        pass

    def create_widget(self,ix,iy,fx,fy):
        pass

class toolfigure จะมีทั้งหมด 6 medthod
  1. draw: ให้เริ่มวาดจากจุดที่เราคลิกครั้งแรก เช่น เริ่มจากตรงกลางของวงกลม หรือ จุดปลายของเส้น
  2. update_figure : update จุดเริ่มต้นถึงจุดสุดท้ายของรูป เช่น รัศมีจากจุดกึ่งกลาง หรือ จากจุดหนึ่งไปยังอีกจุดหนึ่งของเส้น
  3. end_figure : จะบอกจุดสุดท้าย การทำงานคล้ายกับ update_figure
  4. widgetize : สร้างการ drag figure 
  5. create_figure : เป็น method ที่เรียกใช้ ToolLine ,ToolCircle ในการวาดรูป
  6. create_widget : ทำงานเหมือน create_figure 
ไม่ต้องการให้ on_touch_move กับ on_touch_up events ทำงานตลอดเวลา จึงใช้ Binding and unbinding events ในการแก้ปัญหา (ให้แค่ลากแล้วปล่อย) 

โค้ดในส่วนของ class ToolLine ,ToolCircle

class ToolLine(ToolFigure):
    def create_figure(self,ix,iy,fx,fy):
        return Line(points=[ix, iy, fx, fy])

    def create_widget(self,ix,iy,fx,fy):
        pos = (min(ix, fx), min(iy, fy)) 
        size = (abs(fx-ix), abs(fy-iy))
        return DraggableWidget(pos = pos, size = size)

class ToolCircle(ToolFigure):
    def create_figure(self,ix,iy,fx,fy):
        return Line(circle=[ix,iy,math.hypot(ix-fx,iy-fy)])

    def create_widget(self,ix,iy,fx,fy):
        r = math.hypot(ix-fx, iy-fy)
        pos = (ix-r, iy-r)
        size = (2*r, 2*r)
        return DraggableWidget(pos = pos, size = size)

ทั้ง 2 class จะใช้การคำนวณแบบธรรมดาในการกำหนดขนาด

โค้ดในส่วนของ toolbox.kv  (มีการแก้ไข)

# File name: toolbox.kv
#:kivy 1.7.0
#:import toolbox toolbox
<ToolButton>:
    size_hint: None,None
    size: 48,48
    group: 'tool'
    canvas:
        PushMatrix:
        Translate:
            xy: self.x,self.y
    canvas.after:
        PopMatrix:

<ToolBox@GridLayout>:
    cols: 2
    padding: 2
    tool_circle: _tool_circle
    tool_line: _tool_line
    tool_stickman: _tool_stickman
    ToolCircle:
        id:  _tool_circle
        canvas:
            Line:
                circle: 24,24,14
    ToolLine:
        id: _tool_line
        canvas:
            Line:
                points: 10,10,38,38
    ToolStickman:
        id: _tool_stickman
        StickMan:
            pos_hint: {'center_x':.5,'center_y':.5}

โค้ดนี้จะมีการเพิ่ม class ที่เราทำใหม่เข้ามา ToolCircle , ToolLine , ToolStickman และมีการเพิ่ม attributes ที่เอาไว้ใช้ในการสร้าง gestures ใน chapter ที่ 4

Binding events in the Kivy language

ในหัวข้อนี้จะพูดถึงการทำ event ในส่วนของ general option

โค้ด generaloption.kv

# File name: generaloptions.kv
#:kivy 1.7.0
#:import generaloptions generaloptions
<GeneralOptions>:
    orientation: 'horizontal'
    padding: 2
    Button:
        text: 'Clear'
        on_press: root.clear(*args)
    Button:
        text: 'Remove'
        on_release: root.remove(*args)
    ToggleButton:
        text: 'Group'
        on_state: root.group(*args)
    Button:
        text: 'Color'
        on_press: root.color(*args)
    ToggleButton:
        text: 'Gestures'
        on_state: root.gestures(*args)

Button หรือ ปุ่มกด จะมี event หลักๆอยู่ 2 อย่างคือ on_press กับ on_release 
on_press จะใช้กับ Clear Button กับ Color Button ก็คือกดครั้งเดียวพอ ทำงานเสร็จก็จบ
on_release  จะใช้กับ Remove Button กดแล้วสามารถกดต่อได้อีก
on_state จะใช้ ToggleButton ก็คือ กดครั้งนึงไว้แล้วจะทำงานตลอดจนกว่าจะกดอีกครั้งเพื่อหยุด ใช้กับ GroupButton กับ GestureButton

โค้ดในส่วน generaloptions.py

# File name: generaloptions.py
import kivy
kivy.require('1.7.0')
from kivy.uix.boxlayout import BoxLayout
from kivy.properties import NumericProperty, ListProperty

class GeneralOptions(BoxLayout):
    group_mode = False
    translation = ListProperty(None)

    def clear(self, instance):
        self.drawing_space.clear_widgets()

    def remove(self, instance):
        ds = self.drawing_space
        if len(ds.children) > 0:
            ds.remove_widget(ds.children[0])

    def group(self, instance, value):
        if value == 'down':
            self.group_mode = True
        else:
            self.group_mode = False
            self.unselect_all()

    def color(self, instance):
        pass

    def gestures(self, instance, value):
        pass

    def unselect_all(self):
        for child in self.drawing_space.children:
            child.unselect()

    def on_translation(self,instance,value):
        for child in self.drawing_space.children:
            if child.selected:
                child.translate(*self.translation)

class GeneralOptions มีทั้งหมด 7 methods
  1. clear : ล้าง widget ที่เราวาดใน drawing space ทั้งหมด
  2. remove : ลบการวาดครั้งล่าสุด ถ้ากดอีกก็จะลบการวาดครั้งก่อนหน้านั้นไปเรื่อยๆ
  3. group : ถ้าคลิกเลือกจะมีการเปลี่ยนโหมด จาก True เป็น False
  4. color : ในที่นี้ยังไม่มี event
  5. gesture : ในที่นี้ยังไม่มี event
  6. unselect_all : ทำงานเมื่อไมได้คลิกรูปใดๆ
  7. on_translation : 

Creating your own events – the magical properties

การสร้าง properties

ตัวอย่าง types of properties
NumericProperty , StringProperty , ListProperty , DictProperty , or ObjectProperty

ถ้าเป็น Kivy property จะให้ตั้งชื่อโดย ใส่ on_ หน้าชื่อของ properties เช่น state property ของ
ToogleButton ก็จะเป็น on_state

นอกจากนี้ยังกล่าวถึงการสร้าง event ของ group ซึ่งใช้ state propertty ของ ToogleButton ซึ่งเราต้องการให้ เมื่อเราอยู่ใน group mode เราสามารถเลือก object หลายๆตัว แล้วลากไปพร้อมๆกันได้

ภาพจากหนังสือ Kivy : Interactive Applications in Python 

แก้โค้ดในส่วนของ comicwidgets.py ดังนี้

# File name: comicwidgets.py
import kivy
kivy.require('1.7.0')
from kivy.uix.relativelayout import RelativeLayout
from kivy.graphics import Line

class DraggableWidget(RelativeLayout):
    def __init__(self,  **kwargs):
        self.selected = None
        self.touched = False
        super(DraggableWidget, self).__init__(**kwargs)

    def on_touch_down(self, touch):
        if self.collide_point(touch.x, touch.y):
            self.touched = True
            self.select()
            return True
        return super(DraggableWidget, self).on_touch_down(touch)

    def select(self):
        if not self.selected:
            self.ix = self.center_x
            self.iy = self.center_y
            with self.canvas:
                self.selected = Line(rectangle=(0,0,self.width,self.height), dash_offset=2)

    def on_touch_move(self, touch):
        (x,y) = self.parent.to_parent(touch.x, touch.y)
        if self.selected and self.touched and self.parent.collide_point(x - self.width/2, y -self.height/2):
            go = self.parent.general_options
            go.translation=(touch.x-self.ix,touch.y-self.iy)
            return True
        return super(DraggableWidget, self).on_touch_move(touch)

    def translate(self, x, y):
        self.center_x = self.ix = self.ix + x
        self.center_y = self.iy = self.iy + y

    def on_touch_up(self, touch):
        self.touched = False
        if self.selected:
            if not self.parent.general_options.group_mode:
                self.unselect()
        return super(DraggableWidget, self).on_touch_up(touch)

    def unselect(self):
        if self.selected:
            self.canvas.remove(self.selected)
            self.selected = None

class StickMan(DraggableWidget):
    pass

Kivy and properties

หัวข้อนี้ก็จะเป็นตัวอย่างของ property ใน Kivy 

ตัวอย่างจากโค้ด statusbar.py

# File name: statusbar.py
import kivy
kivy.require('1.7.0')
from kivy.uix.boxlayout import BoxLayout
from kivy.properties import NumericProperty, ObjectProperty

class StatusBar(BoxLayout):
    counter = NumericProperty(0)
    previous_counter = 0

    def on_counter(self, instance, value):
        if value == 0:
            self.msg_label.text = "Drawing space cleared"
        elif value - 1 == self.__class__.previous_counter:
            self.msg_label.text = "Widget added"
        elif value + 1 == StatusBar.previous_counter:
            self.msg_label.text = "Widget removed"
        self.__class__.previous_counter = value

มีการใช้ NumericProperty (on_counter) ในการนับ Figure ที่เราสร้าง 

โค้ดในส่วน statusbar.kv

# File name: statusbar.kv
#:kivy 1.7.0
#:import statusbar statusbar
<StatusBar>:
    msg_label: _msg_label
    orientation: 'horizontal'
    Label:
        text: 'Total Figures: ' + str(root.counter)
    Label:
        id: _msg_label
        text: "Kivy started"

ส่วนต่อมาเราต้องการที่ count หรือนับ figure ที่อยู่บน drawings space ให้ทำการแก้ไขโค้ดใน drawingspace.py ดังนี้

# File name: drawingspace.py
import kivy
kivy.require('1.7.0')
from kivy.uix.relativelayout import RelativeLayout

class DrawingSpace(RelativeLayout):
    def on_children(self, instance, value):
        self.status_bar.counter = len(self.children)

โค้ดในส่วนของ  drawingspace.kv

# File name: drawingspace.kv
#:kivy 1.7.0
#:import drawingspace drawingspace
<DrawingSpace@RelativeLayout>:

หน้าต่างเริ่มต้นโปรแกรม


ทดลองสร้าง Figure ต่างๆ

เมื่อคลิกเมาส์ที่ figure จะมีการสร้าง กรอบสี่เหลี่ยมที่เป็นเส้นประล้อมรอบ

ลองกด clear button จะเห็นได้ว่า figure นั้นถูกลบไปหมดแล้ว และมีข้อความแสดง Drawing space cleared

ลองสร้าง Stickman แล้วใช้คำสั่ง Group 

สามารถเคลื่อนย้าย Stickman ได้



ทดลองสร้าง Stickman 2 ตัว 

ทดลองใช้คำสั่ง Remove 

จะเห็นได้ว่าตัวที่เพิ่มเข้ามาล่าสุดถูก Remove ออกไป

อ้างอิงข้อมูลและรูปภาพจากหนังสือ Kivy : Interactive Applications in Python

Comic Creator Chapter 2

Comic creator – PushMatrix and PopMatrix

หลังจากที่เราได้สร้าง GUI ไปใน chapter 1 ก็ได้เวลาสร้างตัวละครกันแล้ว
ในส่วนนี้เรายังใช้ RelativeLayout โดยจะเพิ่มไฟล์ใหม่ชื่อว่า comicwidgets.kv ในส่วนของ commiccreator.py ก็ทำการสร้าง Builder ใหม่ตามนี้
     
           Builder.load_file('comicwidgets.kv')

ในส่วนของ commicwidgets.kv

# File name: comicwidgets.kv
#:kivy 1.7.0
<StickMan@RelativeLayout>:
    size_hint: None, None
    size: 48,48
    canvas:
        PushMatrix
        Line:
            circle: 24,38,5
        Line:
            points: 24,33,24,15
        Line:
            points: 14,5,24,15
        Line:
            points: 34,5,24,15
        Translate:
            y: 48-8
        Rotate:
            angle: 180
            axis: 1,0,0
        Line:
            points: 14,5,24,15
        Line:
            points: 34,5,24,15
        PopMatrix

โค้ดนี้จะพูดถึงการสร้าง stickman โดยถ้าต้องการคืนค่าการวาด stickman ของเรา สามารถทำได้โดยใช้ Kivy Instruction 2 ตัวนี้  PushMatrix และ PopMatrix ในตอนแรก เรา PushMatrix มันจะเก็บค่า state ปัจจุบันเอาไว้ จนกว่าเราจะปิดโปรแกรมหรือทำอย่างอื่น หากเราต้องการคืนค่าไป state ก่อนหน้านี้ ให้คลิกที่เดิมอีกที จะเป็นการคืนค่า state เราเรียกว่า PopMatrix

ต่อมาเราทำการเพิ่มรูปร่างให้กับ ToolButton (วงกลม กับ เส้นตรง) ใน ToolBox ททางด้านซ้ายบน ทำการแก้โค้ด toolbox.kv ดังนี้

# File name: toolbox.kv
<ToolButton@ToggleButton>:
    size_hint: None,None
    size: 48,48
    group: 'tool'
    canvas:
        PushMatrix:
        Translate:
            xy: self.x,self.y
    canvas.after:
        PopMatrix:

<ToolBox@GridLayout>:
    cols: 2
    padding: 2
    ToolButton:
        canvas:
            Line:
                circle: 24,24,14
    ToolButton:
        canvas:
            Line:
                points: 10,10,38,38
    ToolButton:
        StickMan:
            pos_hint: {'center_x':.5,'center_y':.5}

ทำการเพิ่มโค้ดในส่วนของ drawingspace.kv

# File name: drawingspace.kv
<DrawingSpace@RelativeLayout>:
    StickMan:
        pos_hint: {'center_x':.5,'center_y':.5}
        canvas.before:
            Translate:
                xy: -self.width/2, -self.height/2
            Scale:
                xyz: 2,2,0
    StickMan:

ผลลัพท์ที่ได้จากการรัน file comiccreator.py


อ้างอิงข้อมูลและรูปภาพจากหนังสือ Kivy : Interactive Applications in Python


Kivy Project : Kivy: Interactive Applications in Python Chapter 2

ลิงค์

http://bss-i.blogspot.com/2015/09/chapter-2-graphics-canvas.html

Comic Creator Chapter 1

ตัวอย่างการทำ Application Comic Creator

ขั้นตอนแรกของการทำ App เราควรร่างแบบ GUI ไว้ในกระดาษก่อน กำหนดว่าส่วนไหนจะให้ทำอะไร อยู่ตรงไหนของหน้าต่าง App
ตัวอย่างการออกแบบ GUIภาพจากหนังสือ Kivy : Interactive Applications in Python

กำหนดส่วนประกอบต่างๆ ของ App ด้วย Layout ที่เรียนมาในบทที่ 1 ดังนี้

  • ใช้ AnchorLayout สำหรับ toolbox area ตรงส่วน มุมซ้ายบน
  • ใช้ Gridlayout สำหรับ drawing tools 2 columns
  • ใช้ AnchorLayout สำหรับ drawing space ตรงส่วน มุมขวาบน
  • ใช้ RelativeLayout สำหรับ พื้นที่ที่เกี่ยวข้องกับการวาด
  • ใช้ AnchorLayout สำหรับ general options และ status bar ตรงส่วนล่าง
  • ใช้ BoxLayout ในแนวตั้งเพื่อจัดตำแหน่งของ general option ที่ด้านบนของ status bar และ ใช้ BoxLayout ในแนวนอนสำหรับ ปุุ่มกดของ general option และ label ของ status bar


หลังจากที่เราได้กำหนดส่วนต่างแล้ว ก็ทำการสร้างไฟล์ของพื้นที่ส่วนต่างขึ้นมา comiccreator.
py , comiccreator.kv , toolbox.kv , generaltools.kv , drawingspace.kv , และ
statusbar.kv

โค้ดในส่วนของ commiccreator.py

# File name: comiccreator.py
import kivy
kivy.require('1.7.0')
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.anchorlayout import AnchorLayout

Builder.load_file('toolbox.kv')
Builder.load_file('drawingspace.kv')
Builder.load_file('generaloptions.kv')
Builder.load_file('statusbar.kv')

class ComicCreator(AnchorLayout):
    pass

class ComicCreatorApp(App):
    def build(self):
        return ComicCreator()

if __name__=="__main__":
    ComicCreatorApp().run()

Builder.load_file('....') คือการโหลดไฟล์ ซึ่งจะเห็นได้ว่าไม่จำเป็นต้องโหลดไฟล์ commiccreator.kv เนื่องจากไฟล์นี้จะถูกโหลดโดยอัตโนมัติจากการเรียกใช้ ComicCreatorApp

สำหรับ Comiccreator เราเลือกใช้ AnchorLayout ซึ่งไม่ใช่ทางเลือกเดียว แต่ว่าถ้าถึงขั้นตอนในการเขียน code ขั้นต่อไปจะสามารถทำได้ดีกว่า

โค้ดในส่วนของ commiccreator.kv

# File name: comiccreator.kv
#:kivy 1.7.0
<ComicCreator>:
    AnchorLayout:
        anchor_x: 'left'
        anchor_y: 'top'
        ToolBox:
            id: _tool_box
            size_hint: None,None
            width: 100
    AnchorLayout:
        anchor_x: 'right'
        anchor_y: 'top'
        DrawingSpace:
            size_hint: None,None
            width: root.width - _tool_box.width
            height: root.height - _general_options.height - _status_bar.height
    AnchorLayout:
        anchor_x: 'center'
        anchor_y: 'bottom'
        BoxLayout:
            orientation: 'vertical'
            GeneralOptions:
                id: _general_options
                size_hint: 1,None
                height: 48
            StatusBar:
                id: _status_bar
                size_hint: 1,None
                height: 24

สร้าง ToolButton กำหนดขนาดโดย drawing tools และใช้ Kivy Widget : ToolgleButton
สิ่งที่แตกต่างจาก button ปกติก็คือ มันจะคลิกใช้ไปเรื่อยๆจนกว่าเราจะกดคลิกที่ตัวมันอีกที ตัวอย่าง

Toolbox area with an active ToggleButton

ภาพจากหนังสือ Kivy : Interactive Applications in Python 

โค้ดในส่วนของ toolbox.kv

# File name: toolbox.kv
#:kivy 1.7.0
<ToolButton@ToggleButton>:
    size_hint: None,None
    size: 48,48
    group: 'tool' #คำสั่งรวมกลุ่ม button

<ToolBox@GridLayout>:
    cols: 2
    padding: 2
    ToolButton:
        text: 'O'
    ToolButton:
        text: '/'
    ToolButton:
        text: '?'

โค้ดในส่วนของ generaloptions.kv

# File name: generaloptions.kv
#:kivy 1.7.0
<GeneralOptions@BoxLayout>:
    orientation: 'horizontal'
    padding: 2
    Button:
        text: 'Clear'
    Button:
        text: 'Remove'
    ToggleButton:
        text: 'Group'
    Button:
        text: 'Color'
    ToggleButton:
        text: 'Gestures'

ตรงส่วนนี้เราไม่ต้องการให้ button อยู่ใน group เดียวกัน เนื่องจาก button แต่ละอันไม่ได้มีการทำงานที่เกี่ยวข้องกัน ในโค้ดนี้จะยังไม่มีการทำงานใดๆเมื่อกดปุ่ม เป็นเพียงแค่ส่วนของ interface เท่านั้น

General Option area

ภาพจากหนังสือ Kivy : Interactive Applications in Python 
โค้ดในส่วนของ statusbar.kv

# File name: statusbar.kv
#:kivy 1.7.0
<StatusBar@BoxLayout>:
    orientation: 'horizontal'
    Label:
        text: 'Total Figures: ?'
    Label:
        text: "Kivy started"

ผลที่ได้จากการใช้ BoxLayout ก็คือตรงส่วนที่เป็น buttons จะเป็น labels แทน

Status Bar area

ภาพจากหนังสือ Kivy : Interactive Applications in Python 

โค้ดในส่วนของ drawingspace.kv

# File name: drawingspace.kv
#:kivy 1.7.0
<DrawingSpace@RelativeLayout>:
    Label:
        markup: True
        text: "[size=32px][color=#3e6643]The[/color] [sub]Comic[/sub] [i][b]Creator[/b][/i][/size]"

DrawingSpace เป็น subclass ของ RelativeLayout แนะนำให้ใช้ Kivy markup ซึ่งเป็น feature ในการออกแบบ Label class การทำงานของมันจะคล้ายๆกับ XML based languages

เมื่อทำการรันไฟล์ commiccreator.py จะได้หน้าตา GUI ออกมาเป็นแบบนี้ ซึ่งนี่เป็นเพียง GUI เท่านั้น ยังไม่มีการทำงานใดๆ



อ้างอิงข้อมูลและรูปภาพจากหนังสือ Kivy : Interactive Applications in Python