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 for 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.

저작자 표시
신고

Recently, I tried implementing "Text on path" prototype. Even it's not a new concept nor a striking function, still it's an interesting feature to understand UI & graphics. Actually, many drawing tools (ie, MS Photoshop, Inkscape, gimp, etc) have provided it since a long time ago.

For those people who don't know about "Text on path", it is a feature that displays a text on an arbitrary virtual path like the next figure.


Even though this kind of text effects does nothing in point of functionality view, this may work for users to get interested in your application UI or to understand your application context easier. Here is a perfect use-case that this feature is effectively used in a real application (Samsung Android Milk app).


Then how could you implement it? If you are an app developer, probably you could use the convenient easy API set which is provided from your UIFW system. But my question is not for the situation. Sometimes you may need to implement by your hand beyond the framework. This page is for the it.

Most of all, you need to understand two points for this. First is a path information and second is a drawing method. Most UIFWs have their own Path data interface. A traditional path interface might look like this. (just in C style)

Path *path = create_path();
move_to(path, pos_x, pos_y);
line_to(path, to_x, to_y);
circle_to(path, center_x, center_y, radius);
curve_to(path, start_pt, ctrl_pt1, ctrl_pt2, end_pt);
...

This path data keeps the path information internally. You can move the start position using move_to() and then link following path points using line_to(), circle_to() and curve_to(). Probably there would be more functions for path but skip others here now. I don't think this path will have a nice visual but point here is, you can give the path information using sort of those interfaces.

Once the path data is set, all path coordinates per *pixels* could be generated by some interpolation algorithm during a ready stage of your program. Normally, Linear Interpolation provides good quality for this.


Or you can apply other interpolation formulas such as polynomial and spline.

I believe you can generate line, circle and some rectangular shapes path coordinates easily. It doesn't require complex formulas for them. But how about curves? How can you generates pixels for curves? One generic method for representing a curve is a using Bezier Curve. This algorithm has been universally used in graphics and even it works for this text on path. Basically, this requires 4 points - start point, end point and two control points. Start point is the start position and end point is end position for your path. Two control points actually decide the path curve style.


The curve path can be completed with this formula.


Please refer Bezier Curve wikipedia page for more details.

//progress: A normalized value (0 ~ 1) in bezier curve. 0 indicates the position of start point and 1 indicates the position of end point.
get_bezier_curve_pos(progress)
{
    v[0];    //1st control point x coordinate
    v[1];    //1st control point y coordinate
    v[2];    //2nd control point x coordinate
    v[3];    //2nd control point y coordinate
    ...
    tx = (pow((1 - progress), 3) * 0) + (3 * progress * pow((1 - progress), 2) * v[0]) + (3 * pow(progress, 2) * (1 - progress) * v[2]) + (pow(progress, 3) * 1);
    ty = (pow((1 - progress), 3) * 0) + (3 * progress * pow((1 - progress), 2) * v[1]) + (3 * pow(progress, 2) * (1 - progress) * v[3]) + (pow(progress, 3) * 1);
    ...
    return tx, ty;
}

This logic performs to get x, y position on a bezier curve. tx, ty are a pair of xy coodinates. You can get coordinates by giving progress value from 0 to 1. 0 is the start point of the curve and 1 is the end point of the curve. 0.5 is the center point of the curve. If you connect one more than bezier curves by giving several points set(start, end and 2 control points), you can make a long complex arbitrary curving path.

Now, you can generate pixel coordinates of a curving path. Next step is an actual drawing.

(For your information, you can refer the next website to simulate a bezier curve with your control points.)

Firstly, you need a text to draw. But you don't need to implement text rendering feature in your program. Instead, use the existing functions as possible. For instance, you can use one text rendering library such as Freetype. Or, probably your platform may support its own text rendering feature. Since those text rendering requires a huge amount of pages for understanding, I'd rather skip it here. You can google it by yourself.

If you use a text drawing library, defintely those libraries provide an API that returns the generated bitmap of a text. In this case, you can get a bitmap of a glyph or a bitmap of a text(sentence). Rather than glyph, text may be much better because that some languages such as Hindi, its glyphs connected each others. In that case, you can make the completed curved text only with a text.



Now, let's suppose you have a text bitmap. Next work is to bend the text bitmap. If your graphics system has a method to draw textures based on the polygons, that's the most easiest way for this. Or you can use a 3D rendering system such as OpenGL/Direct3D directly. I'm sorry, I don't take cover 3D graphics topics such as polygon and texture mapping here. If you are unfamiliar with that, please go to revise your 3d graphics lesson first.

Suppose you use the polygon based drawing. You need to construct a mesh for curved text area. You can achieve that with some linear algebra idea. Let's compute polygon vectices. Since you have a Bezier curve, you can get vectors along with a curving path by progress.


p1 = get_bezier_curve_pos(0.0);    //Point 1 at 0.0
p2 = get_bezier_curve_pos(0.01);    //Point 2 at 0.01
v = p2 - p1;    //A vector
normalize(v);

You got the first segment vector. Compute it's upper vertex. Transform the vector by -90 degree then increase the vector length by half of text height.

rad = degree_to_radian(90);
matrix2 = { cos(rad), -sin(rad), sin(rad), cos(rad) };    //Euler angle
v *= matrix2;
normalize(v);
v *= (text_height * 0.5);

v1 = v + p1;    //Don't forget the origin of the vector.


Compute the lower vertex. Simply, you can invert the upper vector here.

v2 = -v + p1;


Obviously, you got the 2 vertices. For one polygon, you need next 2 vertices more. Repeat the above sequence with the line segment between 0.01 ~ 0.02.

p1 = get_bezier_curve_pos(0.01);    //Point 1 at 0.01
p2 = get_bezier_curve_pos(0.02);    //Point 2 at 0.02
v = p2 - p1;    //A vector
normalize(v);

v *= matrix2;
normalize(v);
v *= (text_height * 0.5);

v3 = v + p1;
v4 = -v + p1;




Repeat this to the end of the curve. ( ~ 1.0)


Pretty easy. Now you can see how does "Text on path" mesh accomplished! Segments count in the screenshots might not be matched exactly. As far as decrease the segment distance, you will have a fine-grained result. Last step is texture mapping. Since you already have a text bitmap, you can use the bitmap as a texture.


Next video shows my prototype.

저작자 표시
신고
Recently, Tizen released Tizen Studio 1.0 for the Tizen developers. The orignal Tizen SDK components converged all together while their functionalities are improved. Also, UX has been nicely improved. IDE, GUI Builder, Debugger, Profiler and etc, all components have more strong consistent user experience now. Absolutely, it's good news for Tizen developers. :)

In the mean time, if you are a big fan of Tizen Studio EDC Editor, I'd like to give you a tip that a way of launching EDC Editor on the Linux console mode. Of course, you could use EDC Editor on the Tizen Studio through the standard route but sometimes you'd better launch it manually if your project is not supposed to use Tizen IDE. Any case is ok, but this tip is for that case.

I believe you already have the Tizen Studio on Linux (Ubuntu) PC. Necessary components and EDC Editor executable must be installed properly. Let me suppose your Tizen Studio is installed in the HOME directory (/home/user/tizen-studio). You can see the EDC Editor in tizen-studio/tools/edc-editor and necessary libraries(EFL) in tizen-studio/tools/efl-tools. The only thing you need to do is just set PATH and LD_LIBRARY_PATH to edc-editor and efl-tools. Let's try to launch EDC Editor with this commands.

$PATH=/home/user/tizen-studio/tools/edc-editor/bin:/home/user/tizen-studio/tools/efl-tools/bin:$PATH LD_LIBRARY_PATH=/home/user/tizen-studio/tools/edc-editor/lib:/home/user/tizen-studio/tools/efl-tools/lib/ ~/tizen-studio/tools/edc-editor/bin/enventor

Even though you have installed EFL libraries manually on your PC, you must set the PATH and LD_LIBRARY_PATH to EFL in the tizen studio because Tizen EFL is little different with upstream version. It has customized functionalities so the behavior may be different. Also, It's not surprise that executable name is enventor. Actually EDC Editor is customized to Tizen wise from EFL Enventor project. Anyhow, you must see the EDC Editor window.



To launch the EDC Editor with specific EDC file, launch it with this file name.

$PATH=/home/user/tizen-studio/tools/edc-editor/bin:/home/user/tizen-studio/tools/efl-tools/bin:$PATH LD_LIBRARY_PATH=/home/user/tizen-studio/tools/edc-editor/lib:/home/user/tizen-studio/tools/efl-tools/lib/ ~/tizen-studio/tools/edc-editor/bin/enventor sample.edc

Lastly, If you need to set the resource paths then use the below options. Those options could be applied multiple times.

$PATH=/home/user/tizen-studio/tools/edc-editor/bin:/home/user/tizen-studio/tools/efl-tools/bin:$PATH LD_LIBRARY_PATH=/home/user/tizen-studio/tools/edc-editor/lib:/home/user/tizen-studio/tools/efl-tools/lib/ ~/tizen-studio/tools/edc-editor/bin/enventor sample.edc  -i ./my_image_path -i ./my_image_path2 -s ./my_sound_path -s ./my_sound_path2 -f ./my_font_path -f ./my_font_path2 

If you think the command is too long then please write a your script. I believe It would be a piece of cake to you. :)
저작자 표시
신고
A* (A star) is one of the most popular algorithms for the path finding in the computer science. It looks for the optimal way with mixing DFS(Depth First Search)and BFS(Breadth First Search) while as it uses the Heuristic method.

Here I introduce this algorithm with one RPG prototype which I implemented many years back (actually, back in my university school days).

Let's take a look at the two screenshots.


The actual game screen is on the left side whereas right one additionally visualizes the structure of the game map. As you can see them, this game was implemented with 2d style map data which is constructed with 2 dimensional grids. Each grids have proper information including position, size, graphical data and one boolean whether are any obstacles there or not. Actually, the grids in the screenshot are displayed larger than actual size in the game for your easier understanding. If the grid is filled with red color, then it's obstacle space where the player character can't go to. So, we can understand as it's blocked space. You may think some grids looks having obstacles even though they doesn't filled with the red color. Considering perspective, those obstacles are over the player character so it's available space. Let's suppose the player character moves to one arbitrary position(grid) then let's see how A* algorithm works for the path finding.


Before starting, we can implement a grid with a Node. A node is a data structure normaly used in Linked List.

Suppose the yellow node is the destination. It looks for the available nodes from the player character's (It tries the BFS). After investigated the available 8 directions nodes, It selects one which is the closest to the destination. In this case, green arrow node.
In the meantime, it handles the internal data structure. Mainly, it constructs two linked lists (exactly, Stack) which are Open Node and Close Node. The Open Node is a candidate list which needs to investigate the list nodes further. That is, we must visit 8 directions nodes (near 8 grids) from one node. If we don't perform this yet, then this node goes appending in the Open Node. The Close Node, on the other hand, is a list that consisted of the nodes which we don't need to investigate further. And notice this, every time we append one node in the Open Node, it can sort the nodes by distance between a node and the destination.

So far, the Open Node has a green arrow node plus the yellow arrow nodes and the Close Node has only the starting point node.


Let's move on to the next. This time, It visits 8 directions nodes from the green arrow node. (It tries the DFS. A* goes through the best nodes first)
Now, the Open Node has new yellow and green arrow nodes additionally and the Close Node has the starting point node and the light green arrow node.

Annotation
Yellow arrow: New nodes appended in the Open Node.
Light yellow arrow: The nodes appended in the Open Node previously.
Green arrow: A new node appended in the Close Node.
Light green arrow: the node appended in the Close node in the previous step.


Soon it reaches to a cul-de-sac, it tries to navigate with a candidate node from the Open Node. In this case, the available way is only the lower node.


Again, it tries to navigate a candidate node from the Open Node because all directions are unavailable. They are blocked or already visited. So the result will be the above picture.



Since the next candidate node in the Open Node would be the closest to the destination, it would be just right of the starting point. But we can find out soon that the around 8 nodes of it were already visited before, so it will be moved to the Close Node immediately. Now, the next candidate node would be just above the starting point. However, it should be moved to the Close Node also by the same reason. So the next candidate would be the most upper side node in the below picture.


But in this case if we figure out the next candidates around it, they all are more apart from the destination compared to a candidate node in the Open Node. So the next candidate will be the right bottom node from the starting point.
If we keep going through this sequence, we can finally reach to the destination.





Lastly, we can get a path if we track back the white arrows. Those white arrow nodes would be in the Close Node, so just iterate nodes from the last to the first reversely.

If you can understand the A* theory then let's take a look at the code now.

//A node structure 
typedef struct node_s {
    int degree;                       //Depth info. Same with the depth in the tree data structure 
    int distance;                     //Distance between this node and the destination 
    int value_factor;                 //Evaluated value (degree + distance) 
    int x, y;                         //Grid position 
    struct node_s* direct[8];         //Neighbor nodes around this 
    struct node_s* prev_node;         //Previous node in the linked list  
    struct node_s* next_node;         //Next node in the linked list.  
} node_t;
 
//A stack for node sorting
typedef struct node_stack_s {
    node_t*  node;                    //A node in this stack position
    struct node_stack_s*  next_node;  //Next node stack
} node_stack_t;


Obviously, we can get the evaluated value (a sum of degree and distance) but you can modify the units of the degree and distance properly for your case. For your reference, If the minimum value of the degree is 1, then 1 is good to the distance between adjancent 2 nodes.

Now, let's declare global Open Node, Close Node and one stack.

node_t *OPEN = NULL, *CLOSED = NULL;
node_stack_t* STACK = NULL;

Let's see the pseudo code of next functions because it's not much importance. But pretty enough just with commentary.

//Initialize list and stack (also clean up resources)
void init_astar()
{
    //Remove all nodes while iterating Open Node
    //Remove all nodes while iterating Close Node
    //Remove all stack resources while iterating the stack.
}
//Check whether the node in the given position is blocked or not. 
bool is_available_grid(int x, int y)
{
    //Implement whether the player character can move to this node or not.
    //...
    //If it's available, return TRUE.
    //otherwise (in case of blocked?), return FALSE. 
}
//Check whether the node in the given position is existed in Open Node. 
node_t* is_open(int x, int y)
{
    //Iterate Open Node to find the node in the position.
    //If it does, return the node.
}
//Check whether the node in the given position is existed in CLOSE NODE. 
node_t* is_closed(int x, int y)
{
    //Iterate CLOSE NODE to find the node in the position.
    //If it does, return the node.
}
//Insert a given node in the stack. 
void push_into_stack(node_t* node)
{
    //Allocate one stack node.
    //Set the input node to the stack node. 
    //Push the stack node into the stack.
}
//Remove a node from the stack. 
node_t* pop_from_stack()
{
    //Get the last stack node from the stack.
    //Remove stack node.
    //Return the node of the stack node
}

Please refer the attached file if you wanna see the actual source code of above functions.

Now, it's a little more serious. Next is the path finding function.

//Starting point: start_x, start_y, and destination: dest_x, dest_y
node_t* find_path( int start_x, int start_y, int dest_x, int dest_y ) {
 
    node_t* present=NULL;    //Starting point node
 
    //Add the starting point node. Reversely, starting point is the destination here.
    //That means, navigate from the destination to the starting point. 
    //Destination to Starting point as I mentioned above. We can rewind the completed list to figure out the path. 
    present = (node_t*) calloc(1, sizeof(node_t));        
    present->degree = 0;    
    present->distance=  pow((dest_x- start_x), 2) + pow((dest_y - start_y), 2);    //Originally, it requires a square root but not necessary.
    present->value_factor = present->distance;    // distance + degree
    present->x= dest_x;                                          
    present->y= dest_y;
 
    OPEN=present;   

    node_t* best=NULL;      //best keeps the best path list.
    int count=0;     //Count a iteration to prevent the infinite loop.
         
     //Begin the investigation. But limit the loop count, just in case.
     while(count< FINDPATH_LIMIT) {
 
        //No more candidates. Probably, we found the path?
        if (OPEN == NULL) {
            return best;
        }
 
        best = OPEN;    //Begin with investigating a candidate in the Open Node.
        OPEN = best->next_node;    //The next node should be set to a next candidate for next step.
        best->next_node = CLOSED;    //Appends the Closed Nodes to a current best one so that we can keep a constructed path properly.
        CLOSED=best;   //Move to the Closed Node because this best node will be visited in this time.
 
        //Failed at path finding.
        if(best == NULL) {
            return NULL;   
        }
          
        //Succeed!
        if (best->x == start_x && best->y == start_y)  
            return best;
 
        //Extends neighbor nodes from a current node.
        if (make_child(best, start_x, start_y) == 0 && count == 0) 
            return NULL;
 
        count++;             
    }          
    return best;
}

Continue to make_child() function.

 //the node to extends, and destination x, y. it also returns a value of extension fact.
bool make_child(node_t* node, int dest_x, int dest_y) {
         
    bool extended = false;    //Return true if it extended any childs.
    int x = node->x;
    int y = node->y;
 
    //Create children nodes if they were available space.
    //Left
    if( is_available_grid(x - 1, y) ) {
        extend_child_node(node, x-1, y, dest_x, dest_y, LEFT_IDX);
        extended=1;
    }
    
    //Implement last 7 directions in the same manner. Differences are just x, y positions.
    //Right: x + 1, y
    //Top: x, y - 1
    //Bottom: x, y + 1
    //Top Left: x - 1, y - 1
    //Top Right: x + 1, y - 1
    //Bottom Left: x - 1, y + 1
    //Bottom Right: x + 1, y + 1 
 
    return extened;
}

I hope you understood so far. But actually in this game, it would be more realistic that if we allow diagonal moving only if two adjacent grids are available so. The change won't be difficult at all. I believe you can make it yourself.

Don't allow diagonal moving if two adjacent grids are blocked


Next function is extend_child_node(). This function is designed to extend a node and sort it if necessary.

//A node to extend, a current node position, the destination position, a direction to extend
void extend_child_node(node_t* node, int cur_x, int cur_y, int dest_x, int dest_y, int cur_direct) {
 
    node_t *old = NULL, *child = NULL;
    int i;
    int degree= node->degree + 1;
 
    //If a extending node is existed in Open Node, pick it up.
    if (old = Is_open(cur_x, cur_y)) { 

        node->direct[cur_direct] = old;
 
        //Reset the node. Notice that node order is reversed.
        if (degree < old->degree) {
            old->prev_node = node;
            old->degree = degree;
            old->value_factor = old->distance + old->degree;
        }
 
    //If a extending node is existed in Close Node.
    } else if (old = is_close(cur_x, cur_y)) {

        node->direct[cur_direct] = old;
  
        //In some cases, degree won't be valid. Sort it again.
        if (degree < old->degree) {
            old->prev_node = node;
            old->degree = degree;
            old->value_factor = old->distance + old->degree;

            make_sort(old);
        }
 
    //Create a child node and push it into Open Node.
    } else {

        if ((child = (node_t*) calloc(1, sizeof(node_t))) == NULL) 
            return;    //Lack of memory?
 
        child->prev_node = node;
        child->degree = degree;
        child->distance =  (cur_x - dest_x) * (cur_x - dest_x) + (cur_y - dest_y) * (cur_y - dest_y);
        child->value_factor = child->distance + child->degree;
        child->x = cur_x;
        child->y = cur_y;
                    
        insert_node(child);
 
        node->direct[cur_direct] = child;        
    }
}

It's getting more complex and complex!! If you cannot understand the source code, please draw a temporary path and follow step with this logic. it's going over!

//old: A new node for re-evaluation 
void make_sort(node_t* old) {
 
    node_t *direct, *previous;
    int i;
    int degree = old->degree + 1;
 
    for (i=0; i<8; i++) {
 
        if ((direct = old->direct[i]) == NULL) continue;
 
        //Updates nodes. Use a stack for children
        if (direct->degree > degree) {
            direct->prev_node = old;
            direct->degree  = degree;
            direct->value_factor  = direct->distance + direct->degree;                                  
            push_into_stack(direct);
        }
    }
 
    //Updates nodes using a stack.
    while (STACK) {

        previous = pop_from_stack();
 
        for (i=0; i<8; i++) {

            if ((direct = previous->direct[i]) == NULL) break;

            if (direct->degree > previous->degree + 1) {
                direct->prev_node = previous;
                direct->degree  = previous->degree + 1;
                direct->value_factor  = direct->distance + direct->degree;    
                push_into_stack(direct);
            }
        }
    }
}

Here, it handles an exceptional case which hasn't been mentioned in the above example. It just re-evaluate the nodes when the Close Node needs update.

Lastly, append a node to the Open Node. Point is, it keeps the order in distance when it push a node.

//new node
void insert_node(node_t* present) {
 
    node_t* old = NULL, *temp = NULL;
  
    if (OPEN == NULL) {
        OPEN = present;
        return;
    }
 
    temp = OPEN;
    
   //Find a node which has a higher value factor than the present.
    while (temp && (temp->value_factor < present->value_factor)) {
        old = temp;  
        temp = temp->next_node;
    }
 
    //Reconstruct the list. Now these list are sorted by distance.
    if (old) {
        present->next_node = temp;
        old->next_node = present;
    } else {
        present->next_node = temp;
        OPEN = present;
    }
}

Somewhat difficult to describe the complex logic by me. But you could hopefully understand what I am talking here. Otherwise, please read the source code considerably.

Anyhow, A* is done here. This logic is for a standard case so definitely you could optimize it further. For instance, we can skip the list of nodes if they just construct a straight way. Or probably we can add a way point in the map in such cases doors and gates. Definitely it would be much better for the path finding. In case of 3D map, I believe it's not much different even though. But you need to google for more information.

astar.cpp
저작자 표시
신고



EFL provides multiple widget categories and a vast collection of low level and high level APIs to create UI layouts tailored to specific application requirements.
In addition, the programming model also uses a script language called EDC (Edje Data Collection), so that the application logic can be separated from the UI design. Using EDC, application developers can also make complex and dynamic UI layouts. The Tizen SDK has rich collection of UI creation tools and documentation for application developers to make use of the above facilities.
During the course of this webinar, Hermet will introduce the concepts involved in creating complex UI layouts using EFL. The topics covered include EDC, API interactions and different widget classes and how all of these can be combined to build a complex UI layout for an application.
In addition, the usage of dynamic EDC editor tool, Enventor, is discussed. If you are an application developer building native applications for Tizen devices or in general want to understand the native UI programming model of Tizen, this webinar is for you.
저작자 표시
신고
2. 프로그램 확장하기


자, 잠깐의 휴식을 가졌으므로(?) 검은 화면에 뭔가를 채워볼 시간이다. 먼저 윈도우 내에 사각형을 하나 생성하고 색상을 칠할 것이다. 물론 EFL 라이브러리에서는 이를 쉽게 하기 위한 기능을 제공하고 있다.

예제 소스 코드부터 보도록 하자.

#include <Elementary.h>

int main(int argc, char **argv)
{
   elm_init(argc, argv);
 
   //윈도우를 생성하고 초기화 한다.
   Evas_Object *win = elm_win_add(NULL, "Elementary Window", ELM_WIN_BASIC);
   elm_win_title_set(win, "Elementary Window");
   evas_object_resize(win, 480, 400);
   evas_object_move(win, 100, 100);
   evas_object_show(win);
 
   //윈도우 오브젝트로부터 Evas 핸들을 반환받는다.
   Evas *e = evas_object_evas_get(win);
 
   //반환받은 Evas 핸들을 이용하여 화면에 출력할 사각형을 생성한다.
   Evas_Object *rect = evas_object_rectangle_add(e);
   evas_object_resize(rect, 480, 400);
   evas_object_color_set(rect, 0, 0, 255, 255);
   evas_object_show(rect);

   elm_run();

   elm_shutdown();

   return 0;
} 


사각형 오브젝트를 생성하기 전에, 먼저 우리는 윈도우 오브젝트로부터 Evas 핸들을 얻어오고 있다. 이를 위해, evas_object_evas_get() API를 호출하며, 이 API는 오브젝트 포인터를 요구하고, 해당 오브젝트로부터 오브젝트가 소속된 Evas 핸들을 반환한다.

Evas *evas_object_evas_get(const Evas_Object *obj);

Evas에 대해 아직은 충분한 설명이 없지만, 다음 장에서 Evas에 대해 구체적으로 살펴볼 예정이므로 지금은 간략하게만 요약해 보도록 하자.

우리는 Evas를 캔버스 엔진으로 정의할 수 있다. Evas는 내부적으로 출력 버퍼를 초기화하고 이를 대상으로 렌더링 작업을 수행한다. 렌더링을 위해서는 화면에 그려질 오브젝트들의 그래픽 정보를 실질적으로 보유하고 관리를 한다. 그 외에도 Evas는 오브젝트들의 Z 순서를 위한 계층(Layer) 정보 및 마우스 입력과 같은 사용자 입력에 대한 이벤트 등을 처리하고 있다. Evas는 사용자 입력 이벤트를 어떤 오브젝트로 전달해야 할지, 어떤 오브젝트를 화면에 그려야 할지, 오브젝트들의 라이프 사이클을 결정하고 이들을 렌더링하는 역할을 한다.

사실 이 예제의 윈도우 오브젝트 역시 Evas에서 관리하는 하나의 Evas_Object에 해당되기 때문에 우리는 윈도우 오브젝트를 통해 Evas인스턴스에 대한 핸들을 얻어올 수 있으며, Evas핸들을 이용하면 Evas에 사각형 오브젝트를 추가할 수 있다. 일부 독자들은 "그럼 우리가 언제 Evas인스턴스를 생성한 거지?" 라고 의아해 할 수 있겠지만, 사실 이 부분은 elm_win_add()가 호출될 시에 내부적으로 수행되었다. 이 말은 즉, 기본적으로 윈도우를 생성할 때마다 하나의 Evas인스턴스가 생성되며 각 윈도우마다 렌더링 출력물을 갖게 된다.


그림 3 윈도우마다 Evas 인스턴스가 존재한다.


우리는 화면에 출력할 사각형 오브젝트를 생성하기 위해 evas_object_rectangle_add() API를 이용하였다. 다시 한번 말하지만, 이 API 역시 evas 접두사로 시작하며 이 말은 해당 기능이 Evas에서 제공하는 API임을 알 수 있다. 호출 시, Evas인자를 넘겨준 후 Evas_Object 타입의 사각형 오브젝트를 반환 받는다. 이 후 우리는 반환받은 rect 핸들을 가지고 이 사각형 오브젝트에 대한 세부적인 설정을 수행할 수가 있다.

Evas_Object *evas_object_rectangle_add(Evas *e);

우선 사각형의 사이즈를 윈도우와 같은 크기인 480 x 400로 변경하고 위치는 0, 0으로 화면의 좌측, 최상단으로 지정한다. 물론 기본 위치 값이 0, 0 이기 때문에 여기서는 애써 evas_object_move()를 호출하지 않았다. 이렇게 되면 사각형은 윈도우 내부에 가득 찬다. 다음으로 사각형의 색상을 지정한다. 이를 위해 evas_object_color_set() API를 호출하며 여기서는 파란색인 0, 0, 255, 255 (Red, Green, Blue, Alpha) 값을 인자로 전달한다.

void evas_object_color_set(Evas_Object *obj, int r, int g, int b, int a);

evas_object_move() 및 evas_object_resize()와 같이 색상 지정 API 역시 일반적으로 이용되는 API이기 때문에 다른 타입의 오브젝트에도 적용이 가능하다. 여기서 주의할 점은, 매개변수 r, g, b 값들은 알파 값에 의해 미리 계산된(pre-multiplied) 값을 요구하며 사용자는 직접 그 값을 계산해서 전달해야 한다.

예를 들어, 반투명한 흰색의 사각형을 원한다면,

evas_object_color_set(rect, 255, 255, 255, 127);

를 호출하는 것이 아니라,

evas_object_color_set(rect, 127, 127, 127, 127);


와 같이 호출해야 한다. 각 R, G, B 각 요소는

R = 255(R) * 127(A) / 255
G = 255(G) * 127(A) / 255
B = 255(B) * 127(A) / 255

와 같이 계산한 결과 값이다.

경험 상, 이 부분은 많은 개발자들이 오용하는 경우가 많았으므로 여러분들도 꼭 상기하여 실수하지 않기를 바란다.

다음으로는 evas_object_show() API를 호출하여 사각형 오브젝트가 화면에 보이도록 한다.

자, 그럼 이쯤에서 결과물을 확인해 보도록 하자. 만약 여러분의 애플리케이션이 검정색에서 파란색 배경으로 바뀌었다면 성공한 것이다.


4. 윈도우에 사각형 오브젝트를 추가한 결과


만약, 여러분이 배경 색을 다른 색상으로 바꾸고 싶다면 evas_object_color_set()의 r, g, b 의 인자 값을 변경해 보길 바란다.

마지막으로 콘솔이 아닌, 윈도우에 "Hello E World"를 출력해 보도록 하자. 다음 예제 소스 코드를 보면 이 역시 얼마나 쉬운지 알게 될 것이다.

#include <Elementary.h>
 
int main(int argc, char **argv)
{
   elm_init(argc, argv);

  //윈도우를 생성하고 초기화 한다.
   Evas_Object *win = elm_win_add(NULL, "Elementray Window", ELM_WIN_BASIC);
   elm_win_title_set(win, "Elementary Window");
   evas_object_resize(win, 480, 400);
   evas_object_move(win, 100, 100);
   evas_object_show(win);

   //윈도우 오브젝트로부터 Evas 핸들을 반환받는다.
   Evas *e = evas_object_evas_get(win);

   //반환받은 Evas 핸들을 이용하여 화면에 출력할 사각형을 생성한다.
   Evas_Object *rect = evas_object_rectangle_add(e);
   evas_object_resize(rect, 480, 400);
   evas_object_color_set(rect, 0, 0, 255, 255);
   evas_object_show(rect);

   //레이블 위젯을 생성한다. 이 위젯은 텍스트를 출력하는 기능을 수행한다.
   Evas_Object *label = elm_label_add(win);
   elm_object_text_set(label, "Hello E World!");
   evas_object_move(label, 100, 100);
   evas_object_resize(label, 200, 100);
   evas_object_show(label);

   elm_run();

   elm_shutdown();

   return 0;
}

이제 여러분은 EFL 프로그래밍 방식에 어느 정도 감이 잡혔을 것이라고 생각한다. 새로 추가한 볼드체 코드를 보도록 하자. 가장 먼저 elm_label_add()를 호출함으로써 Elementary에서 제공하는 레이블 위젯을 생성한다. 이 때 매개변수로 윈도우 오브젝트를 전달하는 것에 주목하도록 하자.

Evas_Object *elm_label_add(Evas_Object *parent);

이번에는 Evas가 아닌 Elementary API를 이용하여 레이블을 추가하고 있는 점에 주목하자. 사실, Evas에서도 텍스트를 출력하는 텍스트 오브젝트가 존재하며 글자를 출력한다는 측면에서 레이블 위젯과 기능적으로 동일하나, Evas에서 제공하는 원초적인 오브젝트에 비해 Elementary의 위젯들은 애플리케이션에 있어서 필요한 기반 기능을 보다 많이 가지고 있다. 다음은 대표적인 장점들을 나열한다.

포커스(Focus): 각 위젯 간의 포커스 이동. 포커스를 통해 어떠한 오브젝트가 키 이벤트를 전달받을 지 결정하며, 부모에서 자식으로, 자식에서 부모로 또는 자식에서 자식으로 포커스가 이동될 수 있다.
테마(Theme): 동일한 애플리케이션일지라도 다양한 룩앤필(Look & Feel)를 지원하기 위한 테마 설정이 가능하다.
스케일(Scale): 동일한 애플리케이션으로부터 다양한 화면 해상도에서 동일한 화면 구성을 유지하기 위한 GUI 확장성을 제공한다.
미러(Mirror): 여러 나라의 언어 특성에 맞게 각 위젯이 좌/우 대칭으로 반전되는 기능을 제공한다.
언어(Language): 다국어 변경을 지원하기 위해 Elementary는 언어와 관련된 기반 기능을 제공하여 애플리케이션에서 보다 쉽게 언어를 변경할 수 있도록 도와준다.

간략하게나마 설명을 덧붙여 놨는데 자세한 사항은 6장의 “위젯 툴킷: Elementary”에서 살펴보도록 하며 여기서는 일단 이러한 기능들을 Elementary 위젯으로 이용 가능하다는 사실만 알고 넘어가자.

앞서 윈도우를 생성할 때, Elementary에서는 부모-자식 관계가 존재한다고 언급했었는데 elm_label_add() API에서는 매개변수로 부모 오브젝트를 요구하고 있다는 점을 주목하길 바란다. 실제로 모든 Elementary 위젯들을 생성하는 API들은 부모 오브젝트를 인자로 요구하며 이 예제의 경우 윈도우는 레이블의 부모가 되며, 레이블은 윈도우의 자식이 될 것이다. 내부적으로 부모-자식 간의 트리 구조가 성립되어 자식은 부모에게 종속되는데 여기서 우리가 기억해야 하는 점은 부모 오브젝트가 제거될 시 자식도 같이 제거된다는 사실이다.

다음으로 elm_object_text_set() API를 호출함으로써 레이블 위젯이 출력할 텍스트를 지정하고 있다. elm_object_text_set()은 레이아웃을 상속받은 Elementary 위젯이라면 모두 이용할 수 있는 공용 API인데, 사실 텍스트를 출력하는 기능이 있는 위젯이라면 이 API를 통해 화면에 출력할 텍스트를 지정할 수 있다. API의 접두사가 "elm_label"이 아닌, "elm_object"로 시작되고 있다는 점을 주목하면 보다 기억하기 쉬울 것이다.

어쨌든 간에, 우리는 elm_object_text_set() API를 이용하여 "Hello E World!"를 출력하도록 지정하였다.

void elm_object_text_set(Evas_Object *obj, const char *label);  

이 API는 첫 번째 인자로 대상 오브젝트를, 두 번째 인자로 출력할 문자열을 요구한다.

다음으로 텍스트가 출력될 영역이 100, 100에서 200, 100이 되도록 지정하고 화면 상에 출력되도록 Evas API들을 이용하였다. 자, 그럼 결과물을 확인해 보도록 하자.


5. 윈도우에 Hello E World!를 출력한 결과


만약 여러분이100, 100에서 200, 100 영역에 "Hello E World!" 문자열이 가득 채워져 출력되길 바랬다면 오산이다. 해당 영역은 단지 레이블 위젯이 차지할 영역일 뿐이며, 실제로 출력될 텍스트는 레이블 위젯의 폰트명과 폰트 크기를 기반으로 출력될 뿐이다.

자, 이쯤이면 첫 번째 예제가 거의 완성되었다. 하지만 할 일이 하나 더 남아있다. 우리는 Ctrl+C를 통한 애플리케이션 종료가 아닌, 어떤 키 입력이 왔을 때 애플리케이션을 종료할 수 있어야 한다. 이를 위해, 키 입력 사실을 알 수 있어야 하며 어떤 키가 눌러졌는지도 파악할 수 있어야 한다. 이번 예제에서는 Evas를 통해 이를 구현할 것이며 Esc 키가 눌렸을 때 애플리케이션을 종료하도록 할 것이다. 소스 코드를 보도록 하자.

#include <Elementary.h>

void key_down_cb(void *data, Evas *e, Evas_Object *obj, void *event_info)
{
   Evas_Event_Key_Down *ev = event_info;

   if (!strcmp(ev->keyname, "Escape"))
     {
        printf("Good Bye!\n");
        elm_exit();
     }
}

int main(int argc, char **argv)
{
   elm_init(argc, argv);

   //윈도우를 생성하고 초기화 한다.
   Evas_Object *win = elm_win_add(NULL, "Elementary Window", ELM_WIN_BASIC);
   elm_win_title_set(win, "Elementary Window");
   evas_object_resize(win, 480, 400);
   evas_object_move(win, 100, 100);
   evas_object_show(win);

   //윈도우 오브젝트에 키 입력 이벤트를 추가한다.
   evas_object_event_callback_add(win, EVAS_CALLBACK_KEY_DOWN, key_down_cb, NULL);

   //윈도우 오브젝트로부터 Evas 핸들을 반환받는다.
   Evas *e = evas_object_evas_get(win);

   //반환받은 Evas 핸들을 이용하여 화면에 출력할 사각형을 생성한다.
   Evas_Object *rect = evas_object_rectangle_add(e);

   //...

   return 0;
}

새로 추가된 부분은 볼드체로 표시하였다. 먼저 main() 함수에서 윈도우 오브젝트를 생성하는 부분을 보면 윈도우 오브젝트에 이벤트 콜백 함수를 등록하는 부분이 추가되었다.

void evas_object_event_callback_add(Evas_Object *obj, Evas_Callback_Type type, Evas_Object_Event_Cb func, const void *data);

첫 번째 매개변수는 이벤트 대상이 되는 오브젝트를 가리키며 예제에서는 윈도우 오브젝트에 해당한다. 두 번째 매개변수는 이벤트 타입을 전달 받는데, 우리는 받고자 하는 이벤트 종류를 두 번째 인자로 결정할 수가 있다. 예제에서는 키가 눌렸을 때의 이벤트를 받고자 하기 때문에 EVAS_CALLBACK_KEY_DOWN을 전달한다. 이 외에도 우리가 확인할 수 있는 이벤트 종류는 다양하며 다음과 같은 이벤트들에 대해서 우리는 콜백 함수를 등록할 수 있다.

EVAS_CALLBACK_MOUSE_IN - 마우스 커서가 대상 안에 들어온 순간
EVAS_CALLBACK_MOUSE_OUT - 마우스 커서가 대상 밖으로 벗어날 때
EVAS_CALLBACK_MOUSE_DOWN - 마우스 버튼이 눌렸을 때
EVAS_CALLBACK_MOUSE_UP - 마우스 버튼이 해제되었을 때
EVAS_CALLBACK_MOUSE_MOVE - 마우스가 이동할 때
EVAS_CALLBACK_MOUSE_WHEEL - 마우스 휠이 작동할 때
EVAS_CALLBACK_MULTI_DOWN - 멀티 터치와 같은 환경에서 동시에 여러 터치 입력이 발생했을 때
EVAS_CALLBACK_MULTI_UP - 여러 터치 입력 중에서 첫 번째를 제외한 나머지 터치 입력들이 해제될 때
EVAS_CALLBACK_MULTI_MOVE - 여러 터치 입력 중에서 첫 번째를 제외한 나머지 터치 입력들이 이동할 때
EVAS_CALLBACK_FREE – 삭제 요청을 받은 후, Evas에서 완전히 제거될 때
EVAS_CALLBACK_KEY_DOWN - 키 입력이 발생할 때
EVAS_CALLBACK_KEY_UP - 키 입력이 해제될 때
EVAS_CALLBACK_FOCUS_IN – 포커스 상태가 될 때
EVAS_CALLBACK_FOCUS_OUT – 포커스 상태에서 해제될 때
EVAS_CALLBACK_SHOW - evas_object_show()가 호출될 때
EVAS_CALLBACK_HIDE - evas_object_hide()가 호출될 때
EVAS_CALLBACK_MOVE - evas_object_move()가 호출될 때
EVAS_CALLBACK_RESIZE - evas_object_resize()가 호출될 때
EVAS_CALLBACK_RESTACK - evas_object_stack_below(), evas_object_stack_above()등이 호출되어 레이어의 Z 오더의 순서가 바뀔 때
EVAS_CALLBACK_DEL - evas_object_del()이 호출될 때
EVAS_CALLBACK_HOLD - 이벤트 전달 상태가 변경될 때
EVAS_CALLBACK_CHANGED_SIZE_HINTS – evas_object_size_hint_weight_set(), evas_object_size_hint_align_set()과 같은 오브젝트의 크기 힌트 값이 변경될 때
EVAS_CALLBACK_IMAGE_PRELOADED - 이미지 오브젝트의 경우 이미지의 PRELOAD가 완료되었을 때

세 번째 매개변수와 네 번째 매개변수는 각각 등록할 콜백 함수 포인터와 콜백 함수에 전달될 사용자 데이터 포인터를 전달 받는다. 이번 예제에서는 콜백 함수에 전달할 데이터가 딱히 없으므로 네 번째 인자로 NULL을 전달하고 있다. 다음은 콜백 함수의 원형이다.

void (*Evas_Object_Event_Cb)(void *data, Evas *e, Evas_Object *obj, void *event_info);

첫 번째 매개변수로 앞서 콜백 함수 등록 시 네 번째로 전달한 사용자 데이터 포인터가 전달되며, 두 번째는 Evas 핸들, 세 번째는 이벤트 대상 오브젝트, 네 번째는 이벤트 타입에 해당하는 부가적인 정보가 전달된다.

예제에서는 key_down_cb()을 main() 앞에 구현하였고 이를 이벤트 콜백 함수로서 추가하였다. 실제로 키 입력이 발생할 시 이 콜백 함수가 호출될 것이며 첫 번째 매개변수로는 NULL 값이, 두 번째는 Evas 핸들, 세 번째 매개변수로 윈도우 오브젝트가 전달되고 네 번째 매개변수로는 EVAS_CALLBACK_KEY_DOWN에 해당하는 부가적인 정보가 전달된다. 여기서 우리는 네 번째 인자 event_info를 Evas_Event_Key_Down 포인터 형으로 변환하고 우리가 원하는 정보를 확인할 수가 있다. 물론 EVAS_CALLBACK_MOUSE_IN, EVAS_CALLBACK_MULTI_UP와 같은 이벤트 콜백을 등록했다면, 그에 해당하는 데이터로 변환하여 이용해야 한다. 실제로 어떠한 데이터 형으로 변환해야 할지는 Evas 문서를 확인하거나 다음 장, “캔버스 엔진: Evas” 편을 확인해 보길 바란다.

우리는 event_info 인자를 Evas_Event_Key_Down 타입의 포인터 형으로 변환함으로써 구조체가 담고 있는 데이터에 접근할 수 있으며 이 중, keyname을 통해 현재 어떤 키가 눌려졌는지 확인할 수 있다. 예제에서는 Escape (ESC) 키가 눌러졌는지 확인하기 위해서 단순히 문자열 비교를 하고 있으며, 실제로 키 이름이 동일할 시 elm_exit()를 호출하여 elm_run()에 의해 가동 중인 메인 루프를 종료시킨다.

여기서 매우 추상적으로만 보았던 elm_run()의 메인 루프는 다음과 같은 코드로 표현할 수 있을 것이다.

#include <Elementary.h>
 
void key_down_cb(void *data, Evas *e, Evas_Object *obj, void *event_info)
{
   //...
}

int main(int argc, char **argv)
{
   elm_init(argc, argv);

   //...

   //레이블 위젯을 생성한다. 이 위젯은 텍스트를 출력하는 기능을 제공한다.
   Evas_Object *label = elm_label_add(win);
   elm_object_text_set(label, "Hello E World!");
   evas_object_move(label, 100, 100);
   evas_object_resize(label, 200, 100);
   evas_object_show(label);
   
   //elm_run()이 호출되면 메인 루프가 가동되며 내부적으로 여러 작업들이 수행될 것이다.
   while(1)
   {
    //윈도우, 사각형, 레이블 등, 오브젝트들의 위치 및 크기 계산 후 렌더링
    //만약 시스템으로부터 키 입력이 발생했다면 사용자가 등록한 key_down_cb 호출
    //만약 elm_exit()가 호출 되었다면 while문 종료
   }
   
   elm_shutdown();

   return 0;
}

자, 그럼 실제로 애플리케이션을 가동 후 Esc 키를 눌러 종료해 보길 바란다. 이제, 윈도우가 생성되고 윈도우 내에 색상 및 텍스트가 출력되며 키 이벤트에 반응하는 그럴듯한 첫 번째 애플리케이션이 완성되었다!


저작자 표시
신고
1. 첫 번째 프로그램


자, 그럼 이번 절에서는 본격적으로 EFL 애플리케이션을 작성해 보도록 하자. 프로그래밍 관례에 따라 "Hello E World"를 출력하는 매우 단순한 예제로 시작한다.

#include <Elementary.h>

int main(int argc, char **argv)
{
   elm_init(argc, argv);
 
   printf("Hello E World!\n");
 
   elm_run();
 
   elm_shutdown();
 
   return 0;
}

무엇보다도, 여러분이 당황하지 않도록 최소한의 API를 이용하여 예제를 완성해 보았다. 우선 Elementary의 헤더파일을 추가한다. 당연하겠지만, Elementary API를 이용하기 위해선 API가 선언되어 있어야 하며 이는 Elementary.h 파일 내부에 모두 명시되어 있다. 다음으로 매우 익숙한 main() 함수가 존재하고 main() 함수 안에는 "Hello E World!"를 출력하는 printf() 함수가 존재한다. 이외로 생소한 세 개의 API 호출이 보인다. 이들이 바로 Elementary에서 제공하는 API들이며, 하는 역할은 매우 직관적이다. elm_init()은 라이브러리를 초기화한다. Elementary의 기능들을 사용하기 위한 기본 설정들을 내부적으로 수행할 것이다.

int elm_init(int argc, char **argv);

이 API는 두 개의 인자를 전달받는데, 이들은 프로그램 실행 시 시스템을 통해 전달받는 인자의 개수와 그 개수에 해당하는 문자열 배열의 이중 포인터이다. main() 함수에서 전달받은 매개변수들을 그대로 넘겨주면 된다.

elm_run()은 프로그램의 메인 루프를 작동시킨다. 일부 라이브러리들과는 다르게 Elementary는 라이브러리 내부적으로 프로그램 기본 루프를 제공하고 있으며 이 루프는 애플리케이션에서 필요한 메인 루프의 역할을 대신 담당하게 될 것이다. 이해하기 쉽게 비유하자면, 다음과 같은 코드와 동일하다.

#include <Elementary.h>
 
int main(int argc, char **argv)
{
   elm_init(argc, argv);

   printf("Hello E World!\n");

   while(1)
   { 
      //애플리케이션은 여기서 어떤 작업을 수행할 것이다.
   }

   elm_shutdown();

   return 0;
}

물론, elm_run()이 수행하는 메인 루프 내부에서는 앞선 예제와 다르게 매우 중요한 작업들을 수행한다. 여기서는 단지 여러분의 이해를 돕기 위해 매우 추상적으로 설명했음을 알아두자. 어찌되었던 간에, 다음을 살펴보면 elm_shutdown()을 호출하고 있다. elm_shutdown()은 elm_init()과 반대로, 메인 루프 내부에서 작동 중이던 모든 기능들을 중단하고 할당했던 모든 리소스를 해제한다. 따라서 elm_shutdown()은 애플리케이션이 종료될 시점에는 반드시 호출되어야 하며 항상 elm_init()과 한 쌍으로 호출되어야 한다.

그런데, 이 예제에서는 elm_run()에 의해 가동된 메인 루프가 무한 루프와 같이 계속 작동하기 때문에 elm_shutdown()에는 도달하지 못할 것이다. 물론 어떤 이벤트에 의해 루프는 종료될 수 있지만, 그 부분은 좀 더 뒤에서 살펴보도록 하고, 일단 이 프로그램을 가동해 보자. 미안한 말이지만, 만약 여러분이 뭔가 그럴 듯한 결과물을 기대하고 있었다면 실망도 그만큼 클 것이다. 심지어, 아직은 프로그램을 중단할 방법이 없기 때문에 Ctrl+C를 이용하여 프로그램을 중단해야 한다.

그림 1. 첫 번째 예제 실행 결과


그렇다고 해서 이런 시시한 예제에 분노하기엔 아직 이르다. 다음 예제를 계속 보도록 하자. 이번에는 콘솔에 단순한 글자를 출력하는 것이 아닌 하나의 윈도우가 존재하는 진짜 프로그램(?)이 될 것이다.

#include <Elementary.h>

int main(int argc, char **argv)
{
   elm_init(argc, argv);
 
   //윈도우를 생성하고 초기화 한다.
   Evas_Object *win = elm_win_add(NULL, "Elementary Window", ELM_WIN_BASIC);
   elm_win_title_set(win, "Elementary Window");
   evas_object_resize(win, 480, 400);
   evas_object_move(win, 100, 100);
   evas_object_show(win);

   elm_run();

   elm_shutdown();

   return 0;
}

자, 단 다섯 라인을 추가하여 윈도우를 생성하는 프로그램을 완성하였다. 추가한 부분은 볼드체로 표기하였는데 한 라인씩 살펴보도록 하자.

우선, 첫 번째 라인에서는 윈도우를 생성하고 생성된 윈도우 오브젝트를 반환받는다. 이 때, 반환되는 윈도우 오브젝트의 데이터 타입은 Evas_Object의 포인터 형이다. Evas_Object는 EFL에서 오브젝트를 가리키는 단일 인터페이스이며 반드시 포인터 형으로만 쓸 수 있다. 우리는 Evas_Object 타입을 통해 다양한 종류의 오브젝트들을 일관된 방식으로 이용할 수 있다는 장점을 얻을 수 있다.

윈도우를 생성하는 API는 elm_win_add()인데, 이 API는 세 개의 매개변수를 갖는다.

Evas_Object *elm_win_add(Evas_Object *parent, const char *name, Elm_Win_Type type);

첫 번째 매개변수는 부모 윈도우 오브젝트를 전달받는다. 만약 부모가 없는 독립 윈도우라면 예제와 같이 NULL을 넘겨주면 된다. 만약 여러분이 타 윈도우 프로그래밍에 대한 경험이 있다면 윈도우엔 부모-자식 관계가 있다는 사실을 알고 있을 지도 모른다. EFL에서도 윈도우 간에는 부모-자식 간의 관계가 존재하며 이들의 개념은 타 윈도우 프로그래밍의 부모-자식 개념과 대체로 유사하다. 일반적으로 자식 윈도우의 기능 동작은 부모 윈도우의 기능 동작에 영향을 받도록 되어 있다. 일부 독자들에겐 생소한 개념일 지도 모르나, 당장은 중요하지 않으므로 일단은 부모-자식 메커니즘이 준비되어 있다는 사실만 알아두고 넘어가자.

두 번째 매개변수는 윈도우 이름을 전달받는다. elm_win_add()를 통해 윈도우를 생성하는 과정에서는 내부적으로 윈도우 관리자가 개입하며, 윈도우 관리자는 전달받은 윈도우 이름을 식별하여 경우에 따라 어떤 작업을 수행할 수도 있지만 일반 애플리케이션에서는 큰 의미는 없다. 여러분의 독자적인 이름을 정하여 전달하면 된다.

마지막 세 번째 매개변수는 윈도우 타입을 전달받는다. Elm_Win_Type 이라는 열거형이 선언되어 있는데, 우리는 다음과 같은 값들을 이용할 수 있다.

ELM_WIN_BASIC - 일반 윈도우 타입. 일반적으로는 이 값을 이용한다.
ELM_WIN_DIALOG_BASIC - 다이얼로그 윈도우 타입
ELM_WIN_DESKTOP – 데스크탑 배경 화면처럼 아이콘이 배치될 수 있는 백그라운드 윈도우 타입
ELM_WIN_DOCK - 도킹이나 패널 윈도우 타입. 보통 다른 윈도우들 보다 상단에 존재한다.
ELM_WIN_TOOLBAR - 툴바와 같이 위치가 고정되어 있지 않고 분리되어 이동할 수 있는 타입
ELM_WIN_MENU - ELM_WIN_TOOLBAR와 거의 유사
ELM_WIN_NOTIFICATION - 경고 메시지나 이메일 도착을 알려주는 알림 윈도우 타입
ELM_WIN_DND - 타 윈도우와 드래그 앤 드롭(Drag & Drop) 기능을 지원하기 위한 윈도우 타입. elm_win_override_set() API를 이용해야 한다.
ELM_WIN_INLINED_IMAGE - 어떤 특정 이미지 버퍼에 그려질 윈도우. 윈도우뿐만 아니라 윈도우 내의 콘텐츠 역시 동일한 버퍼에 그려질 것이다. 주의할 점은 반드시 부모 윈도우를 가져야 하며 이 타입의 윈도우는 자식 윈도우로서 버퍼에 그려진다. 사용자는 이 버퍼의 데이터를 조작하거나 Evas_Map등을 이용하여 GUI 이펙트를 적용할 수 있다.
ELM_WIN_SOCKET_IMAGE - ELM_WIN_INLINE_IMAGE 타입과 동일하게 이 윈도우는 이미지 버퍼에 그려진다. 하지만 이 이미지 버퍼는 다른 EFL 애플리케이션과 공유되며, 플러그(Plug) 오브젝트가 해당 이미지를 대신 그려줄 수 있다. 달리 말하면, 윈도우 출력물을 타 프로세스들과 공유할 때 쓰인다.

추후에는 더 많은 윈도우 타입이 지원될 수 있으므로 필요하다면 최신 Elementary 문서를 참고하길 바란다. 일반적인 애플리케이션이라면 ELM_WIN_BASIC 타입의 윈도우가 널리 쓰이므로 여러분은 예제와 같이 ELM_WIN_BASIC을 전달해야 할 것이다.

자, 다음 API를 살펴보면 생성한 윈도우에 타이틀을 지정한다. 이 타이틀은 보통 윈도우 관리자에 의해 윈도우 상단 타이틀 바에 표시될 것이다.

void elm_win_title_set(Evas_Object *obj, const char *title);

예제와 같이, 이 API는 윈도우 오브젝트와 표시하고자 하는 윈도우 타이틀 문자열을 전달받는다.

다음에 이어지는 세 개의 API들은 윈도우 크기와 위치를 지정하고 화면 상에 보이겠다고 명시하는 API들이다.

void evas_object_move(Evas_Object *obj, Evas_Coord x, Evas_Coord y);

void evas_object_resize(Evas_Object *obj, Evas_Coord w, Evas_Coord h);

void evas_object_show(Evas_Object *obj);

각 API들의 이름 및 매개변수는 상당히 직관적이라서 추가적인 설명이 필요 없을 듯 하다. 다만 앞서 살펴본 Elementary API들과 비교하여 뭔가 다른 점이 보였을 텐데, 이들의 API들은 접두사가 elm이 아닌 evas로 시작하고 있다. 말 그대로, 앞서 살펴봤던 API들은 Elementary 라이브러리에서 제공하던 API들이고 이 세 개의 API들은 Evas 라이브러리에서 제공하는 API들이다. 실제로 Elementary는 Evas를 기반으로 작성된 라이브러리이며 Evas_Object 인터페이스 역시 Evas에서 정의하고 있다. Evas에서는 이러한 추상화된 Evas_Object 인터페이스를 정의함과 동시에 이 인터페이스로 사용할 수 있는 일반적인 기능들을 제공하는데 그러한 기능들 중 일부가 바로 move, resize, show와 같은 API들이다.

이 후에도 여러분은 이 책의 예제들을 통해, Evas, Elementary와 같은 서로 다른 EFL 라이브러리 간의 기능들이 혼재되어 사용되는 경우를 종종 목격할 수 있을 것이다. 그리고 여러분이 어느 정도 EFL에 숙달된다면, 필요한 기능들을 적절한 EFL 라이브러리에서 가져다 쓸 수 있을 것이다.

좌우지간, 이렇게 함으로써 우리는 480 x 400 픽셀 사이즈의 윈도우를 하나 생성하고 100, 100 좌표에 이 윈도우를 위치시켜 화면에 보여주도록 프로그래밍을 했다. 자 그럼, 결과물을 확인해 보도록 하자.


그림 2. 윈도우 생성 결과 화면


만약 프로그램을 가동시킨 콘솔 창 이외에 윈도우가 하나 더 추가적으로 생성되었다면 여러분은 이번 예제를 성공적으로 수행했음을 확신할 수 있다. 물론 윈도우 내에는 빈 영역만 존재하므로 다소 싱겁긴 마찬가지일 수도 있다. 필자도 동의하므로 잠시 휴식을 갖은 후, 다음 절에서는 이 윈도우 내부에 뭔가를 조금 채워보도록 하자.


저작자 표시
신고