# LVGL Principles

## 1. ภาพรวมของ LVGL

#### LVGL คืออะไร?

**LVGL (Light and Versatile Graphics Library)** เป็น Open-source Graphics Library สำหรับระบบ Embedded ที่ต้องการสร้าง UI บนหน้าจอ

<figure><img src="https://1856353139-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MClo3nC-1US0rbK8Qau%2Fuploads%2F1LY5YemmWcgfudylGNiM%2FEmbedded%20Systems%20and%20IoT-Page-12.drawio.png?alt=media&#x26;token=b0b9e33c-2ae2-449f-8f4e-34364a6aa7f0" alt=""><figcaption></figcaption></figure>

***

## 2. สถาปัตยกรรมของ LVGL

#### 2.1 Object Model

| องค์ประกอบ | คำอธิบาย                      | ตัวอย่าง                  |
| ---------- | ----------------------------- | ------------------------- |
| **Screen** | หน้าจอหลัก (Container สูงสุด) | `lv_screen_active()`      |
| **Object** | วัตถุพื้นฐาน (Base class)     | `lv_obj_t *`              |
| **Widget** | วัตถุที่มี function เฉพาะทาง  | Button, Label, Slider     |
| **Part**   | ส่วนประกอบย่อยของ Widget      | MAIN, INDICATOR, KNOB     |
| **State**  | สถานะของ Object               | DEFAULT, PRESSED, CHECKED |

#### WHY: ทำไมต้องเข้าใจ Object Model?

* UI ทุกตัวใน LVGL เป็น Object ที่มี Parent-Child relationship
* การจัดการ Memory และ Layout ขึ้นอยู่กับโครงสร้างนี้
* Event จะ propagate ตาม hierarchy

#### HOW: การประยุกต์ใช้จริง

<figure><img src="https://1856353139-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MClo3nC-1US0rbK8Qau%2Fuploads%2FRzncdLYccsuVFI1i4KIK%2FEmbedded%20Systems%20and%20IoT-Page-12.drawio-2.png?alt=media&#x26;token=9a60e1ee-6339-435e-89e5-908e0449629a" alt=""><figcaption></figcaption></figure>

#### CAUTION: ข้อควรระวัง

* ห้าม delete parent ก่อน children (จะ crash)
* Object ที่สร้างแล้วจะถูก LVGL จัดการ Memory (ห้าม free เอง)
* Screen เดียวกันไม่ควรมี Object เกิน \~100 ตัว (performance)

#### Flowchart การวาง Widget

<figure><img src="https://1856353139-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MClo3nC-1US0rbK8Qau%2Fuploads%2FKAbh7scFxlSbV0I8xzcp%2FEmbedded%20Systems%20and%20IoT-Page-12.drawio-3.png?alt=media&#x26;token=5fad7b9c-98a6-482e-af5b-cb5f4d72bbe8" alt=""><figcaption></figcaption></figure>

***

## 3. Basic Widgets

#### 3.1 Label - แสดงข้อความ

**ตัวอย่างจาก: `examples/get_started/lv_example_get_started_1.c`**

```c
void lv_example_get_started_1(void)
{
    /* เปลี่ยนสีพื้นหลังของ Screen */
    lv_obj_set_style_bg_color(lv_screen_active(),
                              lv_color_hex(0x003a57),
                              LV_PART_MAIN);

    /* สร้าง Label */
    lv_obj_t * label = lv_label_create(lv_screen_active());
    lv_label_set_text(label, "Hello world");

    /* ตั้งค่าสีข้อความ */
    lv_obj_set_style_text_color(lv_screen_active(),
                                lv_color_hex(0xffffff),
                                LV_PART_MAIN);

    /* จัดตำแหน่งไว้กลางจอ */
    lv_obj_align(label, LV_ALIGN_CENTER, 0, 0);
}
```

| Function                  | หน้าที่                  | Parameter                               |
| ------------------------- | ------------------------ | --------------------------------------- |
| `lv_label_create()`       | สร้าง Label widget       | parent object                           |
| `lv_label_set_text()`     | ตั้งค่าข้อความ           | object, string                          |
| `lv_label_set_text_fmt()` | ตั้งค่าข้อความแบบ format | object, format, ...                     |
| `lv_obj_align()`          | จัดตำแหน่ง               | object, alignment, x\_offset, y\_offset |

#### WHY: ทำไม Label สำคัญ?

* ใช้แสดงค่าจาก Sensor (ADC, IMU, Temperature)
* Debug output บนหน้าจอแทน UART
* Status message ให้ผู้ใช้

#### CAUTION: ข้อควรระวัง Label

* ข้อความยาวเกินไปจะ overflow (ใช้ `lv_label_set_long_mode()`)
* `lv_label_set_text()` จะ copy string ทุกครั้ง (memory allocation)
* ใช้ `lv_label_set_text_static()` ถ้า string เป็น constant

***

#### 3.2 Button - ปุ่มกด

**ตัวอย่างจาก: `examples/get_started/lv_example_get_started_2.c`**

```c
static void btn_event_cb(lv_event_t * e)
{
    lv_event_code_t code = lv_event_get_code(e);
    lv_obj_t * btn = lv_event_get_target(e);

    if(code == LV_EVENT_CLICKED) {
        static uint8_t cnt = 0;
        cnt++;

        /* ดึง Label ที่เป็น child ของ Button */
        lv_obj_t * label = lv_obj_get_child(btn, 0);
        lv_label_set_text_fmt(label, "Button: %d", cnt);
    }
}

void lv_example_get_started_2(void)
{
    /* สร้าง Button */
    lv_obj_t * btn = lv_button_create(lv_screen_active());
    lv_obj_set_pos(btn, 10, 10);           /* กำหนดตำแหน่ง */
    lv_obj_set_size(btn, 120, 50);         /* กำหนดขนาด */

    /* ลงทะเบียน Event callback */
    lv_obj_add_event_cb(btn, btn_event_cb, LV_EVENT_ALL, NULL);

    /* สร้าง Label ใน Button */
    lv_obj_t * label = lv_label_create(btn);
    lv_label_set_text(label, "Button");
    lv_obj_center(label);
}
```

#### WHY: ทำไม Button สำคัญ?

* เป็น input หลักสำหรับ user interaction
* ใช้ควบคุม GPIO (ON/OFF LED, Motor)
* Trigger การทำงานต่างๆ (Start measurement, Reset)

#### HOW: เชื่อมโยงกับ GPIO จากสัปดาห์ที่ 1

```c
/* Button บน UI → ควบคุม LED จริง */
static void btn_led_event_cb(lv_event_t * e)
{
    lv_event_code_t code = lv_event_get_code(e);

    if(code == LV_EVENT_CLICKED) {
        /* Toggle LED - เชื่อมโยงกับ Week 1 GPIO */
        Cy_GPIO_Inv(CYBSP_USER_LED1_PORT, CYBSP_USER_LED1_PIN);
    }
}
```

***

#### 3.3 Switch - สวิตช์เปิด/ปิด

**ตัวอย่างจาก: `examples/widgets/switch/lv_example_switch_1.c`**

```c
static void switch_event_handler(lv_event_t * e)
{
    lv_event_code_t code = lv_event_get_code(e);
    lv_obj_t * obj = lv_event_get_target(e);

    if(code == LV_EVENT_VALUE_CHANGED) {
        /* ตรวจสอบสถานะ Switch */
        bool is_on = lv_obj_has_state(obj, LV_STATE_CHECKED);

        if(is_on) {
            printf("Switch: ON\n");
        } else {
            printf("Switch: OFF\n");
        }
    }
}

void create_switch_example(void)
{
    lv_obj_t * sw = lv_switch_create(lv_screen_active());
    lv_obj_center(sw);
    lv_obj_add_event_cb(sw, switch_event_handler, LV_EVENT_VALUE_CHANGED, NULL);
}
```

#### WHY: ทำไม Switch เหมาะกับ GPIO?

* แสดงสถานะ ON/OFF ชัดเจน
* เหมาะกับการควบคุม Binary Output (LED, Relay, Motor)
* User-friendly กว่า Button สำหรับ toggle state

***

#### 3.4 LED Widget - แสดงสถานะ

**ตัวอย่างจาก: `examples/widgets/led/lv_example_led_1.c`**

```c
void lv_example_led_1(void)
{
    /* LED ปิด */
    lv_obj_t * led1 = lv_led_create(lv_screen_active());
    lv_obj_align(led1, LV_ALIGN_CENTER, -80, 0);
    lv_led_off(led1);

    /* LED สีแดง ความสว่าง 150 */
    lv_obj_t * led2 = lv_led_create(lv_screen_active());
    lv_obj_align(led2, LV_ALIGN_CENTER, 0, 0);
    lv_led_set_brightness(led2, 150);
    lv_led_set_color(led2, lv_palette_main(LV_PALETTE_RED));

    /* LED เปิด */
    lv_obj_t * led3 = lv_led_create(lv_screen_active());
    lv_obj_align(led3, LV_ALIGN_CENTER, 80, 0);
    lv_led_on(led3);
}
```

| Function                  | หน้าที่             |
| ------------------------- | ------------------- |
| `lv_led_create()`         | สร้าง LED widget    |
| `lv_led_on()`             | เปิด LED (สว่างสุด) |
| `lv_led_off()`            | ปิด LED (มืด)       |
| `lv_led_set_brightness()` | ตั้งความสว่าง 0-255 |
| `lv_led_set_color()`      | ตั้งสี              |

#### WHY: ทำไมต้องมี Virtual LED?

* แสดงสถานะ Hardware LED บนหน้าจอ
* ใช้เมื่อ Physical LED ไม่พอ
* สื่อสาร Status ให้ผู้ใช้ (Error, Warning, OK)

***

## 4. Event Handling

#### 4.1 Event Callback Pattern

```c
/* รูปแบบมาตรฐานของ Event Callback */
static void my_event_cb(lv_event_t * e)
{
    /* 1. ดึงประเภท Event */
    lv_event_code_t code = lv_event_get_code(e);

    /* 2. ดึง Object ที่เกิด Event */
    lv_obj_t * target = lv_event_get_target(e);

    /* 3. ดึง User Data (ถ้ามี) */
    void * user_data = lv_event_get_user_data(e);

    /* 4. Handle แต่ละ Event Type */
    switch(code) {
        case LV_EVENT_CLICKED:
            /* ผู้ใช้คลิก */
            break;
        case LV_EVENT_VALUE_CHANGED:
            /* ค่าเปลี่ยน (Slider, Switch) */
            break;
        case LV_EVENT_PRESSED:
            /* กดค้าง */
            break;
        case LV_EVENT_RELEASED:
            /* ปล่อย */
            break;
    }
}
```

#### 4.2 Event Types ที่สำคัญ

| Event Code               | เมื่อไหร่ถูกเรียก       | ใช้กับ Widget       |
| ------------------------ | ----------------------- | ------------------- |
| `LV_EVENT_CLICKED`       | Click (Press + Release) | Button, Image       |
| `LV_EVENT_VALUE_CHANGED` | ค่าเปลี่ยน              | Slider, Switch, Arc |
| `LV_EVENT_PRESSED`       | กดค้าง                  | ทุก Object          |
| `LV_EVENT_LONG_PRESSED`  | กดค้างนาน (>400ms)      | ทุก Object          |
| `LV_EVENT_RELEASED`      | ปล่อย                   | ทุก Object          |

#### WHY: ทำไม Event-Driven สำคัญ?

* ไม่ต้อง Polling สถานะ (ประหยัด CPU)
* แยก Logic ออกจาก UI (Maintainable)
* รองรับ Multiple Events บน Object เดียว

#### HOW: ส่ง User Data ผ่าน Event

```c
/* ส่ง GPIO Port/Pin ผ่าน User Data */
typedef struct {
    GPIO_PRT_Type *port;
    uint32_t pin;
} gpio_info_t;

static gpio_info_t led1_info = {
    .port = CYBSP_USER_LED1_PORT,
    .pin = CYBSP_USER_LED1_PIN
};

static void led_toggle_cb(lv_event_t * e)
{
    gpio_info_t *gpio = (gpio_info_t *)lv_event_get_user_data(e);

    if(lv_event_get_code(e) == LV_EVENT_CLICKED) {
        Cy_GPIO_Inv(gpio->port, gpio->pin);
    }
}

void setup_led_button(void)
{
    lv_obj_t * btn = lv_button_create(lv_screen_active());
    lv_obj_add_event_cb(btn, led_toggle_cb, LV_EVENT_CLICKED, &led1_info);
}
```

#### CAUTION: ข้อควรระวัง Event

* User Data ต้อง valid ตลอด lifetime ของ Object
* ห้ามใช้ local variable เป็น User Data (จะหาย)
* หลาย Event อาจเกิดพร้อมกัน (ต้องกรองด้วย code)

#### Functions สำหรับการวางตำแหน่ง (LVGL v9.2 API)

```c
/*==========================================================================
 * 1. lv_obj_set_pos() - กำหนดตำแหน่งแบบ Absolute (จาก parent's content area)
 *    ใช้เมื่อ: ต้องการวางตำแหน่งที่แน่นอน เช่น grid layout
 *==========================================================================*/
lv_obj_set_pos(obj, x, y);        /* x, y เป็น pixels จาก top-left ของ parent */

/*==========================================================================
 * 2. lv_obj_set_x() / lv_obj_set_y() - กำหนดทีละแกน
 *    ใช้เมื่อ: ต้องการปรับแกนเดียว
 *==========================================================================*/
lv_obj_set_x(obj, 100);           /* ตั้ง x เป็น 100px */
lv_obj_set_y(obj, 50);            /* ตั้ง y เป็น 50px */

/*==========================================================================
 * 3. lv_obj_align() - จัดตำแหน่งตาม alignment พร้อม offset
 *    ใช้เมื่อ: วาง widget โดยอ้างอิงจาก parent (แนะนำ)
 *==========================================================================*/
lv_obj_align(obj, LV_ALIGN_CENTER, x_offset, y_offset);
/*                    ↑                ↑          ↑
            ประเภท alignment        เลื่อน X     เลื่อน Y

    ตัวอย่าง:
    - LV_ALIGN_CENTER, 0, 0     → กลางจอพอดี
    - LV_ALIGN_CENTER, 0, -50   → กลางจอ แต่ขยับขึ้น 50px
    - LV_ALIGN_TOP_MID, 0, 20   → กลางบน แต่ขยับลง 20px (สำหรับ title)
*/

/*==========================================================================
 * 4. lv_obj_align_to() - จัดตำแหน่งโดยอ้างอิงจาก object อื่น
 *    ใช้เมื่อ: วาง widget สัมพันธ์กับ widget อื่น
 *==========================================================================*/
lv_obj_align_to(obj, base_obj, LV_ALIGN_OUT_BOTTOM_MID, x_offset, y_offset);
/*               ↑      ↑              ↑                   ↑          ↑
              widget   อ้างอิง    วางใต้ base_obj           เลื่อน X     เลื่อน Y */

/*==========================================================================
 * 5. lv_obj_center() - วางกลาง parent อย่างง่าย
 *    ใช้เมื่อ: ต้องการวางกลางโดยไม่มี offset
 *==========================================================================*/
lv_obj_center(obj);               /* เทียบเท่า lv_obj_align(obj, LV_ALIGN_CENTER, 0, 0) */
```

## Functions สำหรับการกำหนดขนาด (Size Functions)

```c
/*==========================================================================
 * 1. lv_obj_set_size() - กำหนดทั้ง width และ height พร้อมกัน
 *==========================================================================*/
lv_obj_set_size(obj, width, height);

/* ค่าพิเศษที่ใช้ได้:
   - pixels (เช่น 200, 100)           → ขนาดคงที่
   - lv_pct(50)                       → 50% ของ parent
   - LV_SIZE_CONTENT                  → ขยายตาม content ข้างใน */

/*==========================================================================
 * 2. lv_obj_set_width() / lv_obj_set_height() - กำหนดทีละด้าน
 *==========================================================================*/
lv_obj_set_width(obj, 200);       /* กว้าง 200px */
lv_obj_set_height(obj, 100);      /* สูง 100px */
lv_obj_set_width(obj, lv_pct(80)); /* กว้าง 80% ของ parent */

/*==========================================================================
 * 3. lv_obj_set_style_pad_*() - กำหนด padding (ช่องว่างภายใน)
 *    ใช้เมื่อ: ต้องการขยายขนาด widget โดยอัตโนมัติ
 *==========================================================================*/
lv_obj_set_style_pad_hor(btn, 30, 0);  /* padding ซ้าย-ขวา 30px */
lv_obj_set_style_pad_ver(btn, 15, 0);  /* padding บน-ล่าง 15px */
/* หมายเหตุ: ดีกว่า lv_obj_set_size() สำหรับ Button เพราะขยายตาม text อัตโนมัติ */
```

#### Layout Pattern สำหรับ 480x320 Screen

**Pattern 1: Vertical Stacking (ใช้บ่อยที่สุด)**

```c
/* Title → Main Content → Description → Footer */
void create_vertical_layout(void)
{
    /* 1. Title - ชิดบน */
    lv_obj_t * title = lv_label_create(lv_screen_active());
    lv_label_set_text(title, "Example Title");
    lv_obj_align(title, LV_ALIGN_TOP_MID, 0, 20);        /* y=20 จากขอบบน */

    /* 2. Main content - กลางจอ */
    lv_obj_t * widget = lv_led_create(lv_screen_active());
    lv_obj_align(widget, LV_ALIGN_CENTER, 0, -30);       /* y=-30 ขยับขึ้นเล็กน้อย */

    /* 3. Description - ล่างสุด */
    lv_obj_t * desc = lv_label_create(lv_screen_active());
    lv_label_set_text(desc, "Description text");
    lv_obj_align(desc, LV_ALIGN_BOTTOM_MID, 0, -50);     /* y=-50 จากขอบล่าง */
}
```

**Pattern 2: Multiple Widgets at CENTER with Y-offset**

```c
/* วาง widgets หลายตัวจากกลางจอ โดยใช้ y_offset */
void create_center_stacked_layout(void)
{
    /* LED - อยู่เหนือกลางจอ */
    lv_obj_t * led = lv_led_create(lv_screen_active());
    lv_obj_align(led, LV_ALIGN_CENTER, 0, -70);          /* ขึ้นไป 70px จากกลาง */

    /* Label - กลางจอพอดี */
    lv_obj_t * label = lv_label_create(lv_screen_active());
    lv_obj_align(label, LV_ALIGN_CENTER, 0, 0);          /* กลางจอพอดี */

    /* Buttons - ล่างกลางจอ */
    lv_obj_t * btn1 = lv_button_create(lv_screen_active());
    lv_obj_align(btn1, LV_ALIGN_CENTER, -60, 50);        /* ซ้าย, ลง 50px */

    lv_obj_t * btn2 = lv_button_create(lv_screen_active());
    lv_obj_align(btn2, LV_ALIGN_CENTER, 60, 50);         /* ขวา, ลง 50px */

    /* Slider - ล่างสุดของกลุ่ม */
    lv_obj_t * slider = lv_slider_create(lv_screen_active());
    lv_obj_align(slider, LV_ALIGN_CENTER, 0, 110);       /* ลง 110px จากกลาง */
}
```

**Pattern 3: Container with Grid Layout**

```c
/* ใช้ container เมื่อมี widgets หลายตัวที่ต้องจัดเป็นกลุ่ม */
void create_container_grid_layout(void)
{
    /* Container - ขนาด 420x200 pixels */
    lv_obj_t * cont = lv_obj_create(lv_screen_active());
    lv_obj_set_size(cont, 420, 200);
    lv_obj_align(cont, LV_ALIGN_CENTER, 0, 10);
    lv_obj_clear_flag(cont, LV_OBJ_FLAG_SCROLLABLE);  /* ปิด scroll */

    /* วาง items ใน container ด้วย absolute position */
    for(int i = 0; i < 4; i++) {
        int x_pos = (i % 2) * 200 + 20;   /* 2 columns: x = 20 หรือ 220 */
        int y_pos = (i / 2) * 80 + 20;     /* 2 rows: y = 20 หรือ 100 */

        lv_obj_t * led = lv_led_create(cont);
        lv_obj_set_pos(led, x_pos, y_pos);
    }
}
```

## Widget Sizing Guidelines (แนะนำสำหรับ 800x480)

<table data-header-hidden><thead><tr><th width="100"></th><th></th><th></th></tr></thead><tbody><tr><td>Widget Type</td><td>Recommended Size (800x480)</td><td>Notes / Recommendations</td></tr><tr><td><strong>Title Label</strong></td><td>Auto (text length)</td><td>แนะนำ Font: <code>montserrat_32</code> หรือ <code>36</code> (เพื่อให้เด่นชัดบนความสูง 480px)</td></tr><tr><td><strong>Button</strong></td><td>Padding 50x25</td><td>ใช้ Padding แทนการ fix size เพื่อให้ปุ่มขยายตามขนาดตัวอักษร</td></tr><tr><td><strong>LED</strong></td><td>60x60 ถึง 120x120</td><td>ขนาด 60px เหมาะสำหรับสถานะทั่วไป, 120px สำหรับสถานะหลัก</td></tr><tr><td><strong>Switch</strong></td><td>100x50 ถึง 120x60</td><td>ปรับให้ใหญ่ขึ้นเพื่อให้กดง่าย (Touch Target ที่เหมาะสม)</td></tr><tr><td><strong>Slider</strong></td><td>Width 400-600</td><td>ความยาวควรอยู่ที่ประมาณ 50-75% ของความกว้างจอ</td></tr><tr><td><strong>Bar</strong></td><td>500x40</td><td>ปรับความหนา (Height) เป็น 40px เพื่อให้มองเห็นความก้าวหน้าชัดเจน</td></tr><tr><td><strong>Arc</strong></td><td>200x200 ถึง 350x350</td><td>ควรคงรูปทรงจัตุรัส (Square) เพื่อไม่ให้วงกลมเบี้ยว</td></tr><tr><td><strong>Chart</strong></td><td>700x400 max</td><td>ขยายให้เกือบเต็มพื้นที่หากเป็นหน้า Dashboard หลัก</td></tr><tr><td><strong>Container</strong></td><td>760x420 typical</td><td>เว้นขอบ (Margin) ด้านละ 20px เพื่อความสวยงาม</td></tr></tbody></table>

#### Common Background Colors (จาก Course Examples)

```c
/* Primary backgrounds */
#define BG_DARK_NAVY    0x003a57    /* Week3 Ex1: Hello World */
#define BG_DARK_BLUE    0x1a1a2e    /* Week3 Ex2,5: Button, Dashboard */
#define BG_DARKER       0x0f0f23    /* Week3 Ex3: LED Control */
#define BG_MIDNIGHT     0x16213e    /* Week3 Ex4, Week4: Switch, Chart */

/* Panel/Container backgrounds */
#define BG_PANEL        0x0f0f23    /* Container background */
#define BG_BORDER       0x444444    /* Container border */

/* Text colors */
#define TEXT_WHITE      0xFFFFFF    /* Main text */
#define TEXT_GRAY       0xAAAAAA    /* Description text */
#define TEXT_GREEN      0x00FF00    /* Status text */
```

#### CAUTION: ข้อควรระวังในการวาง Layout

1. **ห้ามวาง Widget นอกขอบจอ**: ค่า y ที่เกิน 319 หรือ x ที่เกิน 479 จะทำให้ widget หายไป
2. **ลำดับการสร้างสำคัญ**: Widget ที่สร้างทีหลังจะอยู่ด้านบน (z-order)
3. **align\_to ต้องทำหลัง base object ถูก position แล้ว**:

   ```c
   /* ✅ ถูก */
   lv_obj_align(slider, LV_ALIGN_CENTER, 0, 50);   /* position slider ก่อน */
   lv_obj_align_to(label, slider, LV_ALIGN_OUT_BOTTOM_MID, 0, 5);  /* แล้วค่อย align label */

   /* ❌ ผิด */
   lv_obj_align_to(label, slider, ...);   /* slider ยังไม่มี position! */
   lv_obj_align(slider, ...);
   ```
4. **ใช้ padding แทน set\_size สำหรับ Button**:

   ```c
   /* ✅ ดี - text ไม่ถูก clip */
   lv_obj_set_style_pad_hor(btn, 30, 0);
   lv_obj_set_style_pad_ver(btn, 15, 0);

   /* ⚠️ ระวัง - อาจ clip text ถ้าขนาดเล็กเกินไป */
   lv_obj_set_size(btn, 80, 30);  /* ต้องแน่ใจว่า text พอดี */
   ```
5. **Container ควรปิด scroll flag**:

   ```c
   lv_obj_clear_flag(cont, LV_OBJ_FLAG_SCROLLABLE);  /* ป้องกัน content เลื่อนได้ */
   ```
