Don't you be too stubborn when you trust your choice is correct? 

If so, how do you know whether that choice must be correct? 

Then I do?




저작자 표시
신고

최근 사용자 경험은 소프트웨어에 있어서 매우 중요하다. 기능적으로 유사한 소프트웨어가 많기 때문에 더 세련된 디자인과 사용하기 편한, 즉 사용자 경험이 보다 뛰어난 소프트웨어가 사용자에게 매력을 더 어필할 수 있다. 물론, 빠르고 부드러운 비주얼 인터렉션은 당연지사이다. 앱 개발에 있어서 UI는 결코 사소한 요소에 해당되지 않는다. 앱 개발자는 보다 쉽고 빠르게 앱을 구현하기 위해 뛰어난 UI 프레임워크를 선호한다.

UI 프레임워크는 앱 개발자가 쉽고 빠르게 앱 화면에 UI를 배치하고 사용자와 앱 간의 상호작용을 수행할 수 있도록 도와준다. UI 프레임워크는 고성능의 화려한 비주얼 효과를 제공하기 위한 뛰어난 그래픽스 처리 엔진은 물론, 사용자 앱의 주 로직과 UI 처리 로직 간의 자연스러운 통합을 위한 메인루프(main loop)와 같은 핵심 로직도 제공한다. 게다가, 보다 쉬운 앱 개발을 위해 프로그래밍 인터페이스는 더욱 더 정교하게 설계되어 제공된다.

UI 프레임워크를 이해하는 가장 단순한 방법은 직접 앱 개발자가 되어서 필요한 기능을 사용해 보는 것이다. 이번 장에서는 앱 개발 관점에서 직접 앱을 구현하면서 UI 프레임워크의 기본 기능에 대해 짚어보고 한편으로는 프레임워크 개발 관점에서 어떻게 그러한 기능을 제공할 수 있는지 알아보고자 한다. 만약 여러분이 앱을 개발해 본 적이 없다면, 어쩌면 이번 장은 여러분에게 매우 적합한 장이 될 것이라고 생각한다.


1. 이번 장 목표

이번 장을 통해 다음 사항을 학습해 보자.

  • UI의 기본 원소와 UI 컨트롤에 대해 이해한다.
  • 앱의 UI를 생성하는 기본 메커니즘을 살펴본다.
  • 버튼과 이를 다루는 이벤트 사용 메커니즘을 배운다.
  • 앱의 기본 구조 및 라이프사이클를 이해한다.
  • UI 엔진의 개념과 메인루프에 대해서 살펴본다.


  • 2. UI의 기본 원소

    본격적인 시작에 앞서 우리는 UI의 기본 원소를 먼저 살펴볼 것이다. UI의 기본 원소는 앱 UI를 구성하는데 있어서 가장 기본이 되는 원소에 해당한다. 다음과 같은 앱의 UI를 구성하기 위해서 우리는 어떤 리소스가 필요할까?

    그림 1: 앱 화면 예

    그림 1은 우리가 잘 알고 있는 크롬(Chrome) 브라우저의 구글 페이지 화면이다. 크롬 역시 하나의 UI 앱으로 간주할 수 있는데 얼핏 보기에 UI가 복잡해 보이지만 사실 UI를 조목조목 뜯어보면 결국에는 이미지와 텍스트 두 요소로 구성되어 있음을 확인할 수 있다.


    그림 2: 앱 화면을 구성하는 기본 원소

    앱의 화면을 구성하는 기본 원소는 화면을 화려하게 장식해 주는 이미지와 문맥 정보를 전달하는 텍스트 두 가지로 축약해 볼 수 있다. 다소 기능이 원시적일지라도, 사실 이 두 기능만 존재한다면 어떠한 종류의 앱 화면도 정확히 구현할 수 있다. 이를 증명하기 위해 코드 1은 그림 1 중에서 검색 상자(Search Box)를 어떻게 구현할 수 있는지를 보여준다.

    그림 3: 구글의 검색 상자

    //검색 상자
    UIImage searchBox = new UIImage();             //이미지 생성
    searchBox.open(“./../SearchBox.png”);          //이미지 리소스
    searchBox.move(80, 300);                       //이미지 위치
    searchBox.resize(350, 50);                     //이미지 크기
    searchBox.show();                              //이미지 출력하기    
    
    //검색 상자 가이드 텍스트
    UIText guideText = new UIText();               //텍스트 생성
    guideText.text(“Search Google or type URL”);   //텍스트 설정
    guideText.color(“lightgray”);                  //텍스트 색상
    guideText.move(90, 310);                       //텍스트 위치
    guideText.resize(130, 40);                     //텍스트 크기
    guideText.show();                              //텍스트 출력하기
    
    //검색 상자 음성 아이콘
    UIImage banner = new UIImage();                //이미지 생성
    banner.open(“./../VoiceRecognition.png”);      //이미지 리소스
    banner.move(400, 310);                         //이미지 위치
    banner.resize(20, 25);                         //이미지 크기
    banner.show();                                 //이미지 출력하기
    

    코드 1: 앱 화면 구성 예

    코드 1처럼 이미지와 텍스트를 이용한다면 그림 1의 다른 부분도 똑같이 구현할 수 있다.

    누군가는 텍스트마저도 미리 준비된 이미지로 대처할 수 있지 않을까 생각할 수도 있다. 틀린 생각은 아니지만 언어, 폰트(Font) 등 시스템 설정에 맞게 텍스트가 유연하게 변경되기 위해서는 텍스트를 이미지로 출력하는 것은 여러 측면에서 한계가 많다.

    안드로이드, IOS, 윈도우와 같은 최신 플랫폼에서 제공하는 UI 기능을 살펴보면, 사실 앱 개발자는 이와 같은 원시적인 방법으로 UI를 구현하는 것은 상상할 수 없다. 사실 모든 플랫폼의 UI 프레임워크는 화면을 구성하는 공통된 기능과 특성을 UI 컨트롤(또는 위젯)로서 정의하여 제공하며 프로그래밍 관점에서 UI 컨트롤은 UI 객체(UI Object)로서 통용된다.

    그림 4: 다양한 종류의 UI 컨트롤

    앱 개발자는 UI 프레임워크에서 제공하는 UI 컨트롤를 조합하여 보다 쉽고 빠르게 다양한 앱 화면을 구축할 수 있다.

    //검색 상자 UI 컨트롤
    UISearchBox searchBox = new UISearchBox();             //검색 상자 생성
    searchBox.text(“Search Google or type URL”);           //가이드 텍스트 설정
    searchBox.icon(“./../VoiceRecognition.png”);           //음성 아이콘 설정
    searchBox.move(80, 300);                               //검색 상자 위치
    searchBox.resize(350, 50);                             //검색 상자 크기
    searchBox.show();                                      //검색 상자 출력하기
    

    코드 2: UI 컨트롤을 이용한 앱 화면 구성

    같은 목적을 수행하는 코드 1과 코드 2를 비교해 보면 구현이 얼마나 간단해 지는지 알 수 있다. 하물며 UI 컨트롤은 화면을 출력하는 기능 뿐만 아니라, UI 컨트롤과 사용자와의 상호작용을 위한 기능 동작까지 제공한다. 검색 상자에 사용자가 입력한 텍스트가 실시간으로 출력되는 기능을 생각해본다면, 앞서 이미지와 텍스트를 이용한 원시적 구현 방식과는 비교할 수 없을 정도로 구현 분량이 축소된다.

    사용자로부터 단순한 클릭 입력을 받는 버튼을 예로 들어보자. 버튼은 사용자의 클릭 이벤트를 전달받고 앱에게 그 상태를 전달한다. 버튼은 클릭이라는 이벤트를 가시적으로 표현하기 위해 클릭에 대한 상태 정의(Normal 상태, Press 상태) 및 각 상태별 이미지를 출력할 수 있어야 한다. 만약 상태 전이간 애니메이션까지 존재한다면 버튼의 구현은 훨씬 더 복잡하다.

    그림 5: 버튼의 클릭 상태 전이

    원시적 방식을 이용하여 버튼의 기능을 모두 구현한다면, 구현량은 물론 개발 난이도까지 급상승한다. 기본적으로 UI 컨트롤은 앱이 작성해야 할 코드 분량을 줄여주는 것은 물론, 룩앤필(Look & Feel)을 갖춘 테마 특성, 애니메이션, 이벤트, 예상치 못한 사용자 입력 처리 등 생각보다 많은 작업을 대신 정의하고 구현한다.

    일반적으로 UI 컨트롤의 동작 및 룩앤필의 특성은 프레임워크에서 정의한 테마에 따라 다르다. 달리 말하면, 앱이 어떤 UI 프레임워크를 기반으로 작성되었느냐에 따라 앱의 UI 특성은 완전히 달라진다. 게다가, 각 UI 프레임워크가 갖춘 다양한 테마에 따라 앱의 그래픽 출력 결과도 완전히 달라질 수 있다.

    그림 6: 테마에 따른 UI 컨트롤의 룩앤필 차이

    그림 7: 테마에 따른 동일 앱 UI의 룩앤필 차이

    만약 제공되는 UI 컨트롤 중 디자인한 앱과 부합하지 않거나 또는 필요한 UI 컨트롤이 존재하지 않는다면 코드 1과 같은 방식으로 이미지와 텍스트를 가지고 화면을 직접 구성할 수 있다. 프로토타입, 테스트 목적 등 앱의 완성도 및 호환성이 그다지 중요하지 않는 상황이라면 충분히 고려할 만하다. 그렇지 않다면, UI 컨트롤의 테마를 직접 수정하거나 새로 작성하는 방법을 고민해 볼 수 있다. 일반적으로 UI 프레임워크에서 제공하는 UI 컨트롤 테마 커스터마이징 기능은 기본 사항에 해당된다.


    3. 첫 번째 예제: 버튼과 이벤트 핸들링

    앞 절에서는 우리는 UI 컨트롤이 무엇인지 대략적으로 살펴보았다. 이번 절에서 우리는 지구 상에서 가장 단순한 UI 컨트롤을 하나 이용해 볼 예정이다. 이 예제로부터 우리는 UI 컨트롤과 앱간의 상호작용을 어떤 식으로 구현할 수 있는지 살펴볼 것이다.

    그림 8: 버튼

    버튼(Button)은 어떤 UI 프레임워크를 막론하고 동일하게 제공되는 UI 의 기본 형태 중 하나이다. 사용자는 버튼을 클릭함으로써 앱에게 신호를 전달한다. 앱은 버튼이 사용자로 하여금 클릭되었는지 신호를 감지할 수 있으며 그 신호에 상응하는 어떤 적절한 동작을 취할 수 있도록 구현을 한다. 이 예제에서는 버튼이 선택되었을 때 버튼 메세지를 변경한다.

    그림 9: 버튼 선택 메세지

    버튼을 앱 화면에 배치하기 위해 앱 개발자는 대략 다음과 같은 버튼 생성 코드를 작성할 수 있을 것이다.

    UIButton myBtn = new UIButton();    //버튼 생성
    myBtn.text(“My Button”);            //버튼의 출력될 텍스트
    myBtn.move(50, 50);                 //버튼 위치
    myBtn.resize(100, 100);             //버튼 크기
    myBtn.show();                       //화면에 나타내기
    

    코드 3: 버튼 생성

    화면에 버튼을 추가했지만, 사용자 입력 신호는 어떻게 처리할 수 있을까? 프로그래밍 관점에서 보면, 이벤트 리스너(Event Listener) 혹은 콜백 함수(Callback Function)와 같은 메커니즘을 이용하면 가능하다. 버튼이 클릭된 시점에 앱 개발자가 등록한 함수가 호출된다면, 앱 개발자는 해당 함수 내에서 원하는 동작을 추가 구현할 수 있다.

    //버튼에 clicked 이벤트 핸들링을 추가한다.
    myBtn.addEventCb(UIButton.CLICKED,
                     //CLICKED 이벤트 발생 시 아래 코드가 수행된다.
                     lambda(UIObject obj)
                     {
                         myBtn.setText(“Button Pressed”);
                     }
                    );
    

    코드 4: 클릭 이벤트 등록

    앱 개발자는 해당 이벤트 함수가 어떤 과정을 통해 호출되는지 전혀 알지 못한다. 하지만, 버튼이 클릭되었을 때 반드시 이벤트 함수가 불린다는 사실을 보장받아야 한다.

    눈치챘겠지만, UI 컨트롤을 잘 활용하기 위해서는 앱 개발자는 해당 컨트롤이 제공하는 이벤트의 종류를 이해할 수 있어야 한다. 버튼의 경우 “클릭” 이벤트를 제공하지만, 추가로 “눌림(PRESSED)”, “눌림 해제(UNPRESSED)”, “롱프레스(LONGPRESSED)” 와 같은 다른 이벤트도 제공할 수 있다. 앱 개발자는 각 이벤트에 대한 명세 내용을 정확히 이해하고 사용자 시나리오에 맞게 그 기능을 잘 활용할 수 있다.

    myBtn.addEventCb(UIButton.PRESSED,
                     //PRESSED 이벤트 발생 시 아래 코드가 수행된다.
                     lambda(UIObject obj) { ... }
                    );
    
    myBtn.addEventCb(UIButton.UNPRESSED,
                     //UNPRESSED 이벤트 발생 시 아래 코드가 수행된다.
                     lambda(UIObject obj) { ... }
                    );
    
    myBtn.addEventCb(UIButton.LONGPRESSED,
                     //LONGPRESSED 이벤트 발생 시 아래 코드가 수행된다.
                     lambda(UIObject obj) { ... }
                    );
    

    코드 5: 버튼에 여러 이벤트 등록

    한편, UI 프레임워크에서는 각 UI 컨트롤에 대한 정확한 동작 정의는 물론, 해당 기능을 앱 개발자가 이해할 수 있도록 명확한 인터페이스 설계 및 문서화가 필수적이다.

    /**
     * @defgroup UIButton Button
     * @ingroup UIFramework
     *
     * This is a push-button. Press it and run some function. It can contain
     * a simple label and icon object and it also has an autorepeat feature.
     *
     * This widget inherits from the @ref Layout one, so that all the
     * functions acting on it also work for button objects.
     * …
     * This control emits the following signals, besides the ones sent from Layout.
     * @li CLICKED: the user clicked the button (press/release).
     * @li PRESSED: button was pressed.
     * @li UNPRESSED: button was released after being pressed.
     * @li LONGPRESSED: the user pressed the button without releasing it.
     * ...
    

    코드 6: Doxygen 형식을 따른 버튼 문서화 예

    UI 프레임워크는 서로 다른 UI 컨트롤일지라도 유사한 동작의 경우 동일한 인터페이스를 갖추는 것이 앱 개발자로 하여금 보다 빠른 이해에 도움이 된다. 앱 개발자는 하나를 배움으로써 다른 UI 컨트롤의 동작도 유추할 수 있을 것이다.

    UIRadio myRadio = new UIRadio();    //라디오 생성
     
    //앱 개발자는 라디오를 잘 모를지라도, CLICKED 동작이 무얼 의미하는지는 유추할 수 있다.
    myRadio.addEventCb(UIRadio.CLICKED,
                       //CLICKED 이벤트 발생 시 아래 코드가 수행된다.
                       lambda(UIObject obj) { ... }
                      );
    

    코드 7: 동일한 이벤트 인터페이스의 예

    하나의 UI 컨트롤에 이벤트 처리를 추가함에 있어서 콜백 함수의 복수 등록은 충분히 가능하다. 버튼이 클릭되었을 때, 한편으로는 메세지를 출력하고 한편으로는 이미지를 출력할 수 있을 것이다. 두 동작을 하나의 함수 내에서 처리할 수도 있지만 서로 다른 개별 동작은 코드 관점에서 분리하여 작성하는 것이 코드 복잡도 측면에서 더 낫다. 결과적으로, 이벤트 처리도 복수 등록이 가능하도록 인터페이스를 설계해야 앱 개발이 더욱 편리하다.

    myBtn.addEventCb(UIButton.CLICKED,
                     //CLICKED 이벤트 발생 시 아래 코드가 수행된다.
                     lambda(UIObject obj) {    //메세지를 출력한다. }
                    );
    
    myBtn.addEventCb(UIButton.CLICKED,
                     //CLICKED 이벤트 발생 시 아래 코드 역시 수행된다.
                     lambda(UIObject obj) {    //이미지를 출력한다. }
                    );
    

    코드 8: 이벤트 중복 등록 예

    이벤트가 중복 등록된 경우, 어느 이벤트 함수가 선호출되어야 하는지 UI 프레임워크는 명확한 정책을 제시해야 한다. 앱 로직의 순서가 완전히 달라질 수도 있기 때문이다. 나중에 등록된 함수가 먼저 호출되는 경우가 일반적이지만, 상황에 따라 이러한 순서를 임의로 결정할 수 있는 이벤트 함수가 필요할 수도 있다.

    myBtn.addEventCb(UIButton.CLICKED, 
                     lambda(UIObject obj) { ... },
                     2,    //우선순위: 2
                    );
    
    //위 이벤트보다 우선순위가 더 높기 때문에 여기 등록된 이벤트 함수가 먼저 불린다.
    myBtn.addEventCb(UIButton.CLICKED,
                     lambda(UIObject obj) { ... },
                     1,    //우선 순위: 1
                    );
    

    코드 9: 이벤트 우선 순위 지정

    사실, 이벤트 함수의 순서에 의존하는 앱의 이벤트 처리 로직이 존재한다면 개선 여지가 있는지 다시 검토해 보아야 한다. 예로, 클릭 이벤트에 복수 이벤트 처리가 등록된 경우 각 이벤트 처리는 서로 독립적이어야 앱 로직의 복잡도를 줄일 수 있다. 각 이벤트 처리간 의존성이 존재한다면 이는 하나의 이벤트 처리로 합치는 것이 더 바람직하다.

    반면, 어떤 상황에서는 동작을 취소하기 위해 등록한 이벤트를 제거해야 할 수도 있다.

    ...
    //이벤트 콜백을 등록한다. 이후 이벤트 콜백을 해지하기 위해 콜백 핸들을 따로 보유한다.
    UIEventCb myEventCb = myBtn.addEventCb(UIButton.CLICKED, ...);
    ...
    //앞서 등록한 이벤트 콜백을 해지한다.
    myBtn.delEventCb(myEventCb);
    ...
    

    코드 10: 등록한 이벤트 삭제

    사용자 조건이 만족하기 전까지 앱의 특정 기능이 비활성화되어 있는 경우도 있다. 이 경우 해당 기능을 트리거하는 UI 컨트롤이 비활성화 상태로 존재해야 한다. 해당 컨트롤을 비활성화하는 기능을 제공하면 사용자로 하여금 해당 기능을 사용할 수 없다는 점을 인지할 수 있게 도와준다. 비활성화된 UI 컨트롤은 기본 동작을 수행하지 않음은 물론 관련 이벤트 역시 발생하지 않는다.

    그림 10: 버튼 비활성화

    myBtn.disable();    //버튼을 비활성화한다. 다시 활성화하려면 enable()을 호출하자.
    

    코드 11: 버튼 비활성화

    비활성화된 버튼은 UI 외양이 달라졌을 뿐만 아니라, 클릭과 같은 기본 기능 역시 동작하지 않는다.


    4. 앱 기본 구조 및 동작 분석

    앞 절에서 우리는 UI 컨트롤을 사용하여 앱과 사용자간의 상호작용하는 방법을 어떤 방식으로 구현할 수 있는지 간단한 예제를 통해 살펴보았다. 이번 절에서는 UI 컨트롤을 사용하는 앱이 UI 프레임워크와 어떻게 연동되어 동작할 수 있는지 간략히 알아보고자 한다. 이번 절 학습을 통해 UI 프레임워크와 앱 코드간의 기본 연동 방식 및 그 원리를 이해할 수 있을 것이다.

    C, C++, 자바 등 현대의 대표적인 프로그래밍 언어에서는 우리는 main() 함수에서 프로그램이 시작됨을 알고 있다. UI 컨트롤의 기능을 사용하기 위해 일반적으로 앱 프로세스는 UI 프레임워크의 엔진을 초기화하고 가동하는 작업을 수행해야 한다. 엔진이라고 하면, UI 컨트롤이 동작하는 핵심 기능을 수행하는 모듈이라고 볼 수 있다. 앱 개발에 있어서 엔진의 내부 동작 원리를 모를지라도 큰 문제는 안되지만, 앱 개발자가 엔진의 동작 원리를 이해한다면 문제 해결 및 앱 최적화 측면에서 큰 도움이 될 수는 있다. 반면, 사용하기 좋은 UI 프레임워크일수록 앱 개발자는 엔진 내부의 동작 방식에 영향을 받지 않고 조금 더 자유로운 방식으로 쉽고 빠르게 앱을 개발할 수 있어야 한다. 일반적으로 UI 엔진은 앱의 로직과는 별개로 앱의 그래픽 출력을 위해 복잡한 연산 및 로직을 무대 뒤에서 열심히 수행한다.

    /*
     * UIEngine은 UI 컨트롤의 기능을 구동하는 모듈이다.
     * UIEngine이라는 명칭은 임의로 정함. 
     * 실제로는 UI 프레임워크 및 모듈의 실제 이름 등의 더 적절한 이름을 요구한다.
    */
    main()
    {
        UIEngine.init();    //엔진 초기화
        UIEngine.run();     //엔진 가동. 내부적으로 메인루프(MainLoop)가 가동한다.
        UIEngine.term();    //엔진 종료
    }
    

    코드 12: UI 엔진 초기화 및 가동

    코드 12는 UIEngine을 초기화, 가동, 종료하는 코드이다. init()에서는 UI 엔진이 사용하는 리소스를 불러오고 엔진이 적절히 구동되기 위한 준비 작업을 수행한다. run()에서는 준비된 리소스를 가지고 실제 엔진을 가동한다. run()이 호출된 이후에는 엔진이 종료 요청을 받기 전까지 run()은 메인루프를 통해 계속 가동되야만 한다. 만약 run() 메서드가 종료된다면 앱 프로세스 역시 main() 함수와 함께 종료될 것이다. 이 경우, 앱의 UI는 화면에서 지속될 수가 없다. run() 내부적으로 메인루프가 가동되면서 매 루프마다 지정된 어떤 작업을 수행한다. term()는 run()이 끝난 앱의 종료 시점에 호출되며 엔진에서 사용한 리소스를 모두 정리하는 작업을 수행한다. 엔진은 사용자가 사용한 UI 컨트롤 등의 리소스가 해제되어 있지 않으면 term() 호출 시점에 내부적으로 알아서 정리해 줄 수 있다.

    일부 플랫폼에서 코드 12와 같은 UI 엔진을 초기화, 가동, 종료하는 호출이 없다고 놀랄 필요가 없다. 일반적으로 앱이 구동되는 여러 플랫폼에서는 UI 엔진을 더욱 추상화한다. 플랫폼은 UI 엔진의 존재를 감추고 앱 개발자로 하여금 필요한 UI 컨트롤을 바로 호출할 수 있도록 코드 템플릿을 제공하거나 애플리케이션 프레임워크를 좀 더 보완하여 제공한다. 결과적으로, main() 함수에서 직접적으로 UI 엔진을 초기화, 가동, 종료하는 코드는 사용되지 않을 가능성이 크다. 일반적으로 앱은 UI 엔진 뿐만 아니라 사운드, 네트워크 등의 기타 라이브러리 및 엔진 그리고 서비스를 동시에 사용하기 때문에 엔진의 초기화 작업 및 가동은 앱 프레임워크 내부에서 일괄적으로 처리해 줄 수 있다. 코드 13은 이러한 부분을 추상화한 UIApp 클래스를 이용하는 예이다.

    main()
    {
        UIApp.init();
        UIApp.run();
        UIApp.term();
    }
    
    /*
     * 애플리케이션 프레임워크에서 제공하는 기능이며 클래스 및 메서드 이름은 임의로 정했다.
    */
    UIApp
    {
        //UI 엔진 뿐만 아니라 여러 라이브러리 및 서비스를 초기화 한다.
        init()
        {
            ...
            UIEngine.init();   
            ...
        }
    
        /* UIEngine.run() 전후로 여러 기능이 수행될 수 있지만 UI 엔진을 가동하는 것이
           핵심이다. */
        run()
        {
            ...
            UIEngine.run();
            ...
        }
    
        //마찬가지로 UI 엔진 뿐만 아니라 여러 라이브러리 및 서비스를 종료한다.
        term()
        {
            ...
            UIEngine.term();
            ...
        }
    }
    

    코드 13: UIApp 클래스를 활용한 main() 작성

    UIApp.run()까지 잘 호출되었다고 가정하자. 앱이 처음 가동되면 앱은 첫 화면으로 무언가를 보여주어야 한다. 2.3절에서 살펴본 버튼 예제와 같은 방식으로 여러 UI 컨트롤을 배치한다면 화면 구성이 가능하다. 다만, 그전에 우리는 UI 컨트롤을 배치할 앱의 윈도우(Window)를 하나 생성해야 한다. 사실, 윈도우는 플랫폼마다 그 특성이 다르긴 한데 안드로이드의 경우에는 하나의 앱이 여러 뷰(View)를 보유할 수 있으며 각 뷰다 윈도우가 할당되는 반면, MS 윈도우나 리눅스의 전통적인 X Window 시스템에서는 일반적으로 하나의 앱이 하나의 윈도우를 보유하며 윈도우 내에서 앱이 마음대로 뷰를 구성한다. 하지만, 지금은 윈도우가 디바이스 화면에서 앱이 출력될 위치 및 크기를 결정하는 출력 영역 정도로 이해해도 좋다. 일반적인 데스크탑 환경을 이용해 보았다면 윈도우의 개념 이해는 크게 어렵지 않을 것이다.

    그림 11: 데스크탑 환경의 윈도우

    앱의 첫 화면을 위해 윈도우를 생성하고 UI 컨트롤을 배치하는 작업은 run()의 무한루프가 본격적으로 수행되기 이전에 수행되어야 할 것이다. 그렇다면, UIApp.init(), UIApp.run() 사이에서 수행하면 될까? UIEngine을 직접 사용한다면 init()과 run() 사이가 맞지만 UIApp 기반에서는 init()과 run() 내에서 여러 추가적인 작업들이 수행될 수 있기 때문에 UIApp 클래스는 사용자에게 UI를 생성할 시점을 알려주는 것이 더 명확하다. 사실 이 부분은 앱의 라이프사이클(Life-Cycle)과도 관련이 있는데, 모바일 환경의 앱의 경우 리소스 제약이나 사용 환경의 특성으로 인해 앱이 시스템의 지배를 더 많이 받기 때문이다. 그렇기 때문에, 애플리케이션 프레임워크는 앱 개발자로 하여금 앱의 라이프사이클에 맞는 동작을 수행할 수 있는 인터페이스를 기본적으로 제공한다. 앱이 UI를 구성해야 하는 시점 역시 이러한 라이프사이클의 일부분으로서 존재할 수 있다.

    그림 12: 앱 라이프사이클 모델

    UIWindow myWnd;     //윈도우 객체를 보관할 인스턴스
    
    /*
     * MyAppLifeCycle은 AppLifeCycle을 상속해서 구현한다.
     * AppLifeCycle은 앱의 라이프사이클 인터페이스를 제공한다.
     * 이 예제에서는 대표적인 4개의 상태만 언급한다.
    */
    MyAppLifeCycle : AppLifeCycle
    {
        /*
         * create()는 앱이 최초 생성될 경우 호출된다.
         * 앱 개발자는 여기서 첫 화면을 구성한다.
        */
        create() override
        {
            myWnd = new Window();             //윈도우 생성
            myWnd.title(“My Window”);         //윈도우의 타이틀
            myWnd.resize(400, 400);           //윈도우 크기
    
            //윈도우 생성 후 필요한 UI 컨트롤을 추가적으로 생성한다...
    
            UIButton myBtn = new Button(myWnd);    //myWnd의 버튼을 생성한다.
            myBtn.text(“Exit”);                    //버튼의 출력될 텍스트
            myBtn.move(50, 50);                    //버튼 위치
            myBtn.resize(100, 100);                //버튼 크기
            myBtn.show();                          //화면에 나타내기
            myBtn.addEventCb(UIButton.CLICKED,
                             lambda(UIObject obj)
                             {
                                 UIApp.exit();    //버튼 클릭시 앱을 바로 종료한다.
                             },
                            );
        }
    
        /*
         * destroy()는 앱 종료를 요청받을 경우 호출된다.
         * 사용자에 의해 윈도우의 종료 버튼이 눌리는 경우 등이 이에 해당된다.
         * 앱 관리자에 의해 강제 종료되는 경우도 해당된다.
         * 여기서 앱은 사용한 리소스를 정리한다.
        */
        destroy() override
        {
            /* 앱이 종료될 시 생성한 윈도우를 제거해준다.
               UIEngine의 term()에 의해 자동으로 수행될 수 있으므로 사실 필수는 아니다. */
            myWnd = null;
        }
    
        /*
         * pause()는 앱이 백그라운드(Background)로 전환되거나 일시정지될 경우 호출된다.
         * 윈도우 최소화, 창 전환 등의 경우에 해당된다.
         * 갑자기 걸려온 전화로 전화 앱이 구동되는 경우에도 해당된다.
         * 애니메이션 등 불필요한 과도한 출력 처리는 여기서 정지시키는 것이 좋다.
        */
        pause() override
        {
            myWnd.hide();    //윈도우를 숨긴다... 꼭 필요할까?
        }
    
        /*
         * resume()는 앱이 포어그라운드(Foreground)로 다시 전환되는 경우 호출된다.
         * 앱이 최초 생성된 후 가동될 경우에도 해당된다.
         * 일시정지했던 작업이 있다면 여기서 다시 재개시킨다.
        */
        resume() override
        {
            myWnd.show();    //윈도우를 나타낸다… 꼭 필요할까?
        }
    }
    
    main()
    {
        //라이프사이클 정보를 UIApp를 통해 앱 프레임워크 코어쪽으로 전달한다.
        MyAppLifeCycle lifeCycle = new MyAppLifeCycle();
        UIApp.init(lifeCycle);
        UIApp.run();
        UIApp.term();
    }
    

    코드 13:앱 라이프사이클에 따른 UI 생성

    주석을 통해 코드에 대한 전반적인 이해는 가능할 것으로 기대한다. 몇 부분만 추가 설명하자면, 우선 24줄을 보면 버튼 생성 시 윈도우 객체를 전달하는 것을 확인할 수 있을 것이다. 생성하는 버튼이 어느 윈도우에서 출력되어야 하는 버튼인지를 지정하기 위해 추가한 사항이다. 하나의 앱이 여러 개의 윈도우를 동시에 보유할 수 있다는 가정하에 필요하다고 판단하여 추가하였다. 두 번째로 32줄을 보면 UIApp.exit()를 통해 앱 종료를 요청하는 작업을 볼 수 있는데 실제로 UIApp.exit()는 UIEngine으로 하여금 메인루프를 중단하는 작업을 요청할 것이다.

    UIApp.exit()
    {
        ...
        UIEngine.stop();     //UIEngine의 메인루프를 중단한다.
        ...
    }
    

    코드 14: UIApp.exit()의 코드

    마지막으로, pause()와 resume()에서 윈도우를 숨기고 나타내는 작업을 앱 개발자가 직접 수행하고 있는데 과연 이러한 호출이 필요할까 의문일 수도 있다. 만약 앱 개발자가 직접 호출을 수행해야 한다면, Pause 시 악의적으로 윈도우를 숨기지 않을 수도 있기 때문에 사실 이 부분은 앱의 역할이라기 보다는 윈도우를 관리하는 윈도우 관리자(Window Manager)가 수행해야 할 작업이 더 맞을 듯 보인다. 아직까지는 윈도우 관리자에 대해서 설명하지 않았으므로 윈도우 관리자가 다소 생소한 독자들을 위해 앱 개발자가 직접 호출하도록 코드를 남겨두었다.

    실제로 플랫폼에서 제공하는 앱 사이클의 정의 및 시나리오는 본 예제보다 다소 복잡할 수 있다. 여기서는 라이프사이클 모델을 최대한 단순화하여 이 정도로만 언급하고 넘어가도록 한다. 다음 그림은 실제 윈도우10 UWP 앱의 라이프사이클을 보여준다.

    그림 13: 윈도우10 UWP(Universal Windows Platform) 앱의 라이프사이클


    마지막으로, 이쯤해서 UIEngine의 메인루프를 도식화해보자. 실제 메인루프 내에서 수행해야 하는 작업들은 훨씬 더 복잡할테지만 지금까지는 대략 다음과 같은 작업을 수행할 것이다.

    그림 14: UIEngine 메인루프


    UIEngine의 메인루프는 매 루프마다 사용자의 입력을 비롯한 새로운 이벤트가 발생했는지 확인한 후, 이벤트가 존재한다면 이벤트를 처리한다. 그리고 이벤트에 의해 UI 컨트롤과 같은 UI 객체에 변화가 발생했는지 확인한 후, 변화가 있다면 이를 반영하여 새로운 렌더링(Rendering) 작업을 수행하고 최종적으로 앱의 화면을 재갱신한다. 이러한 절차의 작업은 UIEngine.stop()이 호출될 때까지 끊임없이 반복된다.


    5. 정리하기

    이상으로, 우리는 앱 개발에 필요한 UI 프레임워크의 기본 기능과 그 개념을 간략하게 살펴보았다. UI를 구성하는데 있어서 가장 원시적인 방법으로 이미지와 텍스트를 이용하는 방법이 있으며 UI 컨트롤을 이용하면 더욱 쉽고 빠른 UI 개발이 가능함을 알 수 있었다. UI 컨트롤은 단순히 그래픽 출력 뿐만 아니라 사용자와 앱간의 상호작용이 가능한 기능을 제공하며 이러한 기능을 위해 앱 개발자는 UI 컨트롤이 제공하는 이벤트를 처리하는 방법이 필요함을 알 수 있었다. 앱은 기본적으로 윈도우를 하나 생성하여 출력 영역을 확보하고 윈도우 안에 UI를 구성함을 알 수 있었고 앱 라이프사이클에 맞춰 UI 생성 및 조작이 필요함을 알 수 있었다. 마지막으로 이러한 UI가 가동될 수 있는 UI 엔진과 메인루프에 대한 기본 개념도 함께 살펴보았다.


    저작자 표시 비영리 변경 금지
    신고

    When I was a young boy, I was totally into a racing genre video games. Back then, I really loved to play Need for Speed(NFS) Porsche Unleashed title which was the best one among the early 20s racing games.


    In the game, I just got into a Porsche Boxster, I could drive it here and there as I wished. And then found out Boxster is a very fancy, gorgeous and luxurious car. After that, I dreamed the moment that I'm driving the car in my real life.


    About 15 years later, I ordered a real Porsche Boxster and had to wait for about 6 months to get it in my hands from Germany. That was the moment I've waited for a long long time since my childhood. Wow, but I had to pass it to an other guy because of unexpected my work. I had to leave South Korea for 1 year. Sucks! 


    These days I just wait for the moment going back to South Korea and drive Boxster.


    I must order it again.


    Sometimes, we need a big shot which makes us more energetic.





    저작자 표시
    신고


    Material: 4B&H pencil, Sketchbook

    저작자 표시
    신고

    Inlist, which is a sort of the Linked List, is an optimized linked list for the data structure and it's behavior performance. The key point of Inlist is, it embeds a user data in its own data structure, does not have a user data separately. Let's see an example quickly.

    Firstly, let me describe a normal Linked List data structure. We can suppose that data structure would be provided with an API like a useful library.

    //A list node information. data field points actual user data.
    struct ListNode {
        ListNode *prev;
        ListNode *next;
        void *data;
    };
    //A data structure for accessing list nodes. struct List { ListNode *head; ListNode *last; };

    It's just a common Linked List data structure. No problems even though no explanation. See next.

    //Create a new list.
    List* list_create() {
        return (List*) calloc(1, sizeof(List));
    };
    //Append a new item(node) in a list. bool list_append_item(List* list, void *data ) { if (list == null) return false; //Create a new node. ListNode *node = (ListNode*) calloc(1, sizeof(ListNode)); if (node == null) return false; node->data = data; //In case of the first node. if (list->last == null) { list->head = list->last = node; return true; } //Append a new node in the list. ListNode *last = list->last; last->next = node; node->prev = last; list->last = node; return true; }


    list_create() generates a list and the other one appends a new node in the list. I believe you already know Linked List so we won't look at the above code in detail. (Just in case, you can easily find a Linked List concept by googling.)

    So far, it looks nice. We can provide a sort of free functions with regard to the above additionally but I'd like to skip them because they are actually at outside of the stake.

    Then, we can suppose a user uses our functions in this scenario.

    //User data structure
    struct UserData {
        int idx;
        int val;
    };
    
    void main() {
    
        List *list = list_create(); 
     
        //Set up a list.
    
        //Create arbitrary 100 items. Skiping here, but create_userdata() creates a UserData data and returns it.
        for (int idx = 0; idx < 100; ++idx) {       
           UserData *usrdat = create_userdata(idx, idx * 100);
           list_append_item(list, usrdat);
        }
     
        //Verify that list is correct or not.
    
        //It would be better if we provide a function, list_foreach(), to iterate a list...
        ListNode *node = null;
        UserData *usrdat = null;
    
        list_foreach(list, node,  usrdat, UserData) {
            if (usrdat)
                printf("%d %d\n", usrdat->idx, usrdat->val );
        }
    
        //Skip the free sequence..
    }
    

    Seems very well. But for some people who are likely to ask me how to implement list_foreach(), I'm adding the function code here.

    #define list_foreach( list, node, usrdat, DATA_TYPE ) \
        for (node = list->head, usrdat = (DATA_TYPE*) _get_usr_data(node); node; node = _prev_get_next(node), usrdat = (DATA_TYPE*) _get_usr_data(node))
    


    The code won't be the perfect however at least, we can imagine such that code we can define.

    _prev_get_next() is an internal function which returns a next node, _get_user_data() is an internal function which returns user data from a node. Both of them actually are not important, we don't need to waste time by diving them to dig. So let's skip them.

    So far, we've looked a normal Linked List and its peripheral functions usage. Here point we have to notice again is the next sequence that builds up a working Linked List.

    1. Create a list(list_create()).
    2. Create a user data(create_userdata())
    3. After creating a node, store user data in that node(list_append_item()).
    4. Append a new node in the list(list_append_item()).

    By this time, if we see a figure of its structure, it must be looked like this.

    The key point here is, on building the structure, it needs to allocate 2 pieces of fragmented data memory per one item. Plus, every loop, it requires referring pointers, node->data, to access user data.

    Then, let's take a look at the difference with Inlist. Inlist reduces memory allocation count as well as pointer access count by merging Node and UsrData like the next figure.


    Someone may think, it's a piece of cake. If user implements the list manually, they could have implemented it like the above. On the other hands, if you provide the list function to users or implement it internally as a re-usable function for yourself, you are possibly somewhat impressed.

    Now, we understand the concept of the Inlist. Let's start to implement it quickly. I will modify the previous list code and here I will show you the just different parts of the code. Let's take a look at UsrData first.

    #define LISTNODE ListNode node;  //Define for user convenience.
    
    //User data structure
    struct UserData {
        //Firstly, adds a field using that macro.
        LISTNODE;
        int idx;
        int val;
    };
    


    Like above the figure, modified UserData to include ListNode data fields. Next, let's modify List and ListNode.

    struct ListNode {
        ListNode *prev;
        ListNode *next;
        void *data;  //No more use.
    };
    
    struct List {    
        //Nothing changed.
        ListNode *head;
        ListNode *last;
    };
    

    Now, it doesn't need to allocate ListNode but build Linked List via UserData.

    //Append a new  item(node) in the list.
    bool list_append_item(List* list, void *data) {
    
        if (list == null || data == null) return false;
    
        //Create a new node. Not necessary anymore.
        ListNode *node = (ListNode*) calloc(1, sizeof(ListNode));
        if( node == NULL ) return false;
        node->data = data;
    
        //Convert to ListNode.
        //In fact, simply use typename to access list node from user data if it is C++...
        ListNode* node = (ListNode*) data;
    
        //In case of the first node.
        if (list->last == null) {       
            list->head = list->last = data;
            return true;
         }
    
        //Append a new node in the list.
        ListNode *last = list->last;
        last->next = node;
        node->prev = last;
        list->last = node;
    
        return true;
    }
    

    No big changes, but just use void* type data(exactly for UserData) instead of the ListNode* to construct the list.

    Lastly, let's take a look at the code which describes the iterator body of the list.

    #define list_foreach(list, usrdat) \
        for (usrdat = list->head, usrdat; usrdat = _prev_get_next(usrdat))
     
    void main() {
     
        //Create a list and set it up here...
    
        UserData *usrdat = null;
        list_foreach(list, usrdat) {
            printf("%d %d\n", usrdat->idx, usrdat->val);
        }
    }
    


    You might be noticed that it is simpler than previous one because it doesn't need to access a user data from a node anymore. But, of course, it has a con that user needs to declare LISTNODE field in the first line of the UserData structure. But actually it is not big deal. Other than that, we can still provide utility functions for user convenience.

    So far, we've taken a look at the Inlist. It is a compact data structure across the nodes, also it's possible to access to user data faster than normal version. But it must be used when the data is designed to work along with the list. Actually, this Inlist concept was introduced in Enlightenment opensource project years ago. If you are interested in it more than this, you can visit here to look the whole functionalities and its implementation bodies.


    저작자 표시
    신고

    I'm gonna talk about corner cases in my smartphone life in China, a worse than other situations. One of annoying stuff is some apps don't support the copy text function. For instance, Baidu(is a kind of google in China) app toggles a context menu when I do long press on the screen. 



    In the context menu, there is not a text copy item. Sure, I can use other web browsers to avoid this suck (yay, good bye!), But just curious why they don't support the copy text? Actually, from Baidu web-surfing, I could find a bunch of users asked about this similar situations, they seemed be annoying about this strange corner.  


    This is not only the Baidu web app problem but other apps also do. For example, Baidu Map, Didichuxing(a kind of Uber) are one of the essential tools for my daily life in China. Practically, a lot of citizens and tourists also depend on these apps. Let's jump into Baidu map.


    Baidu map


    This is worse. Of course Baidu map is not irreplaceable but still it is a representative map app in China. Somewhat it is better than other foreign companies maps (i.e, google map) because it is specialized in China region and data. When user searches a region, it suggests additional information such as most favorite restaurants, tourists attractions etc. That is not surprising, it is a common feature all over the map apps.


    Point here is, when I searched a good place and think to dig it more, I need to research it using a web browser. That means, I need to copy one of information-address, phone number, store name, region name, etc- and then paste in the search box of the browser. But, It doesn't allow me to copy this information. Oops, you know, Chinese is very difficult to type if you don't know how to read the Chinese characters. First time, it is outrageous. It is very annoying when I could not read them. In the end, I give up searching and go back to the google map because English is better than Chinese to me. Of course, we can use Chinese dictionary then find how to read the Chinese characters and then research it. But I'm sure it is also horribly inconvenient.


    Now, question, why they don't allow us to search the characters? I imagine two scenarios.


     A. intentional purpose (for contents protection)

     B. technical issue.

     C. design problem (Is it considerable? Or just have no idea why they need to support it)


    At least, I'm sure there is not an intentional purpose because that information is not serious data at all. Also, when you use a browser using desktop PC, it still allows user to copy text information. So, it just leads to A or B problems.


    In point of S/W development view, basically software platforms support a text widget or similar UI components which support text copy/paste function in default. If some text part or text view of the application doesn't enable the copy text function, I guess it probably uses an extra component, not the default one, for the text area. I don't like to talk to you it is wrong because we don't understand its background. However, I'm still curious why they don't support it, Is it difficult? Or they just think it is just a trivial function?


    I checked Google map and Naver map(a famous Korean map app) just in case. And then surprisingly I just realized Naver map doesn't support multi-language feature. Also, it doesn't support text copy function for whole text area but does only for some of them. Still, it is inconvenient for me but I think it's better than Baidu map.


    Naver map


    Then how about Google? Impressive! It supports not only multi-language but also text copy function.


    Support Multi-Language


    Copy text information


    If you see the above Google map figure, its copy text UI interface is not a default one. It seems one of the additional or extra ones(just my guess). So I am surprised because it means they intentionally added that feature for this user scenario that I encountered.


    Default copy and paste UI interface


    I'm not one google sucker but a little surprised by google. Because in China, people cannot use Google service but google apps still perfectly works for Chinese. (Of course it needs VPN)  


    Today, we checked one use-case even though a trivial one, but I'd like to say this, every software companies can develop similar software products but their quality and service won't be same. As if it is a kind of this, masterpiece or not. That comes from a difference of software design. When we design a software, do we consider user scenarios enough? Do we design a software for user convenient or just try to copy the prime one? It is clear that, with enough considering user scenarios to make them convenient, users definitely feel your software is better and feel a greater identity of your company.

    저작자 표시
    신고

    I've been living in China since the beginning of this year to study Chinese and its culture. Before came to China actually, I had one major goal-and yet still- to achieve, writing a book. Come to think of it, I've somewhat hesitated to start it because of learning Chinese. It's annoying more than I expected, It took me a lot of time struggling.


    Recently, I visited in one of my favorite websites, Fabien Sanglard, and found out his one of activities is writing a game engine black book and it is just out of corner. Yes! That is what I've dreamed long time ago. Also, I am finally sick and tired of this Chinese life. It informs me a moment to go on to the next stage. 


    So far, I believe I'm familiar with Chinese (a little probably?), and need a new one to simultaneously go with. Writing a book in regard to a S/W techniques and that would be a sort of the UI engine black book. 



    저작자 표시
    신고


    Material: 4B&H pencil, Sketchbook

    저작자 표시
    신고
    Samsung Dex... good to use for web surfing, documentation task, play videos, etc ...
    But I won't use it because of PC games.
    Rather than replacing PC, how about it if it does a notebook(monitor + keyboard + power + s8) type?



    저작자 표시
    신고

    Just bought "VOLGARR THE VIKING", a pc game from the steam. When I looked its screenshots, it seemed like a classic dos game (that's why I downloaded). Its game play is very simple, just go forward and kill monsters by levels, but very interesting and addictive. If you've ever played "Ghosts 'n Goblins" before and you loved it, you probably love this game well. And you must know before playing it, the game requests a sort of a hardcore game play... Be attention! :p






    저작자 표시
    신고