GoSuda

Creazione di GUI con Tk in Go

By Yunjin Lee
views ...

ํŒŒ์ด์ฌ์—๋Š” Tkinter ์™€ ๊ฐ™์€ GUI ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๊ฐ€ ๊ธฐ๋ณธ์ ์œผ๋กœ ๋‚ด์žฅ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. ์ตœ๊ทผ์— Go ์–ธ์–ด์—์„œ๋„ Tcl/Tk๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก CGo-Free, Cross Platform Tk ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๊ฐ€ ๊ฐœ๋ฐœ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ์˜ค๋Š˜์€ ๊ทธ ๊ธฐ์ดˆ์ ์ธ ์‚ฌ์šฉ๋ฒ•์„ ์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

Hello, Tk ๋งŒ๋“ค๊ธฐ

๋จผ์ € ๊ฐ„๋‹จํ•œ 'Hello, TK!' ์˜ˆ์ œ๋กœ ์‹œ์ž‘ํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

 1package main
 2
 3import tk "modernc.org/tk9.0"
 4
 5func main() {
 6    tk.Pack(
 7        tk.TButton(
 8            tk.Txt("Hello, TK!"),
 9            tk.Command(func() {
10                tk.Destroy(tk.App)
11            })),
12        tk.Ipadx(10), tk.Ipady(5), tk.Padx(15), tk.Pady(10),
13    )
14    tk.App.Wait()
15}

hello-tk ์‹คํ–‰ ๊ฒฐ๊ณผ


์œ„ ์˜ˆ์ œ ์ฝ”๋“œ์™€ ์‹คํ–‰ ๊ฒฐ๊ณผ๋ฅผ ์ž์„ธํžˆ ์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

ํŒŒ์ด์ฌ์˜ Tk๋ฅผ ์‚ฌ์šฉํ•ด๋ณธ ๊ฒฝํ—˜์ด ์žˆ๋Š” ๋ถ„์ด๋ผ๋ฉด, ์ฐฝ ์•ˆ์— ์œ„์ ฏ์ด ํŒจํ‚น๋˜๊ฑฐ๋‚˜ ์ฐฝ ํ•˜์œ„์— ์ง์ ‘ ์œ„์ ฏ์ด ํŒจํ‚น๋˜๋Š” ๊ตฌ์กฐ๋ฅผ ์ดํ•ดํ•˜์‹ค ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์œ„์ ฏ์˜ ์ข…๋ฅ˜์— ๋”ฐ๋ผ ๋ผ๋ฒจ ๋“ฑ์ด ๊ทธ ์•ˆ์— ํฌํ•จ๋ฉ๋‹ˆ๋‹ค.

Ipadx์™€ Ipady๋Š” Internal padding์˜ ์•ฝ์ž๋กœ, ๋‚ด๋ถ€ ์œ„์ ฏ๋“ค์˜ ์—ฌ๋ฐฑ์„ ์กฐ์ ˆํ•ฉ๋‹ˆ๋‹ค. ์ด ์˜ˆ์ œ์—์„œ๋Š” ๋ฒ„ํŠผ์˜ ์—ฌ๋ฐฑ์ด ์กฐ์ •๋ฉ๋‹ˆ๋‹ค.

์ด ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์—๋Š” Window ๊ตฌ์กฐ์ฒด๊ฐ€ ์žˆ์œผ๋ฉฐ, App์ด๋ผ๋Š” ๋ณ€์ˆ˜๊ฐ€ ์ตœ์ƒ์œ„ ์ฐฝ์„ ๊ด€๋ฆฌํ•ฉ๋‹ˆ๋‹ค. ์ด๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ๋‚ด๋ถ€์— ๋ฏธ๋ฆฌ ์ •์˜๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ tk.App.Wait()๋ฅผ ์ข…๋ฃŒํ•˜๋Š” tk.App.Destroy() ํ•จ์ˆ˜๊ฐ€ ์ตœ์ƒ์œ„ ์ฐฝ์„ ๋‹ซ๋Š” ์—ญํ• ์„ ํ•ฉ๋‹ˆ๋‹ค.

์ด์ œ GitLab์˜ _examples ํด๋”์— ์žˆ๋Š” ๋ช‡ ๊ฐ€์ง€ ์˜ˆ์ œ๋ฅผ ์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

SVG ํŒŒ์ผ ์ฒ˜๋ฆฌํ•˜๊ธฐ

๋‹ค์Œ์€ SVG ํŒŒ์ผ์„ ๋ผ๋ฒจ ์œ„์ ฏ์— ํ‘œ์‹œํ•˜๋Š” ์˜ˆ์ œ์ž…๋‹ˆ๋‹ค.

 1package main
 2
 3import . "modernc.org/tk9.0"
 4
 5// https://en.wikipedia.org/wiki/SVG
 6const svg = `<?xml version="1.0" encoding="UTF-8" standalone="no"?>
 7<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
 8<svg width="391" height="391" viewBox="-70.5 -70.5 391 391" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
 9<rect fill="#fff" stroke="#000" x="-70" y="-70" width="390" height="390"/>
10<g opacity="0.8">
11    <rect x="25" y="25" width="200" height="200" fill="lime" stroke-width="4" stroke="pink" />
12    <circle cx="125" cy="125" r="75" fill="orange" />
13    <polyline points="50,150 50,200 200,200 200,100" stroke="red" stroke-width="4" fill="none" />
14    <line x1="50" y1="50" x2="200" y2="200" stroke="blue" stroke-width="4" />
15</g>
16</svg>`
17
18func main() {
19    Pack(Label(Image(NewPhoto(Data(svg)))),
20        TExit(),
21        Padx("1m"), Pady("2m"), Ipadx("1m"), Ipady("1m"))
22    App.Center().Wait()
23}

go-tk-svg ์‹คํ–‰ ๊ฒฐ๊ณผ

์ด ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์—์„œ SVG๋ฅผ ์ฒ˜๋ฆฌํ•˜๋Š” ๋ฐฉ๋ฒ•์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค:

  1. SVG ํŒŒ์ผ์˜ ๋‚ด์šฉ์„ ๋ฌธ์ž์—ด๋กœ ์ฝ์–ด๋“ค์ž…๋‹ˆ๋‹ค(๋˜๋Š” ์œ„ ์˜ˆ์‹œ์ฒ˜๋Ÿผ ์ง์ ‘ ํฌํ•จ์‹œํ‚ต๋‹ˆ๋‹ค).
  2. ์ด ๋‚ด์šฉ์„ Data ํ•จ์ˆ˜์— ์ „๋‹ฌํ•˜์—ฌ ์˜ต์…˜์ด ํฌํ•จ๋œ ๋ฌธ์ž์—ด๋กœ ๋ณ€ํ™˜ํ•ฉ๋‹ˆ๋‹ค(-data ์˜ต์…˜).
  3. ๋ณ€ํ™˜๋œ ๋ฐ”์ดํŠธ๊ฐ’์€ NewPhoto ํ•จ์ˆ˜๋กœ ์ „๋‹ฌ๋˜์–ด Tcl/Tk ์ด๋ฏธ์ง€๋ฅผ ํ‘œํ˜„ํ•˜๋Š” Img ๊ตฌ์กฐ์ฒด ํฌ์ธํ„ฐ๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.
  4. Image ํ•จ์ˆ˜๋ฅผ ํ†ต๊ณผํ•˜๋ฉด์„œ Img ๊ตฌ์กฐ์ฒด ํฌ์ธํ„ฐ๋Š” -Image ์˜ต์…˜์ด ์ถ”๊ฐ€๋œ ๋ฌธ์ž์—ด๋กœ ๋ณ€ํ™˜๋ฉ๋‹ˆ๋‹ค.
  5. ๊ตฌ์กฐ์ฒด RAW ๊ฐ’์„ ๋‹ด์€ ๋ฌธ์ž์—ด๋กœ ๋ณ€ํ™˜ํ•˜๋Š” ์ด์œ ๋Š” Label ์œ„์ ฏ ์ƒ์„ฑ์„ ์œ„ํ•ด์„œ์ž…๋‹ˆ๋‹ค.

ICO์™€ PNG ํŒŒ์ผ๋„ ๋น„์Šทํ•œ ๋ฐฉ์‹์œผ๋กœ ์ฒ˜๋ฆฌ๋ฉ๋‹ˆ๋‹ค.

PNG ํŒŒ์ผ ์ฒ˜๋ฆฌํ•˜๊ธฐ

 1package main
 2
 3import _ "embed"
 4import . "modernc.org/tk9.0"
 5
 6//go:embed gopher.png
 7var gopher []byte
 8
 9func main() {
10    Pack(Label(Image(NewPhoto(Data(gopher)))),
11        TExit(),
12        Padx("1m"), Pady("2m"), Ipadx("1m"), Ipady("1m"))
13    App.Center().Wait()
14}

go-tk-png ์‹คํ–‰๊ฒฐ๊ณผ

PNG ํŒŒ์ผ ์ฒ˜๋ฆฌ ๊ณผ์ •์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค:

  1. ์ž„๋ฒ ๋”ฉ๋œ gopher.png๋ฅผ ์˜ต์…˜์ด ํฌํ•จ๋œ ๋ฌธ์ž์—ด ํƒ€์ž…์œผ๋กœ ๋ณ€ํ™˜ํ•ฉ๋‹ˆ๋‹ค.
  2. NewPhoto ํ•จ์ˆ˜๋ฅผ ํ†ตํ•ด *Img ํƒ€์ž…์œผ๋กœ ๋ณ€ํ™˜ํ•ฉ๋‹ˆ๋‹ค.
  3. Image ํ•จ์ˆ˜๋ฅผ ๊ฑฐ์ณ RAW ๋ฌธ์ž์—ด๋กœ ๋ณ€ํ™˜๋œ ํ›„, ๋ผ๋ฒจ ์œ„์ ฏ์œผ๋กœ ์ƒ์„ฑ๋ฉ๋‹ˆ๋‹ค.

ICO ํŒŒ์ผ๋„ ๋™์ผํ•œ ๋ฐฉ์‹์œผ๋กœ ์ฒ˜๋ฆฌ๋˜๋ฉฐ, SVG ํฌ๋งท๊ณผ์˜ ์ฐจ์ด์ ์€ Data ํ•จ์ˆ˜ ๋‚ด๋ถ€ ์ฒ˜๋ฆฌ ๋ฐฉ์‹๋ฟ์ž…๋‹ˆ๋‹ค.

์—ฌ๊ธฐ์„œ "์˜ต์…˜์ด ํฌํ•จ๋œ ๋ฌธ์ž์—ด"์˜ ์ •์ฒด๋ฅผ ์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค:

1type rawOption string

์•ž์„œ ์–ธ๊ธ‰ํ•œ ์˜ต์…˜์ด ํฌํ•จ๋œ ๋ฌธ์ž์—ด์€ ๋‹จ์ˆœํžˆ ํฌ๋งทํŒ…๋œ ๋ฌธ์ž์—ด์— ๋ถˆ๊ณผํ•ฉ๋‹ˆ๋‹ค.

1func (w *Window) optionString(_ *Window) string {
2    return w.String()
3}

optionString ๋ฉ”์„œ๋“œ๋Š” Window ํฌ์ธํ„ฐ์— ๋Œ€ํ•œ ๋ฉ”์„œ๋“œ๋กœ, ๋ฌธ์ž์—ด์„ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.

๋งˆ์ง€๋ง‰์œผ๋กœ Data ํ•จ์ˆ˜์˜ ๋‚ด๋ถ€ ๊ตฌ์กฐ๋ฅผ ๊ฐ„๋‹จํžˆ ์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค:

 1func Data(val any) Opt {
 2    switch x := val.(type) {
 3    case []byte:
 4        switch {
 5        case bytes.HasPrefix(x, pngSig):
 6            // ok
 7        case bytes.HasPrefix(x, icoSig):
 8            b := bytes.NewBuffer(x)
 9            img, err := ico.Decode(bytes.NewReader(x))
10            if err != nil {
11                fail(err)
12                return rawOption("")
13            }
14
15            b.Reset()
16            if err := png.Encode(b, img); err != nil {
17                fail(err)
18                return rawOption("")
19            }
20
21            val = b.Bytes()
22        }
23    }
24    return rawOption(fmt.Sprintf(`-data %s`, optionString(val)))
25}

์ฝ”๋“œ๋ฅผ ๋ณด๋ฉด ICO๋‚˜ PNG ํŒŒ์ผ์˜ ๊ฒฝ์šฐ ์ธ์ฝ”๋”ฉ/๋””์ฝ”๋”ฉ ๊ณผ์ •์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. ๊ทธ ์™ธ์˜ ๊ฒฝ์šฐ์—๋Š” ํŠน๋ณ„ํ•œ ๋ณ€ํ™˜ ์—†์ด ๋ฐ”์ดํŠธํ˜•์œผ๋กœ ๋ณ€ํ™˜๋œ ๋ฌธ์ž์—ด์— -data ์˜ต์…˜๋งŒ ์ถ”๊ฐ€ํ•˜์—ฌ Data ํ•จ์ˆ˜์˜ ๊ฒฐ๊ณผ๋ฌผ์ž„์„ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค.

๋ฉ”๋‰ด ์œ„์ ฏ์œผ๋กœ ์ด๋ฏธ์ง€ ๋ถˆ๋Ÿฌ์˜ค๊ธฐ

์•ž์„œ ์—ฐ์Šตํ•œ PNG, ICO ๋ถˆ๋Ÿฌ์˜ค๊ธฐ ์˜ˆ์ œ์— ๋ฉ”๋‰ด ์œ„์ ฏ์„ ์ถ”๊ฐ€ํ•˜๋ฉด ํ•„์š”ํ•œ ์ด๋ฏธ์ง€๋ฅผ ํ‘œ์‹œํ•˜๋Š” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๋งŒ๋“ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋จผ์ € ๊ฐ„๋‹จํ•œ ๋ฉ”๋‰ด ์œ„์ ฏ ์˜ˆ์ œ๋ฅผ ์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

 1package main
 2
 3import (
 4    "fmt"
 5    . "modernc.org/tk9.0"
 6    "runtime"
 7)
 8
 9func main() {
10    menubar := Menu()
11
12    fileMenu := menubar.Menu()
13    fileMenu.AddCommand(Lbl("New"), Underline(0), Accelerator("Ctrl+N"))
14    fileMenu.AddCommand(Lbl("Open..."), Underline(0), Accelerator("Ctrl+O"), Command(func() { GetOpenFile() }))
15    Bind(App, "<Control-o>", Command(func() { fileMenu.Invoke(1) }))
16    fileMenu.AddCommand(Lbl("Save As..."), Underline(5))
17    fileMenu.AddSeparator()
18    fileMenu.AddCommand(Lbl("Exit"), Underline(1), Accelerator("Ctrl+Q"), ExitHandler())
19    Bind(App, "<Control-q>", Command(func() { fileMenu.Invoke(4) }))
20    menubar.AddCascade(Lbl("File"), Underline(0), Mnu(fileMenu))
21
22    App.WmTitle(fmt.Sprintf("%s on %s", App.WmTitle(""), runtime.GOOS))
23    App.Configure(Mnu(menubar), Width("8c"), Height("6c")).Wait()
24}

go-tk-๋ฉ”๋‰ด๋ฐ”-์‹คํ–‰๊ฒฐ๊ณผ

์ด ์˜ˆ์ œ์—์„œ๋Š” ๋ฉ”๋‰ด ๋ฐ”์™€ ๋ฉ”๋‰ด ์ƒ์„ฑ, ๊ธ€์ž ๊ฐ•์กฐ, Command ์˜ต์…˜, ๋‹จ์ถ•ํ‚ค ๋ฐ”์ธ๋”ฉ, ๊ทธ๋ฆฌ๊ณ  ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ฐฝ์˜ ์ดˆ๊ธฐ ํฌ๊ธฐ๋ฅผ ์„ค์ •ํ•ด๋ณด์•˜์Šต๋‹ˆ๋‹ค.

์ด์ œ GetOpenFile๋กœ ์ง€์ •๋œ Command ํ•จ์ˆ˜๋ฅผ ์ˆ˜์ •ํ•˜์—ฌ ์ด๋ฏธ์ง€๋ฅผ ๋ถˆ๋Ÿฌ์™€ ํ‘œ์‹œํ•˜๋Š” ํ”„๋กœ๊ทธ๋žจ์„ ๋งŒ๋“ค์–ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

ํ”„๋กœ๊ทธ๋žจ ์ž‘์„ฑ ๊ณ„ํš์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค:

  1. PNG์™€ ICO ํŒŒ์ผ๋งŒ ์—ด ์ˆ˜ ์žˆ๋„๋ก ์ œํ•œ
  2. ํŒŒ์ผ ๋‹ค์ด์–ผ๋กœ๊ทธ์—์„œ ์„ ํƒํ•œ ํŒŒ์ผ ์ฒ˜๋ฆฌ
  3. ์ด๋ฏธ์ง€ ํ‘œ์‹œ๋ฅผ ์œ„ํ•œ ์œ„์ ฏ ๊ตฌํ˜„

๋‹ค์Œ์€ ์ด๋Ÿฌํ•œ ๊ณ„ํš์„ ๋ฐ˜์˜ํ•œ ์ฝ”๋“œ์ž…๋‹ˆ๋‹ค:

 1package main
 2
 3import (
 4    "fmt"
 5    "log"
 6    "os"
 7    "runtime"
 8    "strings"
 9
10    . "modernc.org/tk9.0"
11)
12
13func handleFileOpen() {
14    s := GetOpenFile()
15    for _, photo := range s {
16        formatCheck := strings.Split(photo, ".")
17        format := formatCheck[len(formatCheck)-1]
18        
19        if (strings.Compare(format, "png") == 0) || (strings.Compare(format, "ico") == 0) {
20            picFile, err := os.Open(photo)
21            if err != nil {
22                log.Println("Error while opening photo, error is: ", err)
23            }
24
25            pictureRawData := make([]byte, 10000*10000)
26            picFile.Read(pictureRawData)
27
28            imageLabel := Label(Image(NewPhoto(Data(pictureRawData))))
29            Pack(imageLabel,
30                TExit(),
31                Padx("1m"), Pady("2m"), Ipadx("1m"), Ipady("1m"))
32        }
33        picFile.Close()
34    }
35}
36
37func main() {
38    menubar := Menu()
39
40    fileMenu := menubar.Menu()
41    fileMenu.AddCommand(Lbl("Open..."), Underline(0), Accelerator("Ctrl+O"), Command(handleFileOpen))
42    Bind(App, "<Control-o>", Command(func() { fileMenu.Invoke(0) }))
43    fileMenu.AddCommand(Lbl("Exit"), Underline(1), Accelerator("Ctrl+Q"), ExitHandler())
44    Bind(App, "<Control-q>", Command(func() { fileMenu.Invoke(1) }))
45    menubar.AddCascade(Lbl("File"), Underline(0), Mnu(fileMenu))
46
47    App.WmTitle(fmt.Sprintf("%s on %s", App.WmTitle(""), runtime.GOOS))
48    App.Configure(Mnu(menubar), Width("10c"), Height("10c")).Wait()
49}

go-tk-์ด๋ฏธ์ง€-๋ถˆ๋Ÿฌ์˜ค๊ธฐ-์‹คํ–‰๊ฒฐ๊ณผ

์œ„ ์ฝ”๋“œ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๋ฐฉ์‹์œผ๋กœ ์ž‘๋™ํ•ฉ๋‹ˆ๋‹ค:

  1. strings ํŒจํ‚ค์ง€์˜ ๋ฌธ์ž์—ด ๋น„๊ต ํ•จ์ˆ˜๋กœ ํŒŒ์ผ ํ™•์žฅ์ž๋ฅผ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค
  2. os ํŒจํ‚ค์ง€๋ฅผ ์‚ฌ์šฉํ•ด ํŒŒ์ผ์„ ์—ด๊ณ  ์ฝ์€ ํ›„ ๋‹ซ์Šต๋‹ˆ๋‹ค
  3. ์ฝ์–ด๋“ค์ธ ์ด๋ฏธ์ง€๋Š” ๋ ˆ์ด๋ธ” ์œ„์ ฏ์œผ๋กœ ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค

์ •๋ฆฌ

์ด๋ฒˆ ๊ธ€์—์„œ๋Š” Go ์–ธ์–ด์˜ Tcl/Tk ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ํ™œ์šฉํ•˜์—ฌ ๋‹ค์Œ ๋‚ด์šฉ์„ ๋‹ค๋ค˜์Šต๋‹ˆ๋‹ค:

  1. GUI ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๊ธฐ๋ณธ ๊ตฌ์กฐ
  2. SVG, PNG, ICO ๋“ฑ ๋‹ค์–‘ํ•œ ์ด๋ฏธ์ง€ ํฌ๋งท ์ฒ˜๋ฆฌ
  3. ์œ„์ ฏ ํŒจํ‚น๊ณผ ๋ ˆ์ด์•„์›ƒ ๊ด€๋ฆฌ
  4. ์ด๋ฏธ์ง€ ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ ๊ตฌ์กฐ
  5. ๋‹จ์ถ•ํ‚ค ๋ฐ”์ธ๋”ฉ๊ณผ ์œ„์ ฏ ์ปค๋งจ๋“œ

Go ์–ธ์–ด์™€ Tcl/Tk๋ฅผ ํ•จ๊ป˜ ์‚ฌ์šฉํ•˜๋ฉด ๊ฐ„๋‹จํ•˜๋ฉด์„œ๋„ ์‹ค์šฉ์ ์ธ GUI ํ”„๋กœ๊ทธ๋žจ์„ ๋งŒ๋“ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋ฅผ ๋ฐ”ํƒ•์œผ๋กœ ๋” ๋ณต์žกํ•œ GUI ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๊ฐœ๋ฐœ์— ๋„์ „ํ•ด๋ณด์‹œ๊ธฐ ๋ฐ”๋ž๋‹ˆ๋‹ค.