← 都来码 / Dolaima← 例程索引python/app/weather.py原文

python/app/weather.py · 24223 字节 · 下载原文

# weather.py —— 常用小程序②:**天气**(联网抓一周预报 → 存文件 → 彩色显示)

# weather.py -- Everyday utility #2: **weather** (fetch a one-week forecast, save it, then show it)

#

# 四段:① 裸套接字取回 api.open-meteo.com 的预报(明文 HTTP,80 端口 —— VM 里没有 TLS);

# Four stages: (1) fetch the forecast from api.open-meteo.com over a raw socket (plain HTTP on port

# 80 -- there is no TLS in the VM); (2) strip the response headers and write the body into

# weather.txt (**relative path**: on the phone the sandbox root is the workspace);

# ② 剥掉响应头,把正文写进 weather.txt(**相对路径**:手机上沙箱根 = 工作区);

# (2) strip the response headers and write the body into weather.txt (**a relative path**: on the

# phone the sandbox root is the workspace and absolute paths are rejected);

# ③ 解析 CSV(**要 `format=csv`**,比 JSON 少一整层解析);④ 画一张彩色的周预报。

# (3) parse the CSV (**ask for `format=csv`**, which saves a whole parsing layer over JSON);

# (4) draw a colour week card.

#

# 这是 `Examples/c/app/weather.c` 的 Python 移植:联网那部分**照搬**(DNS → 裸套接字 → 明文

# This is the Python port of `Examples/c/app/weather.c`: the networking is ported as is

# (DNS -> raw socket -> plain HTTP on port 80 -> `format=csv`), and so is the offline fallback.

# HTTP 80 → `format=csv`),离线回落读 `weather.txt` 也照搬。

# The offline fallback reads `weather.txt` exactly like the C version does.

#

# ## Python 前端没有 `asm()` ⇒ 系统调用只能走共享库的包装(**都实测过**)

# ## This front end has no `asm()`, so syscalls go through the shared library's wrappers

#

# `Lib/python/shared.py` 里那套 `net_*` / `vml_dns_resolve` / `vml_file_*` 就是 `svc` 号

# The `net_*` / `vml_dns_resolve` / `vml_file_*` set in `Lib/python/shared.py` are thin wrappers

# 的薄包装(`net_connect` = SYSCALL #330 + #334、`net_recv` = #336、`vml_file_read` = #112 …),

# over the syscall numbers (`net_connect` = #330 + #334, `net_recv` = #336, `vml_file_read` = #112),

# 与 C 版自己包的那层 `sys_*` 一一对应。

# one for one with the `sys_*` layer the C version wraps itself.

# ⚠ 域名仍然**不能直接连**:宿主 #334 内部是 `IPAddress.Parse` ⇒ 必须先 `vml_dns_resolve`。

# ⚠ A hostname still **cannot be connected directly**: host #334 uses `IPAddress.Parse` inside, so

#   `vml_dns_resolve` has to run first.

#

# ## 收数据这一步是本移植最别扭的地方(原因不是"想不到",是**没得选**)

# ## Reading the response back is the awkward part of this port (not by choice)

#

# 本语言**没有取地址、没有 `peek`/`poke`**(`peekb(x)` 会编成 `CALL vml_peekb`,而 `Lib/` 下

# This language has **no address-of and no `peek`/`poke`** (`peekb(x)` compiles to `CALL vml_peekb`,

# 没有这个标签 —— 实测 `error: 未定义的函数 'vml_peekb'`),所以"往缓冲里收、再逐字节读回来"

# and no such label exists under `Lib/` -- measured: `error: undefined function 'vml_peekb'`), so

# 只能借共享库的两件工具:

# "receive into a buffer, then read it back byte by byte" has to lean on two shared-library tools:

#   · `str_charat(addr, i)` —— 按**地址**取一个字节(C 里叫 `buf[i]`);

#   · `str_charat(addr, i)` -- one byte **by address** (what C writes as `buf[i]`);

#   · `str_substr(dst, src, pos, count)` —— 从地址抄一段到另一个地址(C 里叫"取子串")。

#   · `str_substr(dst, src, pos, count)` -- copy a slice from one address to another.

# 收来的字节因此**从头到尾都以地址形式流动**:`ui_2d_text` 要的也是地址,两者正好同源。

# The received bytes therefore stay **addresses end to end**, which is also what `ui_2d_text`

# wants -- the two agree.

# ⚠ 代价:`str_substr` 的边界靠 `strlen(src)` 定,所以**每写一段都要自己在缓冲尾补一个 0**

# ⚠ The cost: `str_substr` bounds itself by `strlen(src)`, so **every write must NUL-terminate the

# (本文件里就是 `memset(RBUF + TOTAL, 0, 1)` 那几处)。

# buffer tail first** (the `memset(RBUF + TOTAL, 0, 1)` calls in this file).

#

# ## 为什么"空闲 8 秒就自己退出"

# ## Why it exits by itself after 8 seconds of idling

#

# 例程要能被**自动验收**(`scripts/vmlcli` 加 `--frame` 出一张图就够了),所以循环必须有界。

# Samples have to be **automatically verifiable** (one `scripts/vmlcli` run with `--frame` is

# enough), so the loop must be bounded.


# ── 显式引用:`ui_*` 来自共享调用库 `vmlui.vml`;套接字/文件/字符串助手来自共享库 ──

# ── explicit references: `ui_*` from the shared call library `vmlui.vml`; the socket/file/string

#    helpers from the shared library ──

import vml_ui
import shared

HOST = "api.open-meteo.com"
PORT = 80
OUTFILE = "weather.txt"

BUFCAP = 8192            # 响应缓冲:CSV 正文实测 ~250 字节,8K 绰绰有余

                         # Response buffer: the CSV body measures ~250 bytes, so 8K is plenty.

MAXROUNDS = 64           # 读循环上限:防"对端不关连接"时死读

                         # Read-loop cap: stops an endless read when the peer never closes.

IDLE_EXIT_MS = 8000

# 配色(真彩 AA RRGGBB)

# Colours (true colour AA RRGGBB)

C_BG = 0xFF0E1116
C_CARD = 0xFF1A1F28
C_TITLE = 0xFFE8E8F0
C_DIM = 0xFF9AA0B0
C_HI = 0xFFE06C50
C_LO = 0xFF4A90D9
C_TRACK = 0xFF2A3038
C_WARN = 0xFFE0A040

# 请求正文。C 版一段一段 `strcat` 拼(前端没有字符串拼接),本语言的字符串字面量**直接支持**

# The request body. The C version stitches it together with `strcat` because its front end has no

# string concatenation; here a plain string literal works.

# `\r\n` 转义(实测 `strlen` 对得上、服务端也认)。**必须写在一行**:本语言不做相邻字面量拼接

# (`strlen` matches and the server accepts it, measured). It **must stay on one line**: adjacent

# string literals are not concatenated here (measured: `"x" "y"` compiles down to `"x"`).

# ⚠ `format=csv` 是这一版的关键:open-meteo 支持直接回 CSV,省掉一整层 JSON 解析。

# ⚠ `format=csv` is the crux here: open-meteo can return CSV directly, saving a JSON layer.

# ⚠ 现在只留**请求路径**:请求行与那套头由库里的 `http_get_to` 拼(它那份与本例程原先手写的逐字相同)。

# ⚠ Only the **path** is kept now: `http_get_to` in the library builds the request line and headers.

PATH = "/v1/forecast?latitude=22.54&longitude=114.06&daily=temperature_2m_max,temperature_2m_min,weather_code&forecast_days=7&timezone=Asia/Shanghai&format=csv"

# ── 缓冲与状态 ────────────────────────────────────────

# ── buffers and state ─────────────────────────────────

RBUF = vml_alloc(BUFCAP)     # 整个 HTTP 响应(头 + 正文),离线时当缓存正文用

                             # The whole HTTP response (headers plus body), and the cache body when offline

S_NUM = vml_alloc(16)        # 整数转字符串 / 切字段用

                             # scratch for `itoa` and for a cut-out field

S_TB = vml_alloc(24)         # "19.8~28.1" 这种温度串

                             # the "19.8~28.1" temperature line

D_DATES = vml_alloc(96)      # 7 天 × 12 字节(只存 `MM-DD`,长度足够放下 7 段)

                             # 7 days x 12 bytes (only `MM-DD` is stored, room enough for seven)


# 画布尺寸(由顶层算好,函数只读)

# Canvas size (computed at top level, only read inside functions)

SW = 0
SH = 0
LANG = 0
OFFLINE = 0                  # 1 = 没连上,显示的是本地缓存

                             # 1 = offline, showing the local cache

D_N = 0                      # 解析出几天

                             # how many days were parsed

# 每天的最低/最高/天气代码。C 版是定长数组,本语言有列表 ⇒ 直接摆列表(函数里读、顶层写)。

# Low/high/weather code per day. The C version uses fixed arrays; this language has lists, so the

# lists are written at top level and read inside functions.

D_LO = [0, 0, 0, 0, 0, 0, 0, 0]
D_HI = [0, 0, 0, 0, 0, 0, 0, 0]
D_CODE = [0, 0, 0, 0, 0, 0, 0, 0]


# ── 小工具 ────────────────────────────────────────────

# ── small helpers ─────────────────────────────────────


# ⚠ 这里原先有两个手写的协议辅助:`find_body`(找 `\r\n\r\n`)与 `http_code`(解析状态码)——

#   库 `Lib/shared/src/http.c` 的 `http_get_to` 已经把剥头与状态码都做了,只往回装**正文**。

# The two hand-written protocol helpers used to live here; `http_get_to` covers both.

# 一个字节是不是十进制数字

# Is one byte a decimal digit

def is_digit_at(at):
    c = str_charat(RBUF, at)
    if c >= 48 and c <= 57:
        return 1
    return 0


# WMO 天气代码 → 一句话。代码表来自 open-meteo 的文档:

# WMO weather code -> a short phrase. The table follows open-meteo's docs:

# 0 晴 / 1-3 云 / 45 雾 / 5x 毛毛雨 / 6x 雨 / 7x 雪 / 8x 阵雨 / 9x 雷暴。

# 0 clear / 1-3 cloud / 45 fog / 5x drizzle / 6x rain / 7x snow / 8x showers / 9x thunderstorm.

def wmo_label(code, lg):
    if code == 0:
        return "晴" if lg == 0 else "clear"
    if code == 1 or code == 2:
        return "多云" if lg == 0 else "cloudy"
    if code == 3:
        return "阴" if lg == 0 else "overcast"
    if code == 45 or code == 48:
        return "雾" if lg == 0 else "fog"
    if code >= 51 and code <= 57:
        return "毛毛雨" if lg == 0 else "drizzle"
    if code >= 61 and code <= 67:
        return "雨" if lg == 0 else "rain"
    if code >= 71 and code <= 77:
        return "雪" if lg == 0 else "snow"
    if code >= 80 and code <= 82:
        return "阵雨" if lg == 0 else "showers"
    if code >= 95:
        return "雷暴" if lg == 0 else "storm"
    return "未知" if lg == 0 else "unknown"


# ── 绘制 ──────────────────────────────────────────────

# ── drawing ───────────────────────────────────────────

def draw():
    cx = SW // 2
    pad = SW // 18
    top = 0
    avail = 0
    cardh = 0
    i = 0
    y = 0
    h = 0
    midy = 0
    innerw = 0
    barw = 0
    n = D_N

    if pad < 6:
        pad = 6
    top = pad + 58                    # 标题两行

    # room for the two heading lines

    avail = SH - top - 40
    if n <= 0:
        n = 1
    cardh = avail // n

    ui_2d_clear(C_BG)
    ui_2d_text_styled(cx, pad, "一周预报" if LANG == 0 else "One-week forecast",
                      C_TITLE, 17, VML_ANCHOR_CENTER, VML_FONT_BOLD)
    if OFFLINE != 0:
        ui_2d_text(cx, pad + 26, "离线:读本地缓存 weather.txt" if LANG == 0
                   else "offline: local cache weather.txt", C_WARN, 11, VML_ANCHOR_CENTER)
    else:
        ui_2d_text(cx, pad + 26, "数据源 api.open-meteo.com" if LANG == 0
                   else "source api.open-meteo.com", C_DIM, 11, VML_ANCHOR_CENTER)

    i = 0
    while i < D_N:
        y = top + i * cardh
        h = cardh - 6
        midy = y + h // 2

        ui_2d_rect(pad, y, SW - pad * 2, h, C_CARD, 1, 0, 8)

        # 日期:`D_DATES + i * 12` 就是第 i 天那 5 个字节的**地址**,`ui_2d_text` 直接收

        # The date: `D_DATES + i * 12` is the **address** of that day's five bytes and

        # (C 版是"从扁平缓冲搬进一个小局部串"——本语言连这步都不用)。

        # `ui_2d_text` takes it as is (the C version first copies it into a local string).

        ui_2d_text(pad + 12, midy - 8, D_DATES + i * 12, C_TITLE, 14, VML_ANCHOR_LEFT)

        # 天气一句话

        # The condition in words

        ui_2d_text(SW - pad - 12, midy - 7, wmo_label(D_CODE[i], LANG), C_DIM, 12,
                   VML_ANCHOR_RIGHT)

        # 最低~最高 + 温度条(10℃ 起,34℃ 满条)

        # Low~high plus a temperature bar (starts at 10 C and fills at 34 C)

        itoa(D_LO[i], S_NUM)
        strcpy(S_TB, S_NUM)
        strcat(S_TB, "~")
        itoa(D_HI[i], S_NUM)
        strcat(S_TB, S_NUM)
        ui_2d_text(pad + 12, midy + 10, S_TB, C_LO, 12, VML_ANCHOR_LEFT)

        innerw = SW - pad * 2 - 24
        if innerw < 40:
            innerw = 40
        barw = (D_HI[i] - 10) * innerw // 24
        if barw < 8:
            barw = 8
        if barw > innerw:
            barw = innerw
        ui_2d_rect(pad + 12, midy + 26, innerw, 6, C_TRACK, 1, 0, 3)
        if D_HI[i] >= 30:
            ui_2d_rect(pad + 12, midy + 26, barw, 6, C_HI, 1, 0, 3)
        else:
            ui_2d_rect(pad + 12, midy + 26, barw, 6, C_LO, 1, 0, 3)

        i = i + 1

    ui_2d_present()          # ⚠ 不调这句 = 黑屏

                             # ⚠ without this call you get a black screen



# ── 主流程 ────────────────────────────────────────────

# ── main flow ─────────────────────────────────────────


LANG = ui_scr_get_language()
TOTAL = 0
HEAD = 0
BLEN = 0
FAIL = 0
IP = 0
FD = -1
N = 0
WANT = 0
ROUNDS = 0
CODE = 0
FH = 0
WROTE = 0

print_str("=== VML 天气预报 ===\n" if LANG == 0 else "=== VML Weather Report ===\n")
print_str("主机 " if LANG == 0 else "Host ")
print_str(HOST)
print_str("  文件 " if LANG == 0 else "  file ")
print_str(OUTFILE)
print_str("\n")

# ① 域名 → IP(宿主不解析域名)

# ① Hostname to IP (the host does not resolve names)

# ⚠ 解析失败时宿主把 R0 置 **-1**(不是 0),所以判据是 `<= 0`;只判 `== 0` 会把 -1

# ⚠ On a failed lookup the host sets R0 to **-1** (not 0), so the test is `<= 0`; testing only

#   `== 0` would hand -1 to the next call as if it were an address (measured on the C side).

#   as if it were an address (measured on the C side: a "memory error", not a DNS failure).

IP = vml_dns_resolve(HOST)
if IP <= 0:
    print_str("[警告] DNS 解析失败(转为离线)\n" if LANG == 0
              else "[WARN] DNS lookup failed (going offline)\n")
    OFFLINE = 1
else:
    print_str("[1/4] DNS  ")
    print_str(IP)
    print_str("\n")

# ②③ 抓数据 —— 建套接字、连接、发请求、收完整响应、剥头、状态码**全在库里**

#   ⚠ 用 `http_get_to` 而不是 `http_get`:后者把「连哪儿」与「Host 写什么」交给**同一个参数**,

#     传域名连不上(#334 不解析域名)、传 IP 则 Host 头成了 IP ⇒ 虚拟主机按 Host 路由返回 **404**。

# The rest of the fetch lives in the library's `http_get_to` (connect to the IP, `Host:` carries the name).

if OFFLINE == 0:
    CODE = http_get_to(IP, PORT, HOST, PATH, RBUF, BUFCAP)
    if CODE != 200:
        print_str("[警告] 抓取失败(转为离线)\n" if LANG == 0
                  else "[WARN] fetch failed (going offline)\n")
        OFFLINE = 1
    else:
        BLEN = strlen(RBUF)          # `http_get_to` 只往回装**正文**(头已经剥掉了)

        # it stores **only the body**; headers are already stripped

        HEAD = 0
        print_str("[3/4] HTTP ")
        print_int(CODE)
        print_str("  正文 " if LANG == 0 else "  body ")
        print_int(BLEN)
        print_str(" 字节\n" if LANG == 0 else " bytes\n")

# ④ 剥头 → 写文件(离线时跳过)

# ④ Strip the headers and write the file (skipped when offline)

if OFFLINE == 0:
    FH = vml_file_open(OUTFILE, 1)      # 1 = 只写(FileMode.Create)

    # 1 = write only

    if FH < 0:
        print_str("[警告] 打不开 " if LANG == 0 else "[WARN] cannot open ")
        print_str(OUTFILE)
        print_str("(手机上路径必须是相对的)\n" if LANG == 0
                  else " (paths must be relative on the phone)\n")
    else:
        WROTE = vml_file_write(FH, RBUF + HEAD, BLEN)
        vml_file_close(FH)
        print_str("[4/4] HTTP ")
        print_int(CODE)
        print_str("  正文 " if LANG == 0 else "  body ")
        print_int(BLEN)
        print_str(" 字节" if LANG == 0 else " bytes")
        print_str("  写入 " if LANG == 0 else "  wrote ")
        print_int(WROTE)
        print_str(" 字节 -> " if LANG == 0 else " bytes -> ")
        print_str(OUTFILE)
        print_str("\n")

# ⑤ 离线回落:读上一次留下的 weather.txt(相对路径,各宿主自己解析)

# ⑤ Offline fallback: read the weather.txt left by the last run (a relative path each host resolves)

# ⚠ `mode = 0` 是**只读**:写成 1 会把缓存文件清空再读,拿到一份空数据。

# ⚠ `mode = 0` is **read-only**: passing 1 would truncate the cache file first and hand back nothing.

if OFFLINE != 0:
    FH = vml_file_open(OUTFILE, 0)
    if FH < 0:
        print_str("[失败] 离线且没有本地缓存 " if LANG == 0 else "[FAIL] offline and no local cache ")
        print_str(OUTFILE)
        print_str(" —— 先在联网的机器上跑一次,或把预报 CSV 放到同目录。\n" if LANG == 0
                  else " -- run this once on a networked machine, or drop the forecast CSV next to it.\n")
        FAIL = 1
    else:
        BLEN = vml_file_read(FH, RBUF, BUFCAP - 1)
        vml_file_close(FH)
        if BLEN <= 0:
            print_str("[失败] 缓存文件是空的\n" if LANG == 0 else "[FAIL] the cache file is empty\n")
            FAIL = 1
        else:
            memset(RBUF + BLEN, 0, 1)
            HEAD = 0                 # 缓存里没有响应头,正文从 0 开始

            # the cache has no headers


# ⑥ 解析成结构化的"天"

# ⑥ Parse the CSV into structured days

# ⚠ C 版先切好 `line[96]` 再 `split_csv`,本语言直接在缓冲上按**下标**做(`str_charat` 读、

# ⚠ The C version copies each line into `line[96]` and calls `split_csv`; here the buffer is walked

#   by index instead (`str_charat` to read, `str_substr` to cut a field out).

#   by index instead (`str_charat` reads, `str_substr` cuts a field out).

# ⚠ 只认「日期形状」的行:第 1 段必须是 `YYYY-MM-DD`。别只判"数字开头" —— open-meteo 的 CSV

# ⚠ Only lines shaped like a date count: the first field must be `YYYY-MM-DD`. Do not settle for

#   "starts with a digit" -- line 2 of open-meteo's CSV is the latitude/longitude row

#   **第 2 行是经纬度那行**(`22.530754,114.08714,...`),数字开头,判错了会多出一行"日期"。

#   (`22.530754,114.08714,...`), which starts with a digit and would add a bogus "day".

if FAIL == 0:
    D_N = 0
    LI = HEAD
    LE = HEAD
    C1 = 0
    C2 = 0
    C3 = 0
    while LI < HEAD + BLEN:
        LE = LI
        while LE < HEAD + BLEN:
            if str_charat(RBUF, LE) == 10:
                break
            LE = LE + 1
        if LE > LI:
            if is_digit_at(LI) == 1:
                if str_charat(RBUF, LI + 4) == 45 and str_charat(RBUF, LI + 7) == 45:
                    C1 = LI
                    while C1 < LE:
                        if str_charat(RBUF, C1) == 44:
                            break
                        C1 = C1 + 1
                    C2 = C1 + 1
                    while C2 < LE:
                        if str_charat(RBUF, C2) == 44:
                            break
                        C2 = C2 + 1
                    C3 = C2 + 1
                    while C3 < LE:
                        if str_charat(RBUF, C3) == 44:
                            break
                        C3 = C3 + 1
                    if C3 < LE and D_N < 7:
                        str_substr(S_NUM, RBUF, C1 + 1, C2 - C1 - 1)
                        D_HI[D_N] = atoi(S_NUM)
                        str_substr(S_NUM, RBUF, C2 + 1, C3 - C2 - 1)
                        D_LO[D_N] = atoi(S_NUM)
                        str_substr(S_NUM, RBUF, C3 + 1, LE - C3 - 1)
                        D_CODE[D_N] = atoi(S_NUM)
                        # 日期只留 `MM-DD`(年前两位对一周预报没意义,省下的宽度留给温度)

                        # Keep only `MM-DD` from the date (the century is noise for a week)

                        str_substr(D_DATES + D_N * 12, RBUF, LI + 5, 5)
                        memset(D_DATES + D_N * 12 + 5, 0, 1)
                        D_N = D_N + 1
        LI = LE + 1

    if D_N == 0:
        print_str("[失败] 没解析出数据行(CSV 格式变了吗?)\n" if LANG == 0
                  else "[FAIL] no data rows parsed (did the CSV format change?)\n")
        FAIL = 1
    else:
        print_str("解析 " if LANG == 0 else "parsed ")
        print_int(D_N)
        print_str(" 天:\n" if LANG == 0 else " days:\n")
        I = 0
        while I < D_N:
            print_str("  ")
            print_str(D_DATES + I * 12)
            print_str("  ")
            itoa(D_LO[I], S_NUM)
            print_str(S_NUM)
            print_str("~")
            itoa(D_HI[I], S_NUM)
            print_str(S_NUM)
            print_str("  code=")
            itoa(D_CODE[I], S_NUM)
            print_str(S_NUM)
            print_str("\n")
            I = I + 1

# ⑦ 开窗 → 画 → 消息循环

# ⑦ Open a window, draw, then run the message loop

# ⚠ 版面那几项是**模块级标量**,赋值只能写在顶层(函数里写就是新建局部量,实测)。

# ⚠ The layout values are **module-level scalars**, so the assignments live at the top level

#   (inside a function they would only create locals, measured).

if FAIL == 0:
    # 开窗时传 0:尺寸交给宿主(用真实可用区);开窗后再问一次真实画布(见下)。

    # Open with 0 so the host decides the size (the real usable area), then ask the real canvas

    # again below.

    ui_win_open_ex("weather.py", 0, 0, VML_WIN_ROTATABLE, VML_WIN_NO_GAMEPAD)
    # 开窗后再问一次 —— 画布尺寸与开窗前的估算**可能不同**

    # Ask again after opening: the canvas may differ from the pre-open estimate

    SW = ui_scr_get_w()
    SH = ui_scr_get_h()
    if SW <= 0:
        SW = 360
    if SH <= 0:
        SH = 620
    draw()

    T0 = ui_time_get_ms()
    DONE = 0
    while DONE == 0 and ui_win_is_closed() == 0:
        T = ui_msg_fetch_wait(200)
        if T == VML_MSG_NONE:
            if ui_time_get_ms() - T0 > IDLE_EXIT_MS:
                DONE = 1                 # 空闲超时(验收用)

                # idle timeout (for automated checks)

        elif T == VML_MSG_KEYDOWN:
            K = ui_msg_get_a()
            T0 = ui_time_get_ms()
            if K == VML_KEY_ESCAPE or K == 113 or K == 81:
                DONE = 1
            elif K == VML_KEY_LEFT or K == VML_KEY_RIGHT:
                draw()
            elif K == VML_KEY_UP or K == VML_KEY_DOWN:
                draw()
        elif T == VML_MSG_WINDOWORIENT or T == VML_MSG_WINDOWRESIZE:
            SW = ui_scr_get_w()
            SH = ui_scr_get_h()
            if SW <= 0:
                SW = 360
            if SH <= 0:
                SH = 620
            draw()
        elif T == VML_MSG_WINDOWCLOSE:
            DONE = 1