KivyMd를 이용한 Toy project DeepMom -2-

치삼이·2021년 9월 29일
0
post-thumbnail

👨‍💻 Toy Project DeepMom -2-

1. kivy 란?

들어가기전에 잠깐 Kivy에 대한 짧은 설명을 읽고 가도록 하자. 👩‍🏫

Kivy는 자연스러운 사용자 인터페이스로 모바일 앱 및 기타 멀티 터치 애플리케이션 소프트웨어를 개발하기위한 무료 오픈 소스 Python 프레임 워크입니다. MIT 라이선스 조건에 따라 배포되며 Android, iOS, Linux, macOS 및 Windows에서 실행할 수 있습니다.
출처: 🔗 위키피디아 Kivy

위 설명을 한 줄로 요약해보자면 Open Source Python GUI Framework 이며, Window 나 Linux이 아닌 Android같은 모바일 어플리케이션에 초점이 맞춰져 있다. 실제로 Kivy의 UI는 굉장히 구형 Android를 생각나게 한다.

왼쪽이 2012년경 사용했던 갤럭시 넥서스의 설정 UI이고 오른쪽이 Kivy의 기본 UI다.

간단한 UI가 필요한 애플리케이션을 만들때 개발자가 어느정도의 디자인적 요소를 고려하고 싶은데 미적 감각이 꽝이라면 Kivy의 이용을 고려해 볼만 하더라... 디테일에 크게 신경쓰지 않더라도 구형 안드로이드 앱처럼 보일수 있더라...

아무튼 이 위키피디아에 Kivy를 검색 🔎 관련 프로젝트 리스트를 보면 2021.09.30현재 가장 마지막에 KivyMD 가 있는것을 확인할 수 있다.

2. KivyLang

안드로이드 애플리케이션을 작성할때 XML을 사용하는것 처럼 Kivy를 이용해 애플리케이션을 작성할 땐 KivyLang(.kv 파일)이라는 markup language를 이용해 각 컴포넌트를의 프로퍼티를 지정해 디자인을 한다(물론 버튼 릴리즈 콜백 같은 기능적인 것들은 파이썬에서 정의). 문법이나 기타 여러가지 Kivy의 특징을 설명하기에는... 아직까지 공식문서를 다 못봤고 또 그러기위한 포스팅도 아니니.. Kivy를 자세하게 알고싶다면 공식 문서를 확인해보자. 👍

# main.py
class LoadDialog(FloatLayout):
    def load(self, filename): pass
    def cancel(self): pass
# associated .Kv
<LoadDialog>:
    BoxLayout:
        size: root.size
        pos: root.pos
        orientation: "vertical"

        FileChooserListView:
            id: filechooser

        BoxLayout:
            size_hint_y: None
            height: 30

            Button:
                text: "Cancel"
                on_release: root.cancel()

            Button:
                text: "Load"
                on_release: root.load(filechooser.path, filechooser.selection)

참고로 파이썬의 대표 IDEs라 할 수 있는 PyCharm의 Plugin에는 KivyLang Plugin이 없더라... 대신 Visual Studio Code에는 서너개 정도 있으니 맘에드는 Plugin으로 설치 하면 된다. 👍

3. KivyMD

KivyMD는 Kivy의 기본 컴포넌트들을 Material design 규격 에 맞춘 라이브러리 이다. 2021.09.30현재 최신 버전은 0.104.2 이며 MIT License를 채택했다.

Material design 스펙을 채택해서 그런지 Kivy에 비해 약간 강제성이 되는부분이 있더라 예를 들면 버튼크기가 지정이 명시적으로 안된다. 몇몇 버튼에 width 속성이 없더라(편법인지는 모르겠지만 바꿀수는 있다)... 이 🔗 issue 는 github에 올라 왔는데 거기에 대한 대답은

Material design의 규칙때문에 텍스트 크기와 같아야 한다고 하더라. 그래도 KivyMD의 장점은 Python을 이용해 모바일 앱 및 데스크톱 애플리케이션을 작성할 수 있다는 점이며, 기본적으로 Material design을 사용하기 때문에 몇몇 디자인 시안을 참고하면 미적 감각이 없는 사람이 만든것 치곤 꽤 모던하게 만들 수 있더라.👏👏👏

4. KivyMD in DeepMom

4.1 ConnectScreen.py

내가 Java나 Kotlin을 해본적이 없어서 다른 프레임워크나 라이브러리에도 Screen이라는 모듈이 있는지는 모르겠다... 아무튼 Screen이란 화면 전환을 통해 여러 특정 디바이스에 맞게 dpi, 화면사이즈, 구성요소, 환경설정을 바꿔준다. 라고 공식문서에 적혀 있다. 아래 그림을 보면 확실히 이해가 된다(화면 전환).

DeepMom은 크게 ConnectScreen과 DashboardScreen으로 나뉜다. 이번 포스팅에서는 ConnectScreen을 어떻게 작성했는지 까지만...🤣🤣

ConnectScreen은 기본적으로 Broker와 연결하기 위한 Screen이다. 아무것도 입력안하면 localhost:1883으로 연결된다. 여기서 가장 기억에 남은 난관은 아래 두 가지 였다.

  1. kivy는 메인 루프를 계속 돌면서 이벤트가 발생할때 콜백을 실행시킨다. MQTT Broker를 연결하기 위해 MQTT Client에서 Connect를 호출하면 Broker와 연결하기위해 잠깐 Blocking 되는데 연결이 바로되면 사용자는 상관없지만 연결이 지연될 경우 캔슬을 눌러야 한다. 이 캔슬을 누를 경우 다음 프레임에서 실행을 할텐데 캔스을 Blocking이 Timeout 전에 어떻게 실행 시킬것인가.

한마디로 그냥 "브로커랑 커넥션을 할때 브로커가 없거나 연결이 너무 느리면 캔슬을 눌러야 되는데, 타임 아웃이 일어난 뒤에나 캔슬이 눌린다는 것" 사실 큰 고민할 것 없이 MultiThread 나 MultiProecess를 이용하면 된다.

여기서 처음 버그를 발견했다. 🔗 Stack Overflow 에서 확인해보자.

Python에서 MulitProcess를 이용해 KivyMD를 실행하면 빈화면 하나가 더 뜨더라... Python은 Global interpreter Lock 정책 때문에 병렬성 향상을위해 MultiThread 대신 MulitProcess를 사용하려 했지만... 참고로 KivyMD가 아닌 Kivy에서는 일어나지 않는 버그다.


 def thread_work(self):
        while not self._thread_terminate:
            if not self._in_queue.empty():
                response = self._in_queue.get()

                self._button_press = False
                self.ids.spinner.active = False
                self.ids.using_account_check_box.disabled = False
                self.ids.connect_button.text = 'Connect'

                if response.response_state == DMRes_state.CONNECT_OK:
                    self._thread_terminate = True

                    animate = Animation(size=(630, 300), duration=.3)
                    animate.start(self.ids.base_Float)
                    
                    ...
                 
                else:
                    raise ValueError
            else:
                sleep(.1)

이 Thread는 지속적으로 queue를 검사하고 그 내용을 확인하고 Connection이 성립되면 애니메이션을 보여주고 종료된다. sleep(.1)을 넣지 않으면 살짝 버벅이더라.

kivyMD에 자잘 자잘한 버그가 있다고 계속 말하는데 두 번째 버그는 아래 코드에서 확인할 수 있다.

    def check_press(self):
    	# here
        self.ids.using_account_check_box.unselected_color = utils.get_color_from_hex('#607D8B')
        if self.ids.using_account_check_box.active:
            animate = Animation(pos_hint={'center_x': .5, 'center_y': .5}, duration=.2)
            animate.start(self.ids.option_text_field_2)
            animate = Animation(size=(0, 450), duration=.2)
            animate += Animation(size=(400, 450), duration=.2)
            animate.start(self.ids.option_text_field_1)
            self.ids.user_id.disabled = False
            self.ids.user_passwd.disabled = False
            self.ids.user_id.hint_text = 'Enter Your ID'
            self.ids.user_passwd.hint_text = 'Enter Your Password'
        else:
            self.ids.user_id.disabled = True
            self.ids.user_passwd.disabled = True
            self.ids.user_id.text = ''
            self.ids.user_passwd.text = ''
            self.ids.user_id.hint_text = ''
            self.ids.user_passwd.hint_text = ''
            animate = Animation(size=(0, 0), duration=.2)
            animate.start(self.ids.option_text_field_1)
            animate = Animation(pos_hint={'center_x': .5, 'center_y': .65}, duration=.2)
            animate.start(self.ids.option_text_field_2)
            self.ids.spinner.active = False

위 코드는 체크박스가 눌렸을때 내부 값들이 어떻게 바뀌는지 보여주는 코드다.. 맨위에 명시적으로 색을 지정한 이유는 kivyMD의 기본 테마 컬러는 블루이지만 다른 테마컬러를 선택하면 컴포넌트들이 색이 바뀐다. DeepMom은 BlueGray인데 저 체크박스는 자꾸 파란색으로 바뀌는 버그가 있다.

이 버그는 BlueGray가 뿐 아니라 모든 컬러의 테마 심지어 kv 파일에서 속성을 명시적으로 지정해도 자꾸 기본값인 파란색으로 바뀐다. 또 TextFiled역시 잠깐 파란색으로 바뀔때 가 있다.

프로젝트 내부에 DeepMomRequestResponse.py 란 파일이 있는데 이 파일은 브로커와의 커넥션요청, 커넥션완료 중간에 캔슬 명령 객체이다.

4.2 ConnectScreen.kv

두 번째 고민은 다음과 같다.

  1. Connect와 Cancel 두 글자의 수가 다른데 이 때문에 자꾸 버튼의 사이즈가 변경되 보기가 안좋다... 이걸 어떻게 하지...? 였다. 위 에서 언급했던것 처럼 버튼의 크기는 글자의 숫자에따라 바뀐다. 다음과 같은 방법이 편법인지 혹인 정상 방법인지는 모르겠지만 아래의 방식으로하면 버튼 크기가 고정되더라.
MDFloatLayout:
	id: base_Float
	pos_hint: {'center_x':.5, 'center_y':.5}
	size_hint: None , None
	size: 400, 450
	canvas.before:
		Color:
			rgba: utils.get_color_from_hex('#26283A')
		RoundedRectangle:
			pos: self.pos
			size: self.size
			radius: [25, 25, 25, 25]
    MDFillRoundFlatButton:
        id: connect_button
        text:'Connect'
        theme_text_color: 'Custom'
        text_color: utils.get_color_from_hex('#26283A')
        font_size: 16
        pos_hint: {'center_x':.5, 'center_y':.15}
        size_hint_x: .4
        on_release: root.connect_press()

고정 레이아웃을 하나 지정한 뒤, 거기의 상대 사이즈로 맞춰놓으면 버튼 크기를 지정할 수 있다.

🔗 DeepMom Github

0개의 댓글