Files
ViperOS/VKernel/Kernel/services/desktop.py
2026-07-19 12:38:20 +08:00

2715 lines
113 KiB
Python

from stdint import *
import asm
import drivers.video.vesafb.gfx as gfx
import drivers.video.vesafb.vga as vga
import drivers.input.mouse.mouse as mouse
import drivers.input.keyboard.keyboard as keyboard
import drivers.usb.hid.usb_mouse as usb_mouse
import drivers.usb.hid.usb_kbd as usb_kbd
import drivers.serial.uart.serial as serial
import platform.pch.pic as pic
import platform.pch.timer as timer
import platform.pch.rtc as rtc
import sched.sched as sched
import sched.process as process
import mm.mm as mm
import string
import viperlib
import t, c
TASKBAR_HEIGHT: t.CDefine = 50
CURSOR_SIZE: t.CDefine = 16
CURSOR_W: t.CDefine = 16
CURSOR_H: t.CDefine = 16
CURSOR_SAVE_SIZE: t.CDefine = 256
TITLE_HEIGHT_CLASSIC: t.CDefine = 24
TITLE_HEIGHT_WIN: t.CDefine = 32
MAX_APP_WINDOWS: t.CDefine = 16
CLOSE_BTN_SIZE: t.CDefine = 16
CLOSE_BTN_W_WIN: t.CDefine = 46
CLOSE_BTN_W7_W: t.CDefine = 28
CLOSE_BTN_W7_H: t.CDefine = 20
CLOSE_BTN_W7_R: t.CDefine = 3
CLOSE_BTN_XP_SZ: t.CDefine = 21
TITLE_BTN_W_WIN: t.CDefine = 46
TITLE_BTN_W7: t.CDefine = 28
TITLE_BTN_XP: t.CDefine = 21
TASKBAR_ICON_SZ: t.CDefine = 36
TASKBAR_ICON_PAD: t.CDefine = 6
TASKBAR_LOGO_SZ: t.CDefine = 32
WIN_RADIUS: t.CDefine = 8
SHADOW_ENABLED: t.CDefine = False
SHADOW_LEFT: t.CDefine = 8
SHADOW_RIGHT: t.CDefine = 12
SHADOW_TOP: t.CDefine = 4
SHADOW_BOTTOM: t.CDefine = 14
SHADOW_ALPHA: t.CDefine = 100
CURSOR_SHADOW_ALPHA: t.CDefine = 50
STYLE_CLASSIC: t.CDefine = 0
STYLE_WIN10: t.CDefine = 1
STYLE_WIN11: t.CDefine = 2
STYLE_WIN7: t.CDefine = 3
STYLE_WINXP: t.CDefine = 4
_fb: UINT32PTR
_screen_w: t.CInt = 0
_screen_h: t.CInt = 0
_fb_pitch: t.CInt = 0
_cursor_x: t.CInt = 512
_cursor_y: t.CInt = 300
_cursor_buttons: t.CUInt8T = 0
_back_fb: UINT32PTR
_last_time_sec: t.CUInt32T = 0
_row_buf: UINT32PTR
_bg_row_buf: UINT32PTR
@t.Object
class AppWindow:
active: t.CInt
x: t.CInt
y: t.CInt
w: t.CInt
h: t.CInt
title: t.CArray[t.CChar, 32]
orig_title: t.CArray[t.CChar, 32]
content_h: t.CInt
fb: UINT32PTR
fb_w: t.CInt
fb_h: t.CInt
z_order: t.CInt
style: t.CInt
close_hover: t.CInt
min_hover: t.CInt
max_hover: t.CInt
is_maximized: t.CInt
orig_x: t.CInt
orig_y: t.CInt
orig_w: t.CInt
orig_h: t.CInt
pid: t.CInt
cursor_type: t.CInt
app_cursor_type: t.CInt
ping_pending: t.CInt
no_resp: t.CInt
no_resp_sec: t.CUInt32T
minimized: t.CInt
resizable_w: t.CInt
resizable_h: t.CInt
resize_w: t.CInt
resize_h: t.CInt
old_fb: UINT32PTR
_wins: t.CArray[AppWindow, MAX_APP_WINDOWS]
_win_count: t.CInt = 0
_next_z: t.CInt = 1
_current_app_pid: t.CInt = 0
HB_INTERVAL_SEC: t.CUInt32T = 5
HB_NO_RESP_SEC: t.CUInt32T = 30
_hb_last_sec: t.CUInt32T = 0
_dirty_rows: t.CUInt8T | t.CPtr = t.CUInt8T(0, t.CPtr)
_dirty_count: t.CInt = 0
_dirty_y_min: t.CInt = 0
_dirty_y_max: t.CInt = -1
_vblank_ok: t.CInt = -1
_prev_cursor_x: t.CInt = 512
_prev_cursor_y: t.CInt = 300
_cursor_save_buf: t.CArray[t.CUInt32T, 256]
_cursor_saved: t.CInt = 0
_CURSOR_ARROW: t.CArray[t.CUInt8T, 256] = [
1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
1,2,1,0,0,0,0,0,0,0,0,0,0,0,0,0,
1,2,2,1,0,0,0,0,0,0,0,0,0,0,0,0,
1,2,2,2,1,0,0,0,0,0,0,0,0,0,0,0,
1,2,2,2,2,1,0,0,0,0,0,0,0,0,0,0,
1,2,2,2,2,2,1,0,0,0,0,0,0,0,0,0,
1,2,2,2,2,2,2,1,0,0,0,0,0,0,0,0,
1,2,2,2,2,2,2,2,1,0,0,0,0,0,0,0,
1,2,2,2,2,2,2,2,2,1,0,0,0,0,0,0,
1,2,2,2,1,1,1,1,1,1,0,0,0,0,0,0,
1,2,2,1,0,0,0,0,0,0,0,0,0,0,0,0,
1,2,1,0,0,0,0,0,0,0,0,0,0,0,0,0,
1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
]
_CURSOR_IBEAM: t.CArray[t.CUInt8T, 256] = [
0,0,0,1,1,1,1,1,0,0,0,0,0,0,0,0,
0,0,0,1,1,1,1,1,0,0,0,0,0,0,0,0,
0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,
0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,
0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,
0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,
0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,
0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,
0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,
0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,
0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,
0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,
0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,
0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,
0,0,0,1,1,1,1,1,0,0,0,0,0,0,0,0,
0,0,0,1,1,1,1,1,0,0,0,0,0,0,0,0,
]
_CURSOR_RESIZE_EW: t.CArray[t.CUInt8T, 256] = [
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,1,1,1,0,1,1,1,0,0,0,0,0,
0,0,0,1,2,2,1,1,1,2,2,1,0,0,0,0,
0,0,1,2,2,2,2,2,2,2,2,2,1,0,0,0,
0,1,2,2,2,2,2,2,2,2,2,2,2,1,0,0,
0,0,0,0,1,1,1,1,1,1,1,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,1,1,1,1,1,1,1,0,0,0,0,0,
0,1,2,2,2,2,2,2,2,2,2,2,2,1,0,0,
0,0,1,2,2,2,2,2,2,2,2,2,1,0,0,0,
0,0,0,1,2,2,1,1,1,2,2,1,0,0,0,0,
0,0,0,0,1,1,1,0,1,1,1,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
]
_CURSOR_RESIZE_NS: t.CArray[t.CUInt8T, 256] = [
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,
0,0,0,1,2,2,1,0,0,0,0,0,0,0,0,0,
0,0,1,2,2,2,2,1,0,0,0,0,0,0,0,0,
0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,
0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,
0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,
0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,
0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,
0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,
0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,
0,0,1,2,2,2,2,1,0,0,0,0,0,0,0,0,
0,0,0,1,2,2,1,0,0,0,0,0,0,0,0,0,
0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
]
_CURSOR_RESIZE_NWSE: t.CArray[t.CUInt8T, 256] = [
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,
0,1,2,1,1,0,0,0,0,0,0,0,0,0,0,0,
0,0,1,2,2,1,0,0,0,0,0,0,0,0,0,0,
0,0,0,1,2,2,1,0,0,0,0,0,0,0,0,0,
0,0,0,0,1,2,2,1,1,0,0,0,0,0,0,0,
0,0,0,0,0,1,2,2,1,0,0,0,0,0,0,0,
0,0,0,0,0,0,1,2,2,1,0,0,0,0,0,0,
0,0,0,0,0,0,0,1,2,2,1,0,0,0,0,0,
0,0,0,0,0,0,0,0,1,2,2,1,0,0,0,0,
0,0,0,0,0,0,0,0,0,1,2,2,1,0,0,0,
0,0,0,0,0,0,0,0,0,0,1,2,1,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
]
_last_clock_sec: t.CUInt32T = 0
_prev_btn: t.CUInt8T = 0
EVENT_QUEUE_SIZE: t.CDefine = 32
EVENT_TYPE_NONE: t.CDefine = 0
EVENT_TYPE_CLICK: t.CDefine = 1
EVENT_TYPE_HOVER: t.CDefine = 2
EVENT_TYPE_RELEASE: t.CDefine = 3
EVENT_TYPE_HOVER_LEAVE: t.CDefine = 4
EVENT_TYPE_KEY: t.CDefine = 5
EVENT_TYPE_KEY_RELEASE: t.CDefine = 6
EVENT_TYPE_PING: t.CDefine = 7
EVENT_TYPE_RESIZE: t.CDefine = 8
RESIZE_EDGE_NONE: t.CDefine = 0
RESIZE_EDGE_RIGHT: t.CDefine = 1
RESIZE_EDGE_BOTTOM: t.CDefine = 2
RESIZE_EDGE_CORNER: t.CDefine = 3
RESIZE_BORDER: t.CDefine = 6
RESIZE_MIN_W: t.CDefine = 100
RESIZE_MIN_H: t.CDefine = 60
CURSOR_DEFAULT: t.CDefine = 0
CURSOR_IBEAM: t.CDefine = 1
CURSOR_RESIZE_EW: t.CDefine = 2
CURSOR_RESIZE_NS: t.CDefine = 3
CURSOR_RESIZE_NWSE: t.CDefine = 4
KEY_STATE_PRESS: t.CDefine = 1
KEY_STATE_RELEASE: t.CDefine = 2
KEY_STATE_REPEAT: t.CDefine = 3
TYPEMATIC_DELAY: t.CDefine = 500
TYPEMATIC_RATE: t.CDefine = 40
HELD_KEY_MAX: t.CDefine = 8
class held_key_info(t.CStruct):
keycode: t.CUInt8T
is_usb: t.CUInt8T
press_time: t.CUInt64T
last_repeat: t.CUInt64T
_held_keys: t.CArray[held_key_info, HELD_KEY_MAX]
_held_key_count: t.CInt = 0
@c.Attribute(t.attr.packed)
class InputEvent:
type: t.CUInt32T
win_id: t.CInt
x: t.CInt
y: t.CInt
button: t.CUInt8T
_pad0: t.CUInt8T
_pad1: t.CUInt8T
_pad2: t.CUInt8T
INPUT_EVENT_SIZE: t.CDefine = 20
_event_queue: t.CArray[InputEvent, EVENT_QUEUE_SIZE]
_event_head: t.CInt = 0
_event_tail: t.CInt = 0
_last_hover_win: t.CInt = -1
_focused_win: t.CInt = -1
KBD_DEV_PS2: t.CDefine = 0
KBD_DEV_USB: t.CDefine = 1
_ps2_shift_l: t.CUInt8T = 0
_ps2_shift_r: t.CUInt8T = 0
_ps2_caps: t.CUInt8T = 0
_ps2_e0_pending: t.CUInt8T = 0
_kbd_irq_masked: t.CInt = 0
VK_LEFT: t.CDefine = -1
VK_RIGHT: t.CDefine = -2
VK_UP: t.CDefine = -3
VK_DOWN: t.CDefine = -4
VK_HOME: t.CDefine = -5
VK_END: t.CDefine = -6
VK_DELETE: t.CDefine = -7
_ps2_set1: t.CArray[t.CChar, 128] = [
'\0', '\x1b', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '-', '=', '\b',
'\t', 'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p', '[', ']', '\n',
'\0', 'a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', ';', "'", '`', '\0',
'\\', 'z', 'x', 'c', 'v', 'b', 'n', 'm', ',', '.', '/', '\0', '\0', '\0', ' ',
'\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '-', '\0', '\0', '\0', '\0',
'\0', '+', '\0', '\0', '\0', '0', '.', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0',
'\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0',
'\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0',
'\0'
]
_ps2_set1_shift: t.CArray[t.CChar, 128] = [
'\0', '\x1b', '!', '@', '#', '$', '%', '^', '&', '*', '(', ')', '_', '+', '\b',
'\t', 'Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I', 'O', 'P', '{', '}', '\n',
'\0', 'A', 'S', 'D', 'F', 'G', 'H', 'J', 'K', 'L', ':', '"', '~', '\0',
'|', 'Z', 'X', 'C', 'V', 'B', 'N', 'M', '<', '>', '?', '\0', '\0', '\0', ' ',
'\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '-', '\0', '\0', '\0', '\0',
'\0', '+', '\0', '\0', '\0', '0', '.', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0',
'\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0',
'\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0',
'\0'
]
def _ps2_scancode_to_ascii(scancode: t.CUInt8T) -> t.CInt:
code: t.CUInt8T = scancode & t.CUInt8T(0x7F)
if code >= 128: return 0
shifted: t.CInt = 0
if (_ps2_shift_l or _ps2_shift_r) != 0:
shifted = 1
if _ps2_caps != 0:
if code >= 0x10 and code <= 0x32:
shifted = shifted ^ 1
if shifted:
return t.CInt(_ps2_set1_shift[code])
return t.CInt(_ps2_set1[code])
def _push_event(evt: InputEvent):
global _event_head, _event_tail
if evt.type == EVENT_TYPE_RESIZE:
i: t.CInt = _event_head
while i != _event_tail:
if _event_queue[i].type == EVENT_TYPE_RESIZE and _event_queue[i].win_id == evt.win_id:
_event_queue[i].type = EVENT_TYPE_NONE
i = (i + 1) % EVENT_QUEUE_SIZE
next_tail: t.CInt = (_event_tail + 1) % EVENT_QUEUE_SIZE
if next_tail == _event_head:
_event_head = (_event_head + 1) % EVENT_QUEUE_SIZE
string.memcpy(c.Addr(_event_queue[_event_tail]), c.Addr(evt), INPUT_EVENT_SIZE)
_event_tail = next_tail
def poll_event(buf_ptr: t.CVoid | t.CPtr, caller_pid: t.CInt) -> t.CInt:
global _event_head, _event_tail
while _event_head != _event_tail:
if _event_queue[_event_head].type == EVENT_TYPE_NONE:
_event_head = (_event_head + 1) % EVENT_QUEUE_SIZE
continue
evt_win: t.CInt = _event_queue[_event_head].win_id
evt_pid: t.CInt = 0
win_ok: t.CInt = 0
if evt_win >= 0 and evt_win < _win_count:
evt_pid = _wins[evt_win].pid
win_ok = 1
if evt_pid == caller_pid:
string.memcpy(buf_ptr, c.Addr(_event_queue[_event_head]), INPUT_EVENT_SIZE)
_event_head = (_event_head + 1) % EVENT_QUEUE_SIZE
return 1
if win_ok and not _wins[evt_win].active:
_event_head = (_event_head + 1) % EVENT_QUEUE_SIZE
continue
scan: t.CInt = (_event_head + 1) % EVENT_QUEUE_SIZE
while scan != _event_tail:
if _event_queue[scan].type == EVENT_TYPE_NONE:
scan = (scan + 1) % EVENT_QUEUE_SIZE
continue
evt_win2: t.CInt = _event_queue[scan].win_id
evt_pid2: t.CInt = 0
if evt_win2 >= 0 and evt_win2 < _win_count:
evt_pid2 = _wins[evt_win2].pid
if evt_pid2 == caller_pid:
string.memcpy(buf_ptr, c.Addr(_event_queue[scan]), INPUT_EVENT_SIZE)
i: t.CInt = scan
while i != _event_tail:
next_i: t.CInt = (i + 1) % EVENT_QUEUE_SIZE
if next_i != _event_head:
string.memcpy(c.Addr(_event_queue[i]), c.Addr(_event_queue[next_i]), INPUT_EVENT_SIZE)
i = next_i
if _event_tail == 0:
_event_tail = EVENT_QUEUE_SIZE - 1
else:
_event_tail = _event_tail - 1
return 1
scan = (scan + 1) % EVENT_QUEUE_SIZE
break
return 0
def _get_title_height(style: t.CInt) -> t.CInt:
if style == STYLE_CLASSIC:
return TITLE_HEIGHT_CLASSIC
return TITLE_HEIGHT_WIN
def _mark_rows_dirty(start_y: t.CInt, count: t.CInt):
global _dirty_count, _dirty_y_min, _dirty_y_max
for i in range(start_y, start_y + count):
if i >= 0 and i < _screen_h:
if not _dirty_rows[i]:
_dirty_rows[i] = 1
_dirty_count += 1
if i < _dirty_y_min or _dirty_count == 1:
_dirty_y_min = i
if i > _dirty_y_max or _dirty_count == 1:
_dirty_y_max = i
def _mark_all_dirty():
global _dirty_count, _dirty_y_min, _dirty_y_max
string.memset(_dirty_rows, 1, _screen_h)
_dirty_count = _screen_h
_dirty_y_min = 0
_dirty_y_max = _screen_h - 1
def _has_dirty_rows() -> t.CInt:
return _dirty_count
def _clear_dirty_flags():
global _dirty_count, _dirty_y_min, _dirty_y_max
string.memset(_dirty_rows, 0, _screen_h)
_dirty_count = 0
_dirty_y_min = _screen_h
_dirty_y_max = -1
def _wait_vblank():
global _vblank_ok
if _vblank_ok == 0:
return
timeout: t.CInt = 50000
while (asm.inb(0x3DA) & 0x08) != 0:
timeout -= 1
if timeout <= 0:
_vblank_ok = 0
return
while (asm.inb(0x3DA) & 0x08) == 0:
timeout -= 1
if timeout <= 0:
_vblank_ok = 0
return
_vblank_ok = 1
def _fill_rect(buf: UINT32PTR, bw: t.CInt, bh: t.CInt, rx: t.CInt, ry: t.CInt, rw: t.CInt, rh: t.CInt, color: t.CUInt32T):
if rx < 0: rw += rx; rx = 0
if ry < 0: rh += ry; ry = 0
if rx + rw > bw: rw = bw - rx
if ry + rh > bh: rh = bh - ry
if rw <= 0 or rh <= 0: return
if _row_buf:
for i in range(rw):
_row_buf[i] = color
row_bytes: t.CInt = rw * 4
for py in range(ry, ry + rh):
string.memcpy(t.CVoid(t.CUInt64T(buf) + (py * bw + rx) * 4, t.CPtr), _row_buf, row_bytes)
def _alpha_blend(bg: t.CUInt32T, fg: t.CUInt32T, alpha: t.CInt) -> t.CUInt32T:
a: t.CInt = alpha
ia: t.CInt = 255 - a
bg_r: t.CInt = t.CInt(bg & 0xFF)
bg_g: t.CInt = t.CInt((bg >> 8) & 0xFF)
bg_b: t.CInt = t.CInt((bg >> 16) & 0xFF)
fg_r: t.CInt = t.CInt(fg & 0xFF)
fg_g: t.CInt = t.CInt((fg >> 8) & 0xFF)
fg_b: t.CInt = t.CInt((fg >> 16) & 0xFF)
r: t.CInt = (fg_r * a + bg_r * ia) >> 8
g: t.CInt = (fg_g * a + bg_g * ia) >> 8
b: t.CInt = (fg_b * a + bg_b * ia) >> 8
return t.CUInt32T(r | (g << 8) | (b << 16) | 0xFF000000)
def _is_in_corner(px: t.CInt, py: t.CInt, cx: t.CInt, cy: t.CInt, r: t.CInt) -> t.CInt:
dx: t.CInt = px - cx
dy: t.CInt = py - cy
if dx < 0: dx = -dx
if dy < 0: dy = -dy
if dx <= r and dy <= r:
dist2: t.CInt = dx * dx + dy * dy
r2: t.CInt = r * r
if dist2 > r2:
return 0
return 1
def _is_on_border(rx: t.CInt, ry: t.CInt, ww: t.CInt, wh: t.CInt, r: t.CInt) -> t.CInt:
if rx == 0 or rx == ww - 1 or ry == 0 or ry == wh - 1:
return 1
if ry < r:
if rx < r:
dx: t.CInt = rx - r
dy: t.CInt = ry - r
dist2: t.CInt = dx * dx + dy * dy
if dist2 >= (r - 1) * (r - 1): return 1
elif rx >= ww - r:
dx = rx - (ww - r - 1)
dy = ry - r
dist2 = dx * dx + dy * dy
if dist2 >= (r - 1) * (r - 1): return 1
elif ry >= wh - r:
if rx < r:
dx = rx - r
dy = ry - (wh - r - 1)
dist2 = dx * dx + dy * dy
if dist2 >= (r - 1) * (r - 1): return 1
elif rx >= ww - r:
dx = rx - (ww - r - 1)
dy = ry - (wh - r - 1)
dist2 = dx * dx + dy * dy
if dist2 >= (r - 1) * (r - 1): return 1
return 0
def _corner_alpha(px: t.CInt, py: t.CInt, cx: t.CInt, cy: t.CInt, r: t.CInt) -> t.CInt:
dx: t.CInt = px - cx
dy: t.CInt = py - cy
if dx < 0: dx = -dx
if dy < 0: dy = -dy
if dx <= r and dy <= r:
dist2: t.CInt = dx * dx + dy * dy
r2: t.CInt = r * r
if dist2 > r2:
return 0
outer: t.CInt = (r + 1) * (r + 1)
if dist2 >= outer:
return 160
return 255
return 255
def _draw_char(buf: UINT32PTR, bw: t.CInt, bh: t.CInt, x: t.CInt, y: t.CInt, ch: t.CChar, color: t.CUInt32T):
cval: t.CUInt8T = t.CUInt8T(ch)
if cval >= 128: cval = 0
for fy in range(16):
if y + fy < 0 or y + fy >= bh: continue
row: t.CUInt8T = vga.font[cval][fy]
if row == 0: continue
for fx in range(8):
if row & (0x80 >> fx):
px: t.CInt = x + fx
if px >= 0 and px < bw:
buf[(y + fy) * bw + px] = color
def _draw_text(buf: UINT32PTR, bw: t.CInt, bh: t.CInt, x: t.CInt, y: t.CInt, s: t.CConst | str, color: t.CUInt32T):
cx: t.CInt = x
cy: t.CInt = y
for ch in s:
if ch == 10:
cy += 16
cx = x
else:
_draw_char(buf, bw, bh, cx, cy, ch, color)
cx += 8
_drag_idx: t.CInt = -1
def _eff_w(aw2: AppWindow | t.CPtr) -> t.CInt:
if aw2.resize_w != 0: return aw2.resize_w
return aw2.w
def _eff_h(aw2: AppWindow | t.CPtr) -> t.CInt:
if aw2.resize_h != 0: return aw2.resize_h
return aw2.content_h
_drag_off_x: t.CInt = 0
_drag_off_y: t.CInt = 0
_rsz_drag_idx: t.CInt = -1
_rsz_edge: t.CInt = 0
_rsz_start_x: t.CInt = 0
_rsz_start_y: t.CInt = 0
_rsz_start_w: t.CInt = 0
_rsz_start_h: t.CInt = 0
_rsz_last_w: t.CInt = 0
_rsz_last_h: t.CInt = 0
_rsz_last_tick: t.CUInt64T = 0
_mouse_moved: t.CInt = 0
def _bring_to_front(idx: t.CInt):
global _next_z
if idx < 0 or idx >= _win_count: return
aw: AppWindow | t.CPtr = c.Addr(_wins[idx])
if not aw.active or aw.minimized: return
_next_z += 1
aw.z_order = _next_z
def _hit_test_window(mx: t.CInt, my: t.CInt) -> t.CInt:
best: t.CInt = -1
best_z: t.CInt = -1
for ai in range(_win_count):
if _wins[ai].active and not _wins[ai].minimized:
ax: t.CInt = _wins[ai].x
ay: t.CInt = _wins[ai].y
aw: t.CInt = _eff_w(c.Addr(_wins[ai]))
ah: t.CInt = _wins[ai].h
if mx >= ax and mx < ax + aw and my >= ay and my < ay + ah:
z: t.CInt = _wins[ai].z_order
if z > best_z:
best_z = z
best = ai
return best
def _hit_test_resize(mx: t.CInt, my: t.CInt, idx: t.CInt) -> t.CInt:
if idx < 0 or idx >= _win_count: return RESIZE_EDGE_NONE
aw: AppWindow | t.CPtr = c.Addr(_wins[idx])
if not aw.active or aw.minimized: return RESIZE_EDGE_NONE
if not aw.resizable_w and not aw.resizable_h: return RESIZE_EDGE_NONE
on_right: t.CInt = 0
on_bottom: t.CInt = 0
if aw.resizable_w:
if mx >= aw.x + _eff_w(aw) - RESIZE_BORDER and mx < aw.x + _eff_w(aw):
on_right = 1
if aw.resizable_h:
if my >= aw.y + aw.h - RESIZE_BORDER and my < aw.y + aw.h:
on_bottom = 1
if on_right and on_bottom: return RESIZE_EDGE_CORNER
if on_right: return RESIZE_EDGE_RIGHT
if on_bottom: return RESIZE_EDGE_BOTTOM
return RESIZE_EDGE_NONE
def _hid_to_ascii(keycode: t.CUInt8T, modifier: t.CUInt8T) -> t.CInt:
global _ps2_caps
shift: t.CInt = 0
if (modifier & 0x22) != 0:
shift = 1
kc: t.CInt = t.CInt(keycode)
if kc >= 4 and kc <= 29:
if _ps2_caps != 0:
shift = shift ^ 1
if shift:
return kc - 4 + 65
return kc - 4 + 97
if kc >= 30 and kc <= 38:
if shift:
shifts9: t.CArray[t.CInt, 9]
shifts9[0] = 33; shifts9[1] = 64; shifts9[2] = 35
shifts9[3] = 36; shifts9[4] = 37; shifts9[5] = 94
shifts9[6] = 38; shifts9[7] = 42; shifts9[8] = 40
return shifts9[kc - 30]
return kc - 30 + 49
if kc == 39:
if shift: return 41
return 48
if kc == 44: return 32
if kc == 40: return 13
if kc == 42: return 8
if kc == 43: return 9
if kc == 41: return 27
if kc == 45:
if shift: return 95
return 45
if kc == 46:
if shift: return 43
return 61
if kc == 47:
if shift: return 123
return 91
if kc == 48:
if shift: return 125
return 93
if kc == 49:
if shift: return 124
return 92
if kc == 51:
if shift: return 58
return 59
if kc == 52:
if shift: return 34
return 39
if kc == 53:
if shift: return 126
return 96
if kc == 54:
if shift: return 60
return 44
if kc == 55:
if shift: return 62
return 46
if kc == 56:
if shift: return 63
return 47
return 0
def _held_key_add(keycode: t.CUInt8T, is_usb: t.CUInt8T):
global _held_key_count
if _held_key_count >= HELD_KEY_MAX: return
for i in range(_held_key_count):
if _held_keys[i].keycode == keycode and _held_keys[i].is_usb == is_usb:
return
now: t.CUInt64T = timer.timer_get_ticks()
_held_keys[_held_key_count].keycode = keycode
_held_keys[_held_key_count].is_usb = is_usb
_held_keys[_held_key_count].press_time = now
_held_keys[_held_key_count].last_repeat = now
_held_key_count += 1
def _held_key_remove(keycode: t.CUInt8T, is_usb: t.CUInt8T):
global _held_key_count
for i in range(_held_key_count):
if _held_keys[i].keycode == keycode and _held_keys[i].is_usb == is_usb:
for j in range(i, _held_key_count - 1):
_held_keys[j].keycode = _held_keys[j + 1].keycode
_held_keys[j].is_usb = _held_keys[j + 1].is_usb
_held_keys[j].press_time = _held_keys[j + 1].press_time
_held_keys[j].last_repeat = _held_keys[j + 1].last_repeat
_held_key_count -= 1
return
def _check_key_repeat():
global _held_key_count
if _focused_win < 0 or _focused_win >= _win_count: return
if not _wins[_focused_win].active: return
now: t.CUInt64T = timer.timer_get_ticks()
for i in range(_held_key_count):
hk: held_key_info | t.CPtr = c.Addr(_held_keys[i])
elapsed: t.CUInt64T = now - hk.press_time
if elapsed < t.CUInt64T(TYPEMATIC_DELAY):
continue
since_last: t.CUInt64T = now - hk.last_repeat
if since_last >= t.CUInt64T(TYPEMATIC_RATE):
hk.last_repeat = now
ascii_ch: t.CInt = 0
if hk.is_usb:
mod: t.CUInt8T = 0
if _ps2_shift_l or _ps2_shift_r:
mod = mod | 0x22
if _ps2_caps:
mod = mod | 0x01
ascii_ch = _hid_to_ascii(hk.keycode, mod)
else:
ascii_ch = _ps2_scancode_to_ascii(hk.keycode)
if ascii_ch != 0:
rep_evt: InputEvent
rep_evt.type = EVENT_TYPE_KEY
rep_evt.win_id = _focused_win
rep_evt.x = ascii_ch
rep_evt.y = t.CInt(hk.keycode)
rep_evt.button = KEY_STATE_REPEAT
rep_evt._pad0 = 0
rep_evt._pad1 = 0
rep_evt._pad2 = 0
_push_event(rep_evt)
def _update_keyboard():
global _focused_win, _kbd_irq_masked
global _ps2_shift_l, _ps2_shift_r, _ps2_caps
global _ps2_e0_pending
# Quick check: skip if no PS/2 data and no USB data
ps2_status: t.CUInt8T = asm.inb(0x64)
if (ps2_status & 0x01) == 0 and not usb_kbd.has_data():
_check_key_repeat()
return
if _kbd_irq_masked == 0:
pic.clearMask(1)
_kbd_irq_masked = 1
kbd_cnt: t.CInt = 0
while usb_kbd.has_data() and kbd_cnt < 32:
kbd_cnt += 1
kevt: usb_kbd.usb_kbd_event
usb_kbd.read_event(c.Addr(kevt))
if kevt.keycode == 0x39 and kevt.event_type == usb_kbd.KBD_EVT_PRESS:
_ps2_caps = _ps2_caps ^ t.CUInt8T(1)
if kevt.event_type == usb_kbd.KBD_EVT_PRESS:
_held_key_add(kevt.keycode, 1)
usb_ascii: t.CInt = _hid_to_ascii(kevt.keycode, kevt.modifier)
if usb_ascii == 0:
kc2: t.CInt = t.CInt(kevt.keycode)
if kc2 == 0x50: usb_ascii = VK_LEFT
elif kc2 == 0x4F: usb_ascii = VK_RIGHT
elif kc2 == 0x52: usb_ascii = VK_UP
elif kc2 == 0x51: usb_ascii = VK_DOWN
elif kc2 == 0x4A: usb_ascii = VK_HOME
elif kc2 == 0x4D: usb_ascii = VK_END
elif kc2 == 0x4C: usb_ascii = VK_DELETE
if usb_ascii != 0 and _focused_win >= 0 and _focused_win < _win_count and _wins[_focused_win].active:
usb_evt: InputEvent
usb_evt.type = EVENT_TYPE_KEY
usb_evt.win_id = _focused_win
usb_evt.x = usb_ascii
usb_evt.y = t.CInt(kevt.keycode)
usb_evt.button = KEY_STATE_PRESS
usb_evt._pad0 = 0
usb_evt._pad1 = 0
usb_evt._pad2 = 0
_push_event(usb_evt)
elif kevt.event_type == usb_kbd.KBD_EVT_RELEASE:
_held_key_remove(kevt.keycode, 1)
if _focused_win >= 0 and _focused_win < _win_count and _wins[_focused_win].active:
rel_evt: InputEvent
rel_evt.type = EVENT_TYPE_KEY_RELEASE
rel_evt.win_id = _focused_win
rel_evt.x = 0
rel_evt.y = t.CInt(kevt.keycode)
rel_evt.button = KEY_STATE_RELEASE
rel_evt._pad0 = 0
rel_evt._pad1 = 0
rel_evt._pad2 = 0
_push_event(rel_evt)
ps2_poll_count: t.CInt = 0
while ps2_poll_count < 16:
ps2_status: t.CUInt8T = asm.inb(0x64)
if (ps2_status & 0x01) == 0:
break
if (ps2_status & 0x20) != 0:
asm.inb(0x60)
ps2_poll_count += 1
continue
sc: t.CUInt8T = asm.inb(0x60)
ps2_poll_count += 1
if sc == 0x00 or sc == 0xFA or sc == 0xFE or sc == 0xFF:
_ps2_e0_pending = 0
continue
if sc == 0xE0:
_ps2_e0_pending = 1
continue
if sc == 0xE1:
_ps2_e0_pending = 0
continue
if _ps2_e0_pending != 0:
_ps2_e0_pending = 0
e0_code: t.CUInt8T = sc & t.CUInt8T(0x7F)
if (sc & 0x80) != 0:
_held_key_remove(e0_code | 0x80, 0)
if _focused_win >= 0 and _focused_win < _win_count and _wins[_focused_win].active:
ps2_rel2: InputEvent
ps2_rel2.type = EVENT_TYPE_KEY_RELEASE
ps2_rel2.win_id = _focused_win
ps2_rel2.x = 0
ps2_rel2.y = t.CInt(e0_code | 0x80)
ps2_rel2.button = KEY_STATE_RELEASE
ps2_rel2._pad0 = 0
ps2_rel2._pad1 = 0
ps2_rel2._pad2 = 0
_push_event(ps2_rel2)
continue
vk: t.CInt = 0
if e0_code == 0x4B: vk = VK_LEFT
elif e0_code == 0x4D: vk = VK_RIGHT
elif e0_code == 0x48: vk = VK_UP
elif e0_code == 0x50: vk = VK_DOWN
elif e0_code == 0x47: vk = VK_HOME
elif e0_code == 0x4F: vk = VK_END
elif e0_code == 0x53: vk = VK_DELETE
if vk != 0:
_held_key_add(e0_code | 0x80, 0)
if _focused_win >= 0 and _focused_win < _win_count and _wins[_focused_win].active:
e0_evt: InputEvent
e0_evt.type = EVENT_TYPE_KEY
e0_evt.win_id = _focused_win
e0_evt.x = vk
e0_evt.y = t.CInt(e0_code | 0x80)
e0_evt.button = KEY_STATE_PRESS
e0_evt._pad0 = 0
e0_evt._pad1 = 0
e0_evt._pad2 = 0
_push_event(e0_evt)
continue
if sc == 0x2A:
_ps2_shift_l = 1
continue
if sc == 0xAA:
_ps2_shift_l = 0
continue
if sc == 0x36:
_ps2_shift_r = 1
continue
if sc == 0xB6:
_ps2_shift_r = 0
continue
if sc == 0x3A:
_ps2_caps = _ps2_caps ^ t.CUInt8T(1)
continue
if (sc & 0x80) != 0:
release_sc: t.CUInt8T = sc & t.CUInt8T(0x7F)
_held_key_remove(release_sc, 0)
if _focused_win >= 0 and _focused_win < _win_count and _wins[_focused_win].active:
ps2_rel: InputEvent
ps2_rel.type = EVENT_TYPE_KEY_RELEASE
ps2_rel.win_id = _focused_win
ps2_rel.x = 0
ps2_rel.y = t.CInt(release_sc)
ps2_rel.button = KEY_STATE_RELEASE
ps2_rel._pad0 = 0
ps2_rel._pad1 = 0
ps2_rel._pad2 = 0
_push_event(ps2_rel)
continue
_held_key_add(sc, 0)
ascii_ch: t.CInt = _ps2_scancode_to_ascii(sc)
if ascii_ch != 0 and _focused_win >= 0 and _focused_win < _win_count and _wins[_focused_win].active:
push_evt: InputEvent
push_evt.type = EVENT_TYPE_KEY
push_evt.win_id = _focused_win
push_evt.x = ascii_ch
push_evt.y = t.CInt(sc)
push_evt.button = KEY_STATE_PRESS
push_evt._pad0 = 0
push_evt._pad1 = 0
push_evt._pad2 = 0
_push_event(push_evt)
_check_key_repeat()
def set_cursor_type(win_id: t.CInt, cursor_t: t.CInt):
if win_id >= 0 and win_id < _win_count:
_wins[win_id].app_cursor_type = cursor_t
_wins[win_id].cursor_type = cursor_t
def _update_mouse() -> t.CInt:
global _cursor_x, _cursor_y, _cursor_buttons
global _drag_idx, _drag_off_x, _drag_off_y
global _mouse_moved, _prev_btn, _last_hover_win, _focused_win
global _rsz_drag_idx, _rsz_edge, _rsz_start_x, _rsz_start_y
global _rsz_start_w, _rsz_start_h, _rsz_last_w, _rsz_last_h, _rsz_last_tick
moved: t.CInt = 0
poll_cnt: t.CInt = 0
while (mouse.has_data() or usb_mouse.has_data()) and poll_cnt < 32:
poll_cnt += 1
pkt_dx: t.CInt32T = 0
pkt_dy: t.CInt32T = 0
pkt_btn: t.CUInt8T = 0
if mouse.has_data():
pkt: mouse.mouse_packet
mouse.read_packet(c.Addr(pkt))
pkt_dx = t.CInt32T(pkt.dx)
pkt_dy = -t.CInt32T(pkt.dy)
pkt_btn = pkt.buttons
elif usb_mouse.has_data():
upkt: usb_mouse.usb_mouse_packet
usb_mouse.read_packet(c.Addr(upkt))
pkt_dx = t.CInt32T(upkt.dx)
pkt_dy = -t.CInt32T(upkt.dy)
pkt_btn = upkt.buttons
if pkt_dx != 0 or pkt_dy != 0:
_cursor_x = _cursor_x + pkt_dx
_cursor_y = _cursor_y + pkt_dy
if _cursor_x < 0: _cursor_x = 0
if _cursor_y < 0: _cursor_y = 0
if _cursor_x >= _screen_w: _cursor_x = _screen_w - 1
if _cursor_y >= _screen_h: _cursor_y = _screen_h - 1
moved = 1
_mouse_moved = 1
btn_down: t.CUInt8T = pkt_btn & 0x01
prev_down: t.CUInt8T = _prev_btn & 0x01
_cursor_buttons = pkt_btn
_prev_btn = pkt_btn
if btn_down and not prev_down:
hit_app: t.CInt = _hit_test_window(_cursor_x, _cursor_y)
if hit_app >= 0:
aw: AppWindow | t.CPtr = c.Addr(_wins[hit_app])
th: t.CInt = _get_title_height(aw.style)
btn_w: t.CInt = CLOSE_BTN_SIZE
btn_h: t.CInt = CLOSE_BTN_SIZE
ew: t.CInt = _eff_w(aw)
btn_x: t.CInt = aw.x + ew - btn_w - 4
btn_y: t.CInt = aw.y + (th - btn_h) // 2
if aw.style == STYLE_WIN7:
btn_w = CLOSE_BTN_W7_W
btn_h = CLOSE_BTN_W7_H
btn_x = aw.x + ew - btn_w - 5
btn_y = aw.y + (th - btn_h) // 2
elif aw.style == STYLE_WINXP:
btn_w = CLOSE_BTN_XP_SZ
btn_h = CLOSE_BTN_XP_SZ
btn_x = aw.x + ew - btn_w - 5
btn_y = aw.y + (th - btn_h) // 2
elif aw.style == STYLE_WIN10 or aw.style == STYLE_WIN11:
btn_w = CLOSE_BTN_W_WIN
btn_h = th - 2
btn_x = aw.x + ew - btn_w
btn_y = aw.y + 1
if _cursor_x >= btn_x and _cursor_x < btn_x + btn_w and _cursor_y >= btn_y and _cursor_y < btn_y + btn_h:
aw.active = 0
if _focused_win == hit_app:
_focused_win = -1
j: t.CInt = _win_count - 1
while j >= 0:
if _wins[j].active and not _wins[j].minimized:
_focused_win = j
break
j -= 1
if _last_hover_win == hit_app:
_last_hover_win = -1
_mark_all_dirty()
else:
max_bx2: t.CInt = btn_x - btn_w
if aw.style == STYLE_WIN7 or aw.style == STYLE_WINXP:
max_bx2 = btn_x - btn_w - 1
if _cursor_x >= max_bx2 and _cursor_x < max_bx2 + btn_w and _cursor_y >= btn_y and _cursor_y < btn_y + btn_h:
if aw.is_maximized:
aw.is_maximized = 0
aw.x = aw.orig_x
aw.y = aw.orig_y
aw.h = aw.orig_h
aw.resize_w = aw.orig_w
aw.resize_h = aw.orig_h - _get_title_height(aw.style) - 2
else:
aw.is_maximized = 1
aw.orig_x = aw.w
aw.orig_y = aw.y
aw.orig_w = aw.w
aw.orig_h = aw.h
aw.x = 0
aw.y = 0
tb_h_mx: t.CInt = TASKBAR_HEIGHT
aw.h = _screen_h - tb_h_mx
aw.resize_w = _screen_w
aw.resize_h = _screen_h - tb_h_mx - _get_title_height(aw.style) - 2
rs_evt3: InputEvent
rs_evt3.type = EVENT_TYPE_RESIZE
rs_evt3.win_id = hit_app
rs_evt3.x = aw.resize_w
rs_evt3.y = aw.resize_h
rs_evt3.button = 0
rs_evt3._pad0 = 0
rs_evt3._pad1 = 0
rs_evt3._pad2 = 0
_push_event(rs_evt3)
_mark_all_dirty()
else:
min_bx2: t.CInt = max_bx2 - btn_w
if aw.style == STYLE_WIN7 or aw.style == STYLE_WINXP:
min_bx2 = max_bx2 - btn_w - 1
if _cursor_x >= min_bx2 and _cursor_x < min_bx2 + btn_w and _cursor_y >= btn_y and _cursor_y < btn_y + btn_h:
aw.minimized = 1
if _focused_win == hit_app:
_focused_win = -1
j2: t.CInt = _win_count - 1
while j2 >= 0:
if _wins[j2].active and not _wins[j2].minimized:
_focused_win = j2
break
j2 -= 1
_mark_all_dirty()
else:
re: t.CInt = _hit_test_resize(_cursor_x, _cursor_y, hit_app)
if re != RESIZE_EDGE_NONE:
_rsz_drag_idx = hit_app
_rsz_edge = re
_rsz_start_x = _cursor_x
_rsz_start_y = _cursor_y
_rsz_start_w = _eff_w(aw)
_rsz_start_h = aw.h
_rsz_last_w = _eff_w(aw)
_rsz_last_h = aw.h
_rsz_last_tick = timer.timer_get_ticks()
_bring_to_front(hit_app)
_focused_win = hit_app
_mark_all_dirty()
elif _cursor_y >= aw.y and _cursor_y < aw.y + th:
_drag_idx = hit_app
_drag_off_x = _cursor_x - aw.x
_drag_off_y = _cursor_y - aw.y
_bring_to_front(hit_app)
_focused_win = hit_app
_mark_all_dirty()
else:
_bring_to_front(hit_app)
_focused_win = hit_app
_mark_all_dirty()
evt: InputEvent
evt.type = EVENT_TYPE_CLICK
evt.win_id = hit_app
evt.x = _cursor_x - aw.x
evt.y = _cursor_y - aw.y - _get_title_height(aw.style)
evt.button = 1
evt._pad0 = 0
evt._pad1 = 0
evt._pad2 = 0
_push_event(evt)
else:
tb_h_cl: t.CInt = TASKBAR_HEIGHT
bar_y2: t.CInt = _screen_h - tb_h_cl
if _cursor_y >= bar_y2:
logo_x2: t.CInt = 4
logo_sz2: t.CInt = TASKBAR_LOGO_SZ
logo_r2: t.CInt = logo_x2 + logo_sz2
logo_b2: t.CInt = bar_y2 + tb_h_cl
if _cursor_x >= logo_x2 and _cursor_x < logo_r2 and _cursor_y >= bar_y2 and _cursor_y < logo_b2:
if _focused_win >= 0 and _focused_win < _win_count:
if _wins[_focused_win].active:
_wins[_focused_win].active = 0
_focused_win = -1
_mark_all_dirty()
else:
icon_sz2: t.CInt = TASKBAR_ICON_SZ
icon_pad2: t.CInt = TASKBAR_ICON_PAD
icon_x2: t.CInt = logo_x2 + logo_sz2 + 80
icon_cnt2: t.CInt = 0
for ti2 in range(_win_count):
if _wins[ti2].active:
ix2: t.CInt = icon_x2 + icon_cnt2 * (icon_sz2 + icon_pad2)
iy2: t.CInt = bar_y2 + (tb_h_cl - icon_sz2) // 2
if _cursor_x >= ix2 and _cursor_x < ix2 + icon_sz2 and _cursor_y >= iy2 and _cursor_y < iy2 + icon_sz2:
if _wins[ti2].minimized:
_wins[ti2].minimized = 0
_bring_to_front(ti2)
_focused_win = ti2
elif _focused_win == ti2:
_wins[ti2].minimized = 1
_focused_win = -1
for j3 in range(_win_count - 1, -1, -1):
if _wins[j3].active and not _wins[j3].minimized:
_focused_win = j3
break
else:
_bring_to_front(ti2)
_focused_win = ti2
_mark_all_dirty()
break
icon_cnt2 += 1
if not btn_down:
if prev_down:
if _rsz_drag_idx >= 0:
rs_aw: AppWindow | t.CPtr = c.Addr(_wins[_rsz_drag_idx])
final_w: t.CInt = _rsz_last_w
final_h: t.CInt = _rsz_last_h
old_h_mu: t.CInt = _eff_h(rs_aw) + _get_title_height(rs_aw.style) + 2
_mark_rows_dirty(rs_aw.y, old_h_mu)
final_ch: t.CInt = final_h - _get_title_height(rs_aw.style) - 2
rs_aw.resize_w = final_w
rs_aw.resize_h = final_ch
rs_aw.h = final_h
_mark_rows_dirty(rs_aw.y, final_h)
rs_evt: InputEvent
rs_evt.type = EVENT_TYPE_RESIZE
rs_evt.win_id = _rsz_drag_idx
rs_evt.x = final_w
rs_evt.y = final_ch
rs_evt.button = 0
rs_evt._pad0 = 0
rs_evt._pad1 = 0
rs_evt._pad2 = 0
_push_event(rs_evt)
_rsz_drag_idx = -1
_rsz_edge = RESIZE_EDGE_NONE
else:
rel_hit: t.CInt = _hit_test_window(_cursor_x, _cursor_y)
if rel_hit >= 0:
aw_rel: AppWindow | t.CPtr = c.Addr(_wins[rel_hit])
rel_evt: InputEvent
rel_evt.type = EVENT_TYPE_RELEASE
rel_evt.win_id = rel_hit
rel_evt.x = _cursor_x - aw_rel.x
rel_evt.y = _cursor_y - aw_rel.y - _get_title_height(aw_rel.style)
rel_evt.button = 0
rel_evt._pad0 = 0
rel_evt._pad1 = 0
rel_evt._pad2 = 0
_push_event(rel_evt)
if _drag_idx >= 0:
moved = 1
_mark_rows_dirty(_wins[_drag_idx].y, _wins[_drag_idx].h)
_drag_idx = -1
if moved and _drag_idx < 0:
hit_hov: t.CInt = _hit_test_window(_cursor_x, _cursor_y)
if hit_hov >= 0:
if _last_hover_win >= 0 and _last_hover_win != hit_hov and _last_hover_win < _win_count:
if _wins[_last_hover_win].active:
leave_evt3: InputEvent
leave_evt3.type = EVENT_TYPE_HOVER_LEAVE
leave_evt3.win_id = _last_hover_win
leave_evt3.x = -1
leave_evt3.y = -1
leave_evt3.button = 0
leave_evt3._pad0 = 0
leave_evt3._pad1 = 0
leave_evt3._pad2 = 0
_push_event(leave_evt3)
aw_hov: AppWindow | t.CPtr = c.Addr(_wins[hit_hov])
th_hov: t.CInt = _get_title_height(aw_hov.style)
hov_evt: InputEvent
hov_evt.type = EVENT_TYPE_HOVER
hov_evt.win_id = hit_hov
hov_evt.x = _cursor_x - aw_hov.x
hov_evt.y = _cursor_y - aw_hov.y - th_hov
hov_evt.button = 0
hov_evt._pad0 = 0
hov_evt._pad1 = 0
hov_evt._pad2 = 0
_push_event(hov_evt)
_last_hover_win = hit_hov
re_hov: t.CInt = _hit_test_resize(_cursor_x, _cursor_y, hit_hov)
if re_hov == RESIZE_EDGE_RIGHT:
aw_hov.cursor_type = CURSOR_RESIZE_EW
elif re_hov == RESIZE_EDGE_BOTTOM:
aw_hov.cursor_type = CURSOR_RESIZE_NS
elif re_hov == RESIZE_EDGE_CORNER:
aw_hov.cursor_type = CURSOR_RESIZE_NWSE
else:
aw_hov.cursor_type = aw_hov.app_cursor_type
else:
if _last_hover_win >= 0 and _last_hover_win < _win_count:
if _wins[_last_hover_win].active:
leave_evt: InputEvent
leave_evt.type = EVENT_TYPE_HOVER_LEAVE
leave_evt.win_id = _last_hover_win
leave_evt.x = -1
leave_evt.y = -1
leave_evt.button = 0
leave_evt._pad0 = 0
leave_evt._pad1 = 0
leave_evt._pad2 = 0
_push_event(leave_evt)
_last_hover_win = -1
if _drag_idx >= 0:
aw2: AppWindow | t.CPtr = c.Addr(_wins[_drag_idx])
_drag_old_y: t.CInt = aw2.y
_mark_rows_dirty(_drag_old_y, aw2.h)
aw2.x = _cursor_x - _drag_off_x
aw2.y = _cursor_y - _drag_off_y
_mark_rows_dirty(aw2.y, aw2.h)
if _rsz_drag_idx >= 0:
rs_aw2: AppWindow | t.CPtr = c.Addr(_wins[_rsz_drag_idx])
dx: t.CInt = _cursor_x - _rsz_start_x
dy: t.CInt = _cursor_y - _rsz_start_y
new_w: t.CInt = _rsz_start_w
new_h: t.CInt = _rsz_start_h
if _rsz_edge == RESIZE_EDGE_RIGHT or _rsz_edge == RESIZE_EDGE_CORNER:
new_w = _rsz_start_w + dx
if new_w < RESIZE_MIN_W: new_w = RESIZE_MIN_W
if _rsz_edge == RESIZE_EDGE_BOTTOM or _rsz_edge == RESIZE_EDGE_CORNER:
new_h = _rsz_start_h + dy
if new_h < RESIZE_MIN_H: new_h = RESIZE_MIN_H
if new_w != rs_aw2.w or new_h != rs_aw2.h:
dw: t.CInt = new_w - _rsz_last_w
if dw < 0: dw = -dw
dh: t.CInt = new_h - _rsz_last_h
if dh < 0: dh = -dh
now_tick: t.CUInt64T = timer.timer_get_ticks()
elapsed: t.CUInt64T = now_tick - _rsz_last_tick
need_update: t.CInt = 0
if dw >= 32 or dh >= 32:
need_update = 1
elif elapsed >= 100 and (dw >= 4 or dh >= 4):
need_update = 1
if need_update:
_rsz_last_w = new_w
_rsz_last_h = new_h
_rsz_last_tick = now_tick
old_h_rs: t.CInt = _eff_h(rs_aw2) + _get_title_height(rs_aw2.style) + 2
_mark_rows_dirty(rs_aw2.y, old_h_rs)
new_ch: t.CInt = new_h - _get_title_height(rs_aw2.style) - 2
rs_aw2.resize_w = new_w
rs_aw2.resize_h = new_ch
rs_aw2.h = new_h
_mark_rows_dirty(rs_aw2.y, new_h)
rs_evt2: InputEvent
rs_evt2.type = EVENT_TYPE_RESIZE
rs_evt2.win_id = _rsz_drag_idx
rs_evt2.x = new_w
rs_evt2.y = new_ch
rs_evt2.button = 0
rs_evt2._pad0 = 0
rs_evt2._pad1 = 0
rs_evt2._pad2 = 0
_push_event(rs_evt2)
if not moved and _last_hover_win < 0:
hit_hov2: t.CInt = _hit_test_window(_cursor_x, _cursor_y)
if hit_hov2 >= 0 and _wins[hit_hov2].active:
aw_hov2: AppWindow | t.CPtr = c.Addr(_wins[hit_hov2])
th_hov2: t.CInt = _get_title_height(aw_hov2.style)
hov_evt2: InputEvent
hov_evt2.type = EVENT_TYPE_HOVER
hov_evt2.win_id = hit_hov2
hov_evt2.x = _cursor_x - aw_hov2.x
hov_evt2.y = _cursor_y - aw_hov2.y - th_hov2
hov_evt2.button = 0
hov_evt2._pad0 = 0
hov_evt2._pad1 = 0
hov_evt2._pad2 = 0
_push_event(hov_evt2)
_last_hover_win = hit_hov2
if not moved and _last_hover_win >= 0:
if _last_hover_win >= _win_count or not _wins[_last_hover_win].active:
leave_evt2: InputEvent
leave_evt2.type = EVENT_TYPE_HOVER_LEAVE
leave_evt2.win_id = _last_hover_win
leave_evt2.x = -1
leave_evt2.y = -1
leave_evt2.button = 0
leave_evt2._pad0 = 0
leave_evt2._pad1 = 0
leave_evt2._pad2 = 0
_push_event(leave_evt2)
_last_hover_win = -1
return moved
_render_count: t.CInt = 0
_cached_top_win: t.CInt = -1
_perf_t_mouse: t.CUInt64T = 0
_perf_t_kbd: t.CUInt64T = 0
_perf_t_render: t.CUInt64T = 0
_perf_t_flush: t.CUInt64T = 0
_perf_t_total: t.CUInt64T = 0
_perf_frames: t.CUInt32T = 0
_perf_start_ticks: t.CUInt64T = 0
_perf_t_bg: t.CUInt64T = 0
_perf_t_win: t.CUInt64T = 0
_perf_t_cur: t.CUInt64T = 0
_perf_t_fb: t.CUInt64T = 0
def _render():
global _render_count, _cached_top_win
global _perf_t_bg, _perf_t_win, _perf_t_cur, _perf_t_fb
w: t.CInt = _screen_w
h: t.CInt = _screen_h
pitch: t.CInt = _fb_pitch
back: UINT32PTR = _back_fb
_cached_top_win = _get_top_window()
# No need to erase cursor - dirty row mechanism redraws background + windows from scratch
t0: t.CUInt64T = timer.timer_get_ticks()
row_bytes: t.CInt = w * 4
if _bg_row_buf and _dirty_count > 0:
py_bg: t.CInt = _dirty_y_min
while py_bg <= _dirty_y_max:
if _dirty_rows[py_bg]:
string.memcpy(t.CVoid(t.CUInt64T(back) + py_bg * row_bytes, t.CPtr), _bg_row_buf, row_bytes)
py_bg += 1
t1: t.CUInt64T = timer.timer_get_ticks()
_perf_t_bg += (t1 - t0)
_draw_taskbar(back)
t2: t.CUInt64T = timer.timer_get_ticks()
sorted: t.CArray[t.CInt, MAX_APP_WINDOWS]
cnt: t.CInt = 0
for si in range(_win_count):
if _wins[si].active and not _wins[si].minimized:
sorted[cnt] = si
cnt += 1
for si in range(cnt - 1):
for sj in range(si + 1, cnt):
zi: t.CInt = _wins[sorted[si]].z_order
zj: t.CInt = _wins[sorted[sj]].z_order
if zi > zj:
tmp: t.CInt = sorted[si]
sorted[si] = sorted[sj]
sorted[sj] = tmp
for si in range(cnt):
_draw_app_window(back, c.Addr(_wins[sorted[si]]))
t3: t.CUInt64T = timer.timer_get_ticks()
_perf_t_win += (t3 - t2)
# Mark cursor rows as dirty so cursor gets flushed to framebuffer
_mark_rows_dirty(_cursor_y, CURSOR_H)
_draw_cursor(back)
t4: t.CUInt64T = timer.timer_get_ticks()
_perf_t_cur += (t4 - t3)
_wait_vblank()
if _dirty_count > 0:
py_fb: t.CInt = _dirty_y_min
while py_fb <= _dirty_y_max:
if _dirty_rows[py_fb]:
run_start: t.CInt = py_fb
while py_fb <= _dirty_y_max and _dirty_rows[py_fb]:
py_fb += 1
run_bytes: t.CInt = (py_fb - run_start) * row_bytes
string.memcpy(t.CVoid(t.CUInt64T(_fb) + run_start * pitch, t.CPtr), t.CVoid(t.CUInt64T(back) + run_start * row_bytes, t.CPtr), run_bytes)
else:
py_fb += 1
t5: t.CUInt64T = timer.timer_get_ticks()
_perf_t_fb += (t5 - t4)
_render_count += 1
def _draw_taskbar(back: UINT32PTR):
w: t.CInt = _screen_w
h: t.CInt = _screen_h
tb_h: t.CInt = TASKBAR_HEIGHT
bar_y: t.CInt = h - tb_h
if bar_y + tb_h <= _dirty_y_min or bar_y > _dirty_y_max:
return
bar_col: t.CUInt32T = gfx.COLOR_RGB(32, 32, 32)
line_col: t.CUInt32T = gfx.COLOR_RGB(60, 60, 60)
_fill_rect(back, w, h, 0, bar_y, w, tb_h, bar_col)
_fill_rect(back, w, h, 0, bar_y, w, 1, line_col)
logo_x: t.CInt = 4
logo_sz: t.CInt = TASKBAR_LOGO_SZ
logo_y: t.CInt = bar_y + (tb_h - logo_sz) // 2
lc1: t.CUInt32T = gfx.COLOR_RGB(80, 120, 220)
lc2: t.CUInt32T = gfx.COLOR_RGB(50, 80, 180)
lc3: t.CUInt32T = gfx.COLOR_RGB(100, 160, 255)
for lx in range(logo_sz):
for ly in range(logo_sz):
px: t.CInt = logo_x + lx
py: t.CInt = logo_y + ly
if px >= 0 and px < w and py >= 0 and py < h:
col: t.CUInt32T = lc2
cx: t.CInt = logo_sz // 2
cy: t.CInt = logo_sz // 2
dx: t.CInt = lx - cx
dy: t.CInt = ly - cy
dist: t.CInt = dx * dx + dy * dy
r2: t.CInt = cx * cx
if dist <= r2:
col = lc1
if dist <= r2 // 2:
col = lc3
back[py * w + px] = col
_draw_text(back, w, h, logo_x + logo_sz + 8, bar_y + (tb_h - 16) // 2, "ViperOS", 0xFFFFFFFF)
icon_sz: t.CInt = TASKBAR_ICON_SZ
icon_pad: t.CInt = TASKBAR_ICON_PAD
icon_x: t.CInt = logo_x + logo_sz + 80
icon_cnt: t.CInt = 0
for ti in range(_win_count):
if _wins[ti].active:
ix: t.CInt = icon_x + icon_cnt * (icon_sz + icon_pad)
iy: t.CInt = bar_y + (tb_h - icon_sz) // 2
is_focused: t.CInt = 1 if ti == _focused_win else 0
is_minimized: t.CInt = _wins[ti].minimized
ic_bg: t.CUInt32T = gfx.COLOR_RGB(40, 40, 70)
if is_focused:
ic_bg = gfx.COLOR_RGB(60, 60, 120)
elif is_minimized:
ic_bg = gfx.COLOR_RGB(30, 30, 50)
_fill_rect(back, w, h, ix, iy, icon_sz, icon_sz, ic_bg)
ic_border: t.CUInt32T = gfx.COLOR_RGB(80, 80, 140)
if is_focused:
ic_border = gfx.COLOR_RGB(120, 120, 220)
_fill_rect(back, w, h, ix, iy, icon_sz, 1, ic_border)
_fill_rect(back, w, h, ix, iy + icon_sz - 1, icon_sz, 1, ic_border)
_fill_rect(back, w, h, ix, iy, 1, icon_sz, ic_border)
_fill_rect(back, w, h, ix + icon_sz - 1, iy, 1, icon_sz, ic_border)
if _wins[ti].title[0] != 0:
first_ch: t.CChar = _wins[ti].title[0]
ch_col: t.CUInt32T = 0xFFFFFFFF
if not is_focused:
ch_col = gfx.COLOR_RGB(180, 180, 200)
ch_x: t.CInt = ix + (icon_sz - 8) // 2
ch_y: t.CInt = iy + (icon_sz - 16) // 2
_draw_char(back, w, h, ch_x, ch_y, first_ch, ch_col)
icon_cnt += 1
if _render_count % 30 == 0:
rtc.rtc_read_time()
hr: t.CUInt8T = rtc.rtc_get_hours()
mn: t.CUInt8T = rtc.rtc_get_minutes()
sc: t.CUInt8T = rtc.rtc_get_seconds()
day: t.CUInt8T = rtc.rtc_get_day()
mon: t.CUInt8T = rtc.rtc_get_month()
yr: t.CUInt16T = rtc.rtc_get_year()
time_buf: t.CArray[t.CChar, 64]
string.memset(c.Addr(time_buf), 0, 64)
viperlib.snprintf(c.Addr(time_buf), 64, "%04u-%02u-%02u %02u:%02u:%02u", t.CUnsignedInt(yr), t.CUnsignedInt(mon), t.CUnsignedInt(day), t.CUnsignedInt(hr), t.CUnsignedInt(mn), t.CUnsignedInt(sc))
time_str_x: t.CInt = w - 180
_draw_text(back, w, h, time_str_x, bar_y + (tb_h - 16) // 2, time_buf, 0xFFFFFFFF)
def _draw_window_shadow(back: UINT32PTR, bw: t.CInt, bh: t.CInt, wx: t.CInt, wy: t.CInt, ww: t.CInt, wh: t.CInt, is_rounded: t.CInt, rad: t.CInt):
if not SHADOW_ENABLED: return
sl: t.CInt = SHADOW_LEFT
sr: t.CInt = SHADOW_RIGHT
st: t.CInt = SHADOW_TOP
sb: t.CInt = SHADOW_BOTTOM
sa: t.CInt = SHADOW_ALPHA
shadow_col: t.CUInt32T = gfx.COLOR_RGB(0, 0, 0)
for py in range(wy - st, wy + wh + sb):
if py >= 0 and py < bh:
if not _dirty_rows[py]:
continue
for px in range(wx - sl, wx + ww + sr):
if px >= 0 and px < bw:
in_win: t.CInt = 0
if px >= wx and px < wx + ww and py >= wy and py < wy + wh:
in_win = 1
if is_rounded:
rx: t.CInt = px - wx
ry: t.CInt = py - wy
if rx < rad and ry < rad:
if (rx - rad) * (rx - rad) + (ry - rad) * (ry - rad) > rad * rad:
in_win = 0
elif rx >= ww - rad and ry < rad:
if (rx - (ww - rad - 1)) * (rx - (ww - rad - 1)) + (ry - rad) * (ry - rad) > rad * rad:
in_win = 0
elif rx < rad and ry >= wh - rad:
if (rx - rad) * (rx - rad) + (ry - (wh - rad - 1)) * (ry - (wh - rad - 1)) > rad * rad:
in_win = 0
elif rx >= ww - rad and ry >= wh - rad:
if (rx - (ww - rad - 1)) * (rx - (ww - rad - 1)) + (ry - (wh - rad - 1)) * (ry - (wh - rad - 1)) > rad * rad:
in_win = 0
if in_win:
continue
cpx: t.CInt = px
if cpx < wx: cpx = wx
elif cpx > wx + ww - 1: cpx = wx + ww - 1
cpy: t.CInt = py
if cpy < wy: cpy = wy
elif cpy > wy + wh - 1: cpy = wy + wh - 1
if is_rounded and rad > 0:
if cpx < wx + rad and cpy < wy + rad:
acx: t.CInt = wx + rad
acy: t.CInt = wy + rad
adx: t.CInt = cpx - acx
ady: t.CInt = cpy - acy
ad_sq: t.CInt = adx * adx + ady * ady
if ad_sq > rad * rad and ad_sq > 0:
aax: t.CInt = adx
if aax < 0: aax = 0 - aax
aay: t.CInt = ady
if aay < 0: aay = 0 - aay
am: t.CInt = aax
if aay > am: am = aay
an: t.CInt = aax
if aay < an: an = aay
ad: t.CInt = am + (an * 3) / 8
if ad > 0:
cpx = acx + (adx * rad) / ad
cpy = acy + (ady * rad) / ad
elif cpx >= wx + ww - rad and cpy < wy + rad:
acx = wx + ww - rad - 1
acy = wy + rad
adx = cpx - acx
ady = cpy - acy
ad_sq = adx * adx + ady * ady
if ad_sq > rad * rad and ad_sq > 0:
aax = adx
if aax < 0: aax = 0 - aax
aay = ady
if aay < 0: aay = 0 - aay
am = aax
if aay > am: am = aay
an = aax
if aay < an: an = aay
ad = am + (an * 3) / 8
if ad > 0:
cpx = acx + (adx * rad) / ad
cpy = acy + (ady * rad) / ad
elif cpx < wx + rad and cpy >= wy + wh - rad:
acx = wx + rad
acy = wy + wh - rad - 1
adx = cpx - acx
ady = cpy - acy
ad_sq = adx * adx + ady * ady
if ad_sq > rad * rad and ad_sq > 0:
aax = adx
if aax < 0: aax = 0 - aax
aay = ady
if aay < 0: aay = 0 - aay
am = aax
if aay > am: am = aay
an = aax
if aay < an: an = aay
ad = am + (an * 3) / 8
if ad > 0:
cpx = acx + (adx * rad) / ad
cpy = acy + (ady * rad) / ad
elif cpx >= wx + ww - rad and cpy >= wy + wh - rad:
acx = wx + ww - rad - 1
acy = wy + wh - rad - 1
adx = cpx - acx
ady = cpy - acy
ad_sq = adx * adx + ady * ady
if ad_sq > rad * rad and ad_sq > 0:
aax = adx
if aax < 0: aax = 0 - aax
aay = ady
if aay < 0: aay = 0 - aay
am = aax
if aay > am: am = aay
an = aax
if aay < an: an = aay
ad = am + (an * 3) / 8
if ad > 0:
cpx = acx + (adx * rad) / ad
cpy = acy + (ady * rad) / ad
dx: t.CInt = px - cpx
dy: t.CInt = py - cpy
adx_d: t.CInt = dx
if adx_d < 0: adx_d = 0 - adx_d
ady_d: t.CInt = dy
if ady_d < 0: ady_d = 0 - ady_d
dist: t.CInt = adx_d
if ady_d > dist: dist = ady_d
dist = dist + ((adx_d + ady_d - dist) * 3) / 8
me_x: t.CInt = sl
if dx > 0: me_x = sr
me_y: t.CInt = st
if dy > 0: me_y = sb
me: t.CInt = me_x
if adx_d + ady_d > 0:
me = (me_x * adx_d + me_y * ady_d) / (adx_d + ady_d)
if me > 0 and dist > 0 and dist < me:
t_val: t.CInt = (256 * dist) / me
t_inv: t.CInt = 256 - t_val
a: t.CInt = (sa * t_inv * t_inv) / 65536
if a > 0:
back[py * bw + px] = _alpha_blend(back[py * bw + px], shadow_col, a)
def _cursor_save_bg(back: UINT32PTR, cx: t.CInt, cy: t.CInt, w: t.CInt, h: t.CInt):
global _cursor_saved
cw: t.CInt = CURSOR_W
ch: t.CInt = CURSOR_H
for dy in range(ch):
for dx in range(cw):
sx: t.CInt = cx + dx
sy: t.CInt = cy + dy
if sx >= 0 and sx < w and sy >= 0 and sy < h:
_cursor_save_buf[dy * cw + dx] = back[sy * w + sx]
else:
_cursor_save_buf[dy * cw + dx] = 0
_cursor_saved = 1
def _cursor_restore_bg(back: UINT32PTR, cx: t.CInt, cy: t.CInt, w: t.CInt, h: t.CInt):
global _cursor_saved
if _cursor_saved == 0: return
cw: t.CInt = CURSOR_W
ch: t.CInt = CURSOR_H
for dy in range(ch):
for dx in range(cw):
sx: t.CInt = cx + dx
sy: t.CInt = cy + dy
if sx >= 0 and sx < w and sy >= 0 and sy < h:
back[sy * w + sx] = _cursor_save_buf[dy * cw + dx]
_cursor_saved = 0
def _cursor_draw_bitmap(back: UINT32PTR, cx: t.CInt, cy: t.CInt, w: t.CInt, h: t.CInt, bmp: t.CArray[t.CUInt8T, 256]):
cw: t.CInt = CURSOR_W
ch: t.CInt = CURSOR_H
white: t.CUInt32T = gfx.COLOR_RGB(255, 255, 255)
black: t.CUInt32T = gfx.COLOR_RGB(0, 0, 0)
for dy in range(ch):
for dx in range(cw):
pixel: t.CUInt8T = bmp[dy * cw + dx]
if pixel != 0:
px: t.CInt = cx + dx
py: t.CInt = cy + dy
if px >= 0 and px < w and py >= 0 and py < h:
if pixel == 1:
back[py * w + px] = black
elif pixel == 2:
back[py * w + px] = white
def _cursor_draw_xor(back: UINT32PTR, cx: t.CInt, cy: t.CInt, w: t.CInt, h: t.CInt, bmp: t.CArray[t.CUInt8T, 256]):
cw: t.CInt = CURSOR_W
ch: t.CInt = CURSOR_H
xor_val: t.CUInt32T = t.CUInt32T(0x00FFFFFF)
for dy in range(ch):
for dx in range(cw):
pixel: t.CUInt8T = bmp[dy * cw + dx]
if pixel != 0:
px: t.CInt = cx + dx
py: t.CInt = cy + dy
if px >= 0 and px < w and py >= 0 and py < h:
back[py * w + px] = back[py * w + px] ^ xor_val
def _draw_cursor(back: UINT32PTR):
cx: t.CInt = _cursor_x
cy: t.CInt = _cursor_y
w: t.CInt = _screen_w
h: t.CInt = _screen_h
cur_type: t.CInt = CURSOR_DEFAULT
if _last_hover_win >= 0 and _last_hover_win < _win_count:
cur_type = _wins[_last_hover_win].cursor_type
if cur_type == CURSOR_IBEAM:
_cursor_draw_bitmap(back, cx, cy, w, h, _CURSOR_IBEAM)
elif cur_type == CURSOR_RESIZE_EW:
_cursor_draw_bitmap(back, cx, cy, w, h, _CURSOR_RESIZE_EW)
elif cur_type == CURSOR_RESIZE_NS:
_cursor_draw_bitmap(back, cx, cy, w, h, _CURSOR_RESIZE_NS)
elif cur_type == CURSOR_RESIZE_NWSE:
_cursor_draw_bitmap(back, cx, cy, w, h, _CURSOR_RESIZE_NWSE)
else:
_cursor_draw_bitmap(back, cx, cy, w, h, _CURSOR_ARROW)
def _erase_cursor(back: UINT32PTR):
global _cursor_saved
if _cursor_saved == 0: return
_cursor_restore_bg(back, _prev_cursor_x, _prev_cursor_y, _screen_w, _screen_h)
def desktop_thread(arg: t.CVoid | t.CPtr) -> t.CInt:
global _last_clock_sec, _mouse_moved
global _prev_cursor_x, _prev_cursor_y
global _perf_t_mouse, _perf_t_kbd, _perf_t_render, _perf_t_flush, _perf_t_total, _perf_frames, _perf_start_ticks
global _perf_t_bg, _perf_t_win, _perf_t_cur, _perf_t_fb
serial.puts("[desktop] thread started\n")
_mark_all_dirty()
_dt_hb_sec: t.CUInt32T = 0
tb_h_dt: t.CInt = TASKBAR_HEIGHT
_perf_buf: t.CArray[t.CChar, 256]
_perf_start_ticks = timer.timer_get_ticks()
_kbd_poll_ticks: t.CUInt64T = 0
_mouse_poll_ticks: t.CUInt64T = 0
while True:
_loop_t0: t.CUInt64T = timer.timer_get_ticks()
# Poll mouse at most every 4ms to avoid excessive I/O port overhead
_now_ticks: t.CUInt64T = timer.timer_get_ticks()
if _now_ticks - _mouse_poll_ticks >= 4:
_mouse_poll_ticks = _now_ticks
_mt0: t.CUInt64T = timer.timer_get_ticks()
mouse_active: t.CInt = _update_mouse()
_mt1: t.CUInt64T = timer.timer_get_ticks()
_perf_t_mouse += (_mt1 - _mt0)
# Poll keyboard at most every 4ms to avoid excessive I/O port overhead
if _now_ticks - _kbd_poll_ticks >= 4:
_kbd_poll_ticks = _now_ticks
_kt0: t.CUInt64T = timer.timer_get_ticks()
_update_keyboard()
_kt1: t.CUInt64T = timer.timer_get_ticks()
_perf_t_kbd += (_kt1 - _kt0)
_heartbeat_check()
sched.Scheduler.try_reschedule()
_dt_cur_sec: t.CUInt32T = timer.timer_get_seconds()
if _dt_cur_sec != _dt_hb_sec:
_dt_hb_sec = _dt_cur_sec
cur_sec: t.CUInt32T = timer.timer_get_seconds()
if cur_sec != _last_clock_sec:
_last_clock_sec = cur_sec
_mark_rows_dirty(_screen_h - tb_h_dt, tb_h_dt)
if _mouse_moved:
_mouse_moved = 0
sz: t.CInt = CURSOR_H
_mark_rows_dirty(_prev_cursor_y, sz)
_mark_rows_dirty(_cursor_y, sz)
_prev_cursor_x = _cursor_x
_prev_cursor_y = _cursor_y
top_win: t.CInt = _hit_test_window(_cursor_x, _cursor_y)
for ai in range(_win_count):
if _wins[ai].active and not _wins[ai].minimized:
th: t.CInt = _get_title_height(_wins[ai].style)
btn_w: t.CInt = CLOSE_BTN_SIZE
btn_h: t.CInt = CLOSE_BTN_SIZE
ew_hov: t.CInt = _eff_w(c.Addr(_wins[ai]))
btn_x: t.CInt = _wins[ai].x + ew_hov - btn_w - 4
btn_y: t.CInt = _wins[ai].y + (th - btn_h) // 2
if _wins[ai].style == STYLE_WIN7:
btn_w = CLOSE_BTN_W7_W
btn_h = CLOSE_BTN_W7_H
btn_x = _wins[ai].x + ew_hov - btn_w - 5
btn_y = _wins[ai].y + (th - btn_h) // 2
elif _wins[ai].style == STYLE_WINXP:
btn_w = CLOSE_BTN_XP_SZ
btn_h = CLOSE_BTN_XP_SZ
btn_x = _wins[ai].x + ew_hov - btn_w - 5
btn_y = _wins[ai].y + (th - btn_h) // 2
elif _wins[ai].style == STYLE_WIN10 or _wins[ai].style == STYLE_WIN11:
btn_w = CLOSE_BTN_W_WIN
btn_h = th - 2
btn_x = _wins[ai].x + ew_hov - btn_w
btn_y = _wins[ai].y + 1
max_bx3: t.CInt = btn_x - btn_w
if _wins[ai].style == STYLE_WIN7 or _wins[ai].style == STYLE_WINXP:
max_bx3 = btn_x - btn_w - 1
min_bx3: t.CInt = max_bx3 - btn_w
if _wins[ai].style == STYLE_WIN7 or _wins[ai].style == STYLE_WINXP:
min_bx3 = max_bx3 - btn_w - 1
is_over_close: t.CInt = 0
is_over_max: t.CInt = 0
is_over_min: t.CInt = 0
if ai == top_win:
if _cursor_x >= btn_x and _cursor_x < btn_x + btn_w and _cursor_y >= btn_y and _cursor_y < btn_y + btn_h:
is_over_close = 1
elif _cursor_x >= max_bx3 and _cursor_x < max_bx3 + btn_w and _cursor_y >= btn_y and _cursor_y < btn_y + btn_h:
is_over_max = 1
elif _cursor_x >= min_bx3 and _cursor_x < min_bx3 + btn_w and _cursor_y >= btn_y and _cursor_y < btn_y + btn_h:
is_over_min = 1
if is_over_close and _wins[ai].close_hover < 255:
_old_hov: t.CInt = _wins[ai].close_hover
_wins[ai].close_hover += 60
if _wins[ai].close_hover > 255:
_wins[ai].close_hover = 255
if _wins[ai].close_hover != _old_hov:
_mark_rows_dirty(btn_y, btn_h)
elif not is_over_close and _wins[ai].close_hover > 0:
_old_hov2: t.CInt = _wins[ai].close_hover
_wins[ai].close_hover -= 45
if _wins[ai].close_hover < 0:
_wins[ai].close_hover = 0
if _wins[ai].close_hover != _old_hov2:
_mark_rows_dirty(btn_y, btn_h)
if is_over_max and _wins[ai].max_hover < 255:
_old_mh: t.CInt = _wins[ai].max_hover
_wins[ai].max_hover += 60
if _wins[ai].max_hover > 255:
_wins[ai].max_hover = 255
if _wins[ai].max_hover != _old_mh:
_mark_rows_dirty(btn_y, btn_h)
elif not is_over_max and _wins[ai].max_hover > 0:
_old_mh2: t.CInt = _wins[ai].max_hover
_wins[ai].max_hover -= 45
if _wins[ai].max_hover < 0:
_wins[ai].max_hover = 0
if _wins[ai].max_hover != _old_mh2:
_mark_rows_dirty(btn_y, btn_h)
if is_over_min and _wins[ai].min_hover < 255:
_old_nh: t.CInt = _wins[ai].min_hover
_wins[ai].min_hover += 60
if _wins[ai].min_hover > 255:
_wins[ai].min_hover = 255
if _wins[ai].min_hover != _old_nh:
_mark_rows_dirty(btn_y, btn_h)
elif not is_over_min and _wins[ai].min_hover > 0:
_old_nh2: t.CInt = _wins[ai].min_hover
_wins[ai].min_hover -= 45
if _wins[ai].min_hover < 0:
_wins[ai].min_hover = 0
if _wins[ai].min_hover != _old_nh2:
_mark_rows_dirty(btn_y, btn_h)
if _has_dirty_rows():
_rt0: t.CUInt64T = timer.timer_get_ticks()
_render()
_rt1: t.CUInt64T = timer.timer_get_ticks()
_perf_t_render += (_rt1 - _rt0)
_ft0: t.CUInt64T = timer.timer_get_ticks()
_clear_dirty_flags()
_ft1: t.CUInt64T = timer.timer_get_ticks()
_perf_t_flush += (_ft1 - _ft0)
_perf_frames += 1
_loop_t1: t.CUInt64T = timer.timer_get_ticks()
_perf_t_total += (_loop_t1 - _loop_t0)
_now_ticks: t.CUInt64T = timer.timer_get_ticks()
_elapsed_ms: t.CUInt64T = _now_ticks - _perf_start_ticks
if _elapsed_ms >= 1000:
_fps_val: t.CUInt32T = 0
if _elapsed_ms > 0:
_fps_val = t.CUInt32T(t.CUInt64T(_perf_frames) * 1000 / _elapsed_ms)
string.memset(c.Addr(_perf_buf), 0, 256)
viperlib.snprintf(c.Addr(_perf_buf), 256, "[perf] fps=%u wall=%lu work=%lu mouse=%lu kbd=%lu render=%lu(bg=%lu win=%lu cur=%lu fb=%lu) dirty=%d vsync=%d\n", _fps_val, t.CUInt64T(_elapsed_ms), t.CUInt64T(_perf_t_total), t.CUInt64T(_perf_t_mouse), t.CUInt64T(_perf_t_kbd), t.CUInt64T(_perf_t_render), t.CUInt64T(_perf_t_bg), t.CUInt64T(_perf_t_win), t.CUInt64T(_perf_t_cur), t.CUInt64T(_perf_t_fb), _dirty_count, _vblank_ok)
serial.puts(c.Addr(_perf_buf))
_perf_t_mouse = 0
_perf_t_kbd = 0
_perf_t_render = 0
_perf_t_flush = 0
_perf_t_total = 0
_perf_t_bg = 0
_perf_t_win = 0
_perf_t_cur = 0
_perf_t_fb = 0
_perf_frames = 0
_perf_start_ticks = _now_ticks
sched.Scheduler.try_reschedule()
return 0
def start(fb_ptr: t.CVoid | t.CPtr, w: t.CInt, h: t.CInt, pitch: t.CInt = 0) -> sched.Thread | t.CPtr:
global _fb, _screen_w, _screen_h, _fb_pitch, _cursor_x, _cursor_y
global _back_fb, _row_buf
global _dirty_rows, _prev_cursor_x, _prev_cursor_y
_fb = fb_ptr
_screen_w = w
_screen_h = h
if pitch > 0:
_fb_pitch = pitch
else:
_fb_pitch = w * 4
_cursor_x = w // 2
_cursor_y = h // 2
_prev_cursor_x = _cursor_x
_prev_cursor_y = _cursor_y
_dirty_rows = mm.malloc(h)
if _dirty_rows:
string.memset(_dirty_rows, 0, h)
else:
serial.puts("[desktop] dirty_rows malloc FAILED\n")
return None
_back_fb = mm.malloc(w * h * 4)
if _back_fb:
string.memset(_back_fb, 0, w * h * 4)
else:
serial.puts("[desktop] back_fb malloc FAILED\n")
return None
_row_buf = mm.malloc(w * 4)
if _row_buf:
string.memset(_row_buf, 0, w * 4)
else:
serial.puts("[desktop] row_buf malloc FAILED!\n")
_bg_row_buf = mm.malloc(w * 4)
if _bg_row_buf:
desktop_init_col: t.CUInt32T = gfx.COLOR_RGB(25, 25, 35)
for ri in range(w):
_bg_row_buf[ri] = desktop_init_col
else:
serial.puts("[desktop] bg_row_buf malloc FAILED!\n")
_mark_all_dirty()
p: process.Process | t.CPtr = process.ProcessManager.create_process("desktop", None)
desktop_pid: t.CInt = 0
if p:
desktop_pid = p.pid
th: sched.Thread | t.CPtr = sched.Scheduler.create_thread(desktop_thread, None, desktop_pid)
if p and th:
p.addThread(th.tid)
return th
def _get_top_window() -> t.CInt:
best: t.CInt = -1
best_z: t.CInt = -1
for ai in range(_win_count):
if _wins[ai].active and not _wins[ai].minimized:
z: t.CInt = _wins[ai].z_order
if z > best_z:
best_z = z
best = ai
return best
def _draw_min_btn(back: UINT32PTR, bw: t.CInt, bh: t.CInt, bx: t.CInt, by: t.CInt, btn_w: t.CInt, btn_h: t.CInt, hover_alpha: t.CInt, style: t.CInt):
if hover_alpha > 0:
hover_col: t.CUInt32T = gfx.COLOR_RGB(80, 80, 80)
if style == STYLE_WIN7:
hover_col = gfx.COLOR_RGB(200, 220, 240)
elif style == STYLE_WINXP:
hover_col = gfx.COLOR_RGB(180, 210, 240)
for py in range(by, by + btn_h):
if py >= 0 and py < bh:
for px in range(bx, bx + btn_w):
if px >= 0 and px < bw:
bg_pixel: t.CUInt32T = back[py * bw + px]
back[py * bw + px] = _alpha_blend(bg_pixel, hover_col, hover_alpha)
line_col: t.CUInt32T = gfx.COLOR_RGB(180, 180, 180)
if hover_alpha > 0:
line_col = gfx.COLOR_RGB(255, 255, 255)
cx: t.CInt = bx + btn_w // 2
cy: t.CInt = by + btn_h // 2
lw: t.CInt = 5
if btn_w >= 30:
lw = 8
ly: t.CInt = cy + 3
if ly >= 0 and ly < bh:
for lx in range(cx - lw, cx + lw + 1):
if lx >= 0 and lx < bw:
back[ly * bw + lx] = line_col
def _draw_max_btn(back: UINT32PTR, bw: t.CInt, bh: t.CInt, bx: t.CInt, by: t.CInt, btn_w: t.CInt, btn_h: t.CInt, hover_alpha: t.CInt, style: t.CInt, is_maximized: t.CInt):
if hover_alpha > 0:
hover_col2: t.CUInt32T = gfx.COLOR_RGB(80, 80, 80)
if style == STYLE_WIN7:
hover_col2 = gfx.COLOR_RGB(200, 220, 240)
elif style == STYLE_WINXP:
hover_col2 = gfx.COLOR_RGB(180, 210, 240)
for py2 in range(by, by + btn_h):
if py2 >= 0 and py2 < bh:
for px2 in range(bx, bx + btn_w):
if px2 >= 0 and px2 < bw:
bg_pixel2: t.CUInt32T = back[py2 * bw + px2]
back[py2 * bw + px2] = _alpha_blend(bg_pixel2, hover_col2, hover_alpha)
rect_col: t.CUInt32T = gfx.COLOR_RGB(180, 180, 180)
if hover_alpha > 0:
rect_col = gfx.COLOR_RGB(255, 255, 255)
cx2: t.CInt = bx + btn_w // 2
cy2: t.CInt = by + btn_h // 2
sz: t.CInt = 4
if btn_w >= 30:
sz = 6
if is_maximized:
off: t.CInt = 2
for ry1 in range(cy2 - sz + off, cy2 + sz + off + 1):
if ry1 >= 0 and ry1 < bh:
for rx1 in range(cx2 - sz + off, cx2 + sz + off + 1):
if rx1 >= 0 and rx1 < bw:
is_top: t.CInt = 0
if ry1 == cy2 - sz + off or ry1 == cy2 - sz + off + 1:
is_top = 1
is_border: t.CInt = 0
if ry1 == cy2 - sz + off or ry1 == cy2 + sz + off or rx1 == cx2 - sz + off or rx1 == cx2 + sz + off:
is_border = 1
if is_top or is_border:
back[ry1 * bw + rx1] = rect_col
for ry2 in range(cy2 - sz, cy2 + sz + 1):
if ry2 >= 0 and ry2 < bh:
for rx2 in range(cx2 - sz, cx2 + sz + 1):
if rx2 >= 0 and rx2 < bw:
if ry2 == cy2 - sz or ry2 == cy2 - sz + 1 or rx2 == cx2 - sz or rx2 == cx2 + sz:
back[ry2 * bw + rx2] = rect_col
else:
for ry3 in range(cy2 - sz, cy2 + sz + 1):
if ry3 >= 0 and ry3 < bh:
for rx3 in range(cx2 - sz, cx2 + sz + 1):
if rx3 >= 0 and rx3 < bw:
if ry3 == cy2 - sz or ry3 == cy2 + sz or rx3 == cx2 - sz or rx3 == cx2 + sz:
back[ry3 * bw + rx3] = rect_col
top_y: t.CInt = cy2 - sz
if top_y >= 0 and top_y < bh:
for tx in range(cx2 - sz, cx2 + sz + 1):
if tx >= 0 and tx < bw:
back[top_y * bw + tx] = rect_col
if top_y + 1 < bh:
for tx2 in range(cx2 - sz, cx2 + sz + 1):
if tx2 >= 0 and tx2 < bw:
back[(top_y + 1) * bw + tx2] = rect_col
def _draw_close_btn(back: UINT32PTR, bw: t.CInt, bh: t.CInt, bx: t.CInt, by: t.CInt, btn_w: t.CInt, btn_h: t.CInt, hover_alpha: t.CInt, style: t.CInt, clip_wx: t.CInt, clip_wy: t.CInt, clip_ww: t.CInt, clip_r: t.CInt):
x_col: t.CUInt32T = gfx.COLOR_RGB(200, 200, 200)
if style == STYLE_CLASSIC:
bg: t.CUInt32T = gfx.COLOR_RGB(40, 40, 70)
if hover_alpha > 0:
red: t.CUInt32T = gfx.COLOR_RGB(200, 50, 50)
bg = _alpha_blend(bg, red, hover_alpha)
_fill_rect(back, bw, bh, bx, by, btn_w, btn_h, bg)
if hover_alpha > 128:
x_col = gfx.COLOR_RGB(255, 255, 255)
elif style == STYLE_WIN7:
br: t.CInt = CLOSE_BTN_W7_R
bg_r: t.CInt = 200
bg_g: t.CInt = 50
bg_b: t.CInt = 50
if hover_alpha > 0:
bg_r = 200 + ((232 - 200) * hover_alpha) / 255
bg_g = 50 + ((17 - 50) * hover_alpha) / 255
bg_b = 50 + ((35 - 50) * hover_alpha) / 255
bg_col7: t.CUInt32T = gfx.COLOR_RGB(bg_r, bg_g, bg_b)
for py2 in range(by, by + btn_h):
if py2 >= 0 and py2 < bh:
for px2 in range(bx, bx + btn_w):
if px2 >= 0 and px2 < bw:
lrx: t.CInt = px2 - bx
lry: t.CInt = py2 - by
if lry < br:
if lrx < br:
if (lrx - br) * (lrx - br) + (lry - br) * (lry - br) > br * br:
continue
elif lrx >= btn_w - br:
if (lrx - (btn_w - br - 1)) * (lrx - (btn_w - br - 1)) + (lry - br) * (lry - br) > br * br:
continue
elif lry >= btn_h - br:
if lrx < br:
if (lrx - br) * (lrx - br) + (lry - (btn_h - br - 1)) * (lry - (btn_h - br - 1)) > br * br:
continue
elif lrx >= btn_w - br:
if (lrx - (btn_w - br - 1)) * (lrx - (btn_w - br - 1)) + (lry - (btn_h - br - 1)) * (lry - (btn_h - br - 1)) > br * br:
continue
if clip_r > 0:
crx: t.CInt = px2 - clip_wx
cry: t.CInt = py2 - clip_wy
if cry < clip_r and crx >= clip_ww - clip_r:
cdx: t.CInt = crx - (clip_ww - clip_r - 1)
cdy: t.CInt = cry - clip_r
if cdx * cdx + cdy * cdy > clip_r * clip_r:
continue
back[py2 * bw + px2] = bg_col7
x_col = gfx.COLOR_RGB(255, 255, 255)
if hover_alpha > 0:
x_col = _alpha_blend(gfx.COLOR_RGB(255, 255, 255), gfx.COLOR_RGB(255, 240, 240), hover_alpha)
elif style == STYLE_WINXP:
br_xp: t.CInt = 3
bg_r_xp: t.CInt = 200
bg_g_xp: t.CInt = 50
bg_b_xp: t.CInt = 50
if hover_alpha > 0:
bg_r_xp = 200 + ((232 - 200) * hover_alpha) / 255
bg_g_xp = 50 + ((17 - 50) * hover_alpha) / 255
bg_b_xp = 50 + ((35 - 50) * hover_alpha) / 255
bg_col_xp: t.CUInt32T = gfx.COLOR_RGB(bg_r_xp, bg_g_xp, bg_b_xp)
for py_xp in range(by, by + btn_h):
if py_xp >= 0 and py_xp < bh:
for px_xp in range(bx, bx + btn_w):
if px_xp >= 0 and px_xp < bw:
lrx_xp: t.CInt = px_xp - bx
lry_xp: t.CInt = py_xp - by
if lry_xp < br_xp:
if lrx_xp < br_xp:
if (lrx_xp - br_xp) * (lrx_xp - br_xp) + (lry_xp - br_xp) * (lry_xp - br_xp) > br_xp * br_xp:
continue
elif lrx_xp >= btn_w - br_xp:
if (lrx_xp - (btn_w - br_xp - 1)) * (lrx_xp - (btn_w - br_xp - 1)) + (lry_xp - br_xp) * (lry_xp - br_xp) > br_xp * br_xp:
continue
elif lry_xp >= btn_h - br_xp:
if lrx_xp < br_xp:
if (lrx_xp - br_xp) * (lrx_xp - br_xp) + (lry_xp - (btn_h - br_xp - 1)) * (lry_xp - (btn_h - br_xp - 1)) > br_xp * br_xp:
continue
elif lrx_xp >= btn_w - br_xp:
if (lrx_xp - (btn_w - br_xp - 1)) * (lrx_xp - (btn_w - br_xp - 1)) + (lry_xp - (btn_h - br_xp - 1)) * (lry_xp - (btn_h - br_xp - 1)) > br_xp * br_xp:
continue
back[py_xp * bw + px_xp] = bg_col_xp
x_col = gfx.COLOR_RGB(255, 255, 255)
if hover_alpha > 0:
x_col = _alpha_blend(gfx.COLOR_RGB(255, 255, 255), gfx.COLOR_RGB(255, 240, 240), hover_alpha)
else:
if hover_alpha > 0:
red = gfx.COLOR_RGB(232, 17, 35)
for py3 in range(by, by + btn_h):
if py3 >= 0 and py3 < bh:
for px3 in range(bx, bx + btn_w):
if px3 >= 0 and px3 < bw:
if clip_r > 0:
crx3: t.CInt = px3 - clip_wx
cry3: t.CInt = py3 - clip_wy
if cry3 < clip_r and crx3 >= clip_ww - clip_r:
cdx3: t.CInt = crx3 - (clip_ww - clip_r - 1)
cdy3: t.CInt = cry3 - clip_r
if cdx3 * cdx3 + cdy3 * cdy3 > clip_r * clip_r:
continue
bg_pixel: t.CUInt32T = back[py3 * bw + px3]
back[py3 * bw + px3] = _alpha_blend(bg_pixel, red, hover_alpha)
x_col = gfx.COLOR_RGB(150, 150, 150)
if hover_alpha > 0:
x_col = _alpha_blend(gfx.COLOR_RGB(150, 150, 150), gfx.COLOR_RGB(255, 255, 255), hover_alpha)
cx: t.CInt = bx + btn_w // 2
cy: t.CInt = by + btn_h // 2
d: t.CInt = 3
if btn_w >= 30:
d = 4
i: t.CInt = -d
while i <= d:
px1: t.CInt = cx + i
py1: t.CInt = cy + i
px2b: t.CInt = cx + i
py2b: t.CInt = cy - i
if px1 >= 0 and px1 < bw and py1 >= 0 and py1 < bh:
if clip_r > 0:
crx1: t.CInt = px1 - clip_wx
cry1: t.CInt = py1 - clip_wy
if cry1 < clip_r and crx1 >= clip_ww - clip_r:
cdx1: t.CInt = crx1 - (clip_ww - clip_r - 1)
cdy1: t.CInt = cry1 - clip_r
if cdx1 * cdx1 + cdy1 * cdy1 > clip_r * clip_r:
i += 1
continue
back[py1 * bw + px1] = x_col
if px2b >= 0 and px2b < bw and py2b >= 0 and py2b < bh:
if clip_r > 0:
crx2: t.CInt = px2b - clip_wx
cry2: t.CInt = py2b - clip_wy
if cry2 < clip_r and crx2 >= clip_ww - clip_r:
cdx2: t.CInt = crx2 - (clip_ww - clip_r - 1)
cdy2: t.CInt = cry2 - clip_r
if cdx2 * cdx2 + cdy2 * cdy2 > clip_r * clip_r:
i += 1
continue
back[py2b * bw + px2b] = x_col
i += 1
def _draw_app_window(back: UINT32PTR, win: AppWindow | t.CPtr):
w: t.CInt = _screen_w
h: t.CInt = _screen_h
wx: t.CInt = win.x
wy: t.CInt = win.y
ww: t.CInt = _eff_w(win)
wh: t.CInt = win.h
ch: t.CInt = _eff_h(win)
style: t.CInt = win.style
th: t.CInt = _get_title_height(style)
if wy + wh <= _dirty_y_min or wy > _dirty_y_max:
return
top_idx: t.CInt = _cached_top_win
is_active: t.CInt = 0
if top_idx >= 0:
top_z: t.CInt = _wins[top_idx].z_order
if win.z_order == top_z:
is_active = 1
title_col: t.CUInt32T = gfx.COLOR_RGB(50, 50, 90)
border_col: t.CUInt32T = gfx.COLOR_RGB(100, 100, 180)
content_col: t.CUInt32T = gfx.COLOR_RGB(30, 30, 50)
outline_col: t.CUInt32T = gfx.COLOR_RGB(100, 100, 180)
title_alpha: t.CInt = 255
has_separator: t.CInt = 1
if style == STYLE_WIN10:
has_separator = 0
if is_active:
title_col = gfx.COLOR_RGB(0, 0, 0)
border_col = gfx.COLOR_RGB(0, 0, 0)
content_col = gfx.COLOR_RGB(32, 32, 32)
outline_col = gfx.COLOR_RGB(80, 80, 80)
else:
title_col = gfx.COLOR_RGB(32, 32, 32)
border_col = gfx.COLOR_RGB(32, 32, 32)
content_col = gfx.COLOR_RGB(24, 24, 24)
outline_col = gfx.COLOR_RGB(60, 60, 60)
elif style == STYLE_WIN11:
has_separator = 0
if is_active:
title_col = gfx.COLOR_RGB(32, 32, 32)
border_col = gfx.COLOR_RGB(32, 32, 32)
content_col = gfx.COLOR_RGB(32, 32, 32)
outline_col = gfx.COLOR_RGB(70, 70, 70)
else:
title_col = gfx.COLOR_RGB(40, 40, 40)
border_col = gfx.COLOR_RGB(40, 40, 40)
content_col = gfx.COLOR_RGB(40, 40, 40)
outline_col = gfx.COLOR_RGB(60, 60, 60)
elif style == STYLE_WIN7:
has_separator = 0
if is_active:
title_col = gfx.COLOR_RGB(156, 181, 209)
border_col = gfx.COLOR_RGB(156, 181, 209)
content_col = gfx.COLOR_RGB(240, 240, 240)
outline_col = gfx.COLOR_RGB(100, 130, 170)
else:
title_col = gfx.COLOR_RGB(180, 195, 210)
border_col = gfx.COLOR_RGB(180, 195, 210)
content_col = gfx.COLOR_RGB(240, 240, 240)
outline_col = gfx.COLOR_RGB(140, 155, 175)
title_alpha = 102
elif style == STYLE_WINXP:
has_separator = 0
if is_active:
title_col = gfx.COLOR_RGB(107, 158, 212)
border_col = gfx.COLOR_RGB(107, 158, 212)
content_col = gfx.COLOR_RGB(235, 235, 235)
outline_col = gfx.COLOR_RGB(43, 87, 151)
else:
title_col = gfx.COLOR_RGB(140, 160, 195)
border_col = gfx.COLOR_RGB(140, 160, 195)
content_col = gfx.COLOR_RGB(235, 235, 235)
outline_col = gfx.COLOR_RGB(100, 115, 145)
else:
if is_active:
title_col = gfx.COLOR_RGB(70, 70, 130)
border_col = gfx.COLOR_RGB(130, 130, 220)
outline_col = gfx.COLOR_RGB(130, 130, 220)
rounded: t.CInt = 0
if style == STYLE_WIN11 or style == STYLE_WIN7 or style == STYLE_WINXP:
rounded = 1
r: t.CInt = WIN_RADIUS
_draw_window_shadow(back, w, h, wx, wy, ww, wh, rounded, r)
if rounded:
for py in range(wy, wy + wh):
if py < 0 or py >= h: continue
if not _dirty_rows[py]: continue
ry: t.CInt = py - wy
row_col: t.CUInt32T = content_col
row_is_title: t.CInt = 0
row_is_sep: t.CInt = 0
if ry < th:
row_is_title = 1
if style == STYLE_WIN7:
ratio: t.CInt = (ry * 100) / th
if ratio > 100: ratio = 100
if is_active:
gr: t.CInt = 156 + ((186 - 156) * ratio) / 100
gg: t.CInt = 181 + ((209 - 181) * ratio) / 100
gb: t.CInt = 209 + ((233 - 209) * ratio) / 100
else:
gr = 180 + ((200 - 180) * ratio) / 100
gg = 195 + ((210 - 195) * ratio) / 100
gb = 210 + ((225 - 210) * ratio) / 100
row_col = gfx.COLOR_RGB(gr, gg, gb)
if ry == 0:
row_col = _alpha_blend(row_col, gfx.COLOR_RGB(220, 235, 255), 50)
elif ry == 1:
row_col = _alpha_blend(row_col, gfx.COLOR_RGB(210, 225, 250), 20)
if ry == th - 1:
row_col = _alpha_blend(row_col, gfx.COLOR_RGB(130, 160, 200), 25)
elif style == STYLE_WINXP:
ratio = (ry * 100) / th
if ratio > 100: ratio = 100
if is_active:
gr = 107 + ((58 - 107) * ratio) / 100
gg = 158 + ((124 - 158) * ratio) / 100
gb = 212 + ((195 - 212) * ratio) / 100
else:
gr = 150 + ((115 - 150) * ratio) / 100
gg = 170 + ((135 - 170) * ratio) / 100
gb = 205 + ((175 - 205) * ratio) / 100
row_col = gfx.COLOR_RGB(gr, gg, gb)
if ry == 0:
row_col = _alpha_blend(row_col, gfx.COLOR_RGB(200, 225, 255), 50)
elif ry == 1:
row_col = _alpha_blend(row_col, gfx.COLOR_RGB(200, 225, 255), 25)
if ry == th - 1:
row_col = _alpha_blend(row_col, gfx.COLOR_RGB(100, 160, 230), 40)
else:
row_col = title_col
elif ry == th and has_separator:
row_is_sep = 1
row_col = outline_col
in_corner_zone: t.CInt = 0
if ry < r or ry >= wh - r:
in_corner_zone = 1
if not in_corner_zone:
left_border: t.CInt = wx
right_border: t.CInt = wx + ww - 1
if left_border >= 0 and left_border < w:
back[py * w + left_border] = outline_col
if right_border >= 0 and right_border < w:
back[py * w + right_border] = outline_col
fill_left: t.CInt = wx + 1
fill_right: t.CInt = wx + ww - 1
if fill_left < 0: fill_left = 0
if fill_right > w: fill_right = w
fill_w: t.CInt = fill_right - fill_left
if fill_w > 0:
if row_is_title and (style == STYLE_WIN7 or style == STYLE_WINXP) and title_alpha < 255:
for fx in range(fill_left, fill_right):
bg_p: t.CUInt32T = back[py * w + fx]
back[py * w + fx] = _alpha_blend(bg_p, row_col, title_alpha)
else:
_fill_rect(back, w, h, fill_left, py, fill_w, 1, row_col)
else:
for px in range(wx, wx + ww):
if px < 0 or px >= w: continue
rx: t.CInt = px - wx
in_corner: t.CInt = 1
on_border: t.CInt = 0
if ry < r:
if rx < r:
in_corner = _is_in_corner(rx, ry, r, r, r)
elif rx >= ww - r:
in_corner = _is_in_corner(rx, ry, ww - r - 1, r, r)
elif ry >= wh - r:
if rx < r:
in_corner = _is_in_corner(rx, ry, r, wh - r - 1, r)
elif rx >= ww - r:
in_corner = _is_in_corner(rx, ry, ww - r - 1, wh - r - 1, r)
if not in_corner:
on_border = _is_on_border(rx, ry, ww, wh, r)
if not on_border:
continue
col: t.CUInt32T = row_col
is_border_pixel: t.CInt = 0
if on_border or _is_on_border(rx, ry, ww, wh, r):
col = outline_col
is_border_pixel = 1
ca: t.CInt = 255
if ry < r:
if rx < r:
ca = _corner_alpha(rx, ry, r, r, r)
elif rx >= ww - r:
ca = _corner_alpha(rx, ry, ww - r - 1, r, r)
elif ry >= wh - r:
if rx < r:
ca = _corner_alpha(rx, ry, r, wh - r - 1, r)
elif rx >= ww - r:
ca = _corner_alpha(rx, ry, ww - r - 1, wh - r - 1, r)
if is_border_pixel and ca > 0 and ca < 255:
ca = 255
if row_is_title and (style == STYLE_WIN7 or style == STYLE_WINXP) and ry >= 1 and rx >= 1 and rx < ww - 1 and ca == 255 and not is_border_pixel:
ca = title_alpha
if ca < 255:
bg_pixel: t.CUInt32T = back[py * w + px]
col = _alpha_blend(bg_pixel, col, ca)
back[py * w + px] = col
else:
_fill_rect(back, w, h, wx, wy, ww, th, title_col)
_fill_rect(back, w, h, wx, wy + th + 1, ww, ch, content_col)
if not has_separator:
_fill_rect(back, w, h, wx, wy + th, ww, 1, content_col)
if wx >= 0 and wx < w:
for py in range(wy, wy + wh):
if py >= 0 and py < h:
back[py * w + wx] = border_col
if wx + ww - 1 >= 0 and wx + ww - 1 < w:
for py in range(wy, wy + wh):
if py >= 0 and py < h:
back[py * w + wx + ww - 1] = border_col
_fill_rect(back, w, h, wx, wy, ww, 1, border_col)
_fill_rect(back, w, h, wx, wy + wh - 1, ww, 1, border_col)
if has_separator:
_fill_rect(back, w, h, wx, wy + th, ww, 1, border_col)
win_fb: UINT32PTR = win.fb
if win_fb:
fb_stride: t.CInt = win.fb_w
fbh: t.CInt = win.fb_h
if fb_stride <= 0 or fbh <= 0:
win_fb = t.CVoid(0, t.CPtr)
if win_fb:
draw_rows: t.CInt = ch
if draw_rows > fbh: draw_rows = fbh
if draw_rows < 0: draw_rows = 0
content_y: t.CInt = wy + th + 1
fb_total: t.CInt = fb_stride * fbh
for row in range(draw_rows):
dst_y: t.CInt = content_y + row
if dst_y < 0 or dst_y >= h: continue
if not _dirty_rows[dst_y]: continue
ry: t.CInt = th + 1 + row
if rounded:
if ry >= r and ry < wh - r:
src_x: t.CInt = 1
dst_x: t.CInt = wx + 1
if dst_x < 0:
src_x = src_x - dst_x
dst_x = 0
end_x: t.CInt = wx + ww - 1
if end_x > w: end_x = w
actual_w: t.CInt = end_x - dst_x
if actual_w <= 0: continue
if src_x + actual_w > fb_stride: actual_w = fb_stride - src_x
if actual_w <= 0: continue
src_off: t.CInt = row * fb_stride + src_x
if src_off + actual_w > fb_total: continue
string.memcpy(t.CVoid(t.CUInt64T(back) + (dst_y * w + dst_x) * 4, t.CPtr), t.CVoid(t.CUInt64T(win_fb) + src_off * 4, t.CPtr), actual_w * 4)
else:
copy_w: t.CInt = fb_stride - 1
if copy_w > ww - 1: copy_w = ww - 1
for col_px in range(1, copy_w):
if ry < r:
if col_px < r:
if not _is_in_corner(col_px, ry, r, r, r): continue
elif col_px >= ww - r:
if not _is_in_corner(col_px, ry, ww - r - 1, r, r): continue
elif ry >= wh - r:
if col_px < r:
if not _is_in_corner(col_px, ry, r, wh - r - 1, r): continue
elif col_px >= ww - r:
if not _is_in_corner(col_px, ry, ww - r - 1, wh - r - 1, r): continue
if _is_on_border(col_px, ry, ww, wh, r): continue
dst_x2: t.CInt = wx + col_px
if dst_x2 < 0 or dst_x2 >= w: continue
if col_px >= fb_stride: continue
px_off: t.CInt = row * fb_stride + col_px
if px_off < 0 or px_off >= fb_total: continue
back[dst_y * w + dst_x2] = win_fb[px_off]
else:
src_x2: t.CInt = 1
dst_x3: t.CInt = wx + 1
if dst_x3 < 0:
src_x2 = src_x2 - dst_x3
dst_x3 = 0
end_x2: t.CInt = wx + ww - 1
if end_x2 > w: end_x2 = w
actual_w2: t.CInt = end_x2 - dst_x3
if actual_w2 <= 0: continue
if src_x2 + actual_w2 > fb_stride: actual_w2 = fb_stride - src_x2
if actual_w2 <= 0: continue
src_off2: t.CInt = row * fb_stride + src_x2
if src_off2 + actual_w2 > fb_total: continue
string.memcpy(t.CVoid(t.CUInt64T(back) + (dst_y * w + dst_x3) * 4, t.CPtr), t.CVoid(t.CUInt64T(win_fb) + src_off2 * 4, t.CPtr), actual_w2 * 4)
btn_w: t.CInt = CLOSE_BTN_SIZE
btn_h: t.CInt = CLOSE_BTN_SIZE
btn_x: t.CInt = wx + ww - btn_w - 4
btn_y: t.CInt = wy + (th - btn_h) // 2
clip_r: t.CInt = 0
if style == STYLE_WIN7:
btn_w = CLOSE_BTN_W7_W
btn_h = CLOSE_BTN_W7_H
btn_x = wx + ww - btn_w - 5
btn_y = wy + (th - btn_h) // 2
if rounded:
clip_r = r
elif style == STYLE_WINXP:
btn_w = CLOSE_BTN_XP_SZ
btn_h = CLOSE_BTN_XP_SZ
btn_x = wx + ww - btn_w - 5
btn_y = wy + (th - btn_h) // 2
elif style == STYLE_WIN10 or style == STYLE_WIN11:
btn_w = CLOSE_BTN_W_WIN
btn_h = th - 2
btn_x = wx + ww - btn_w
btn_y = wy + 1
if rounded:
clip_r = r
_draw_close_btn(back, w, h, btn_x, btn_y, btn_w, btn_h, win.close_hover, style, wx, wy, ww, clip_r)
max_bx: t.CInt = btn_x - btn_w
if style == STYLE_WIN7 or style == STYLE_WINXP:
max_bx = btn_x - btn_w - 1
_draw_max_btn(back, w, h, max_bx, btn_y, btn_w, btn_h, win.max_hover, style, win.is_maximized)
min_bx: t.CInt = max_bx - btn_w
if style == STYLE_WIN7 or style == STYLE_WINXP:
min_bx = max_bx - btn_w - 1
_draw_min_btn(back, w, h, min_bx, btn_y, btn_w, btn_h, win.min_hover, style)
text_col: t.CUInt32T = 0xFFFFFFFF
if not is_active:
text_col = gfx.COLOR_RGB(170, 170, 170)
text_y: t.CInt = wy + (th - 16) // 2
_draw_text(back, w, h, wx + 8, text_y, c.Addr(win.title[0]), text_col)
def create(x: t.CInt, y: t.CInt, w: t.CInt, h: t.CInt, title: t.CConst | t.CChar | t.CPtr, style: t.CInt) -> t.CInt:
global _win_count, _next_z, _focused_win
if _win_count >= MAX_APP_WINDOWS: return -1
idx: t.CInt = _win_count
_win_count += 1
aw: AppWindow | t.CPtr = c.Addr(_wins[idx])
aw.active = 1
aw.x = x
aw.y = y
aw.w = w
aw.h = h
aw.content_h = h - _get_title_height(style) - 2
_next_z += 1
aw.z_order = _next_z
aw.style = style
aw.close_hover = 0
aw.min_hover = 0
aw.max_hover = 0
aw.is_maximized = 0
aw.orig_x = x
aw.orig_y = y
aw.orig_w = w
aw.orig_h = h
aw.pid = _current_app_pid
aw.cursor_type = 0
aw.app_cursor_type = 0
aw.fb_w = 0
aw.fb_h = 0
aw.old_fb = t.CVoid(0, t.CPtr)
aw.ping_pending = 0
aw.no_resp = 0
aw.no_resp_sec = 0
aw.minimized = 0
aw.resizable_w = 1
aw.resizable_h = 1
aw.resize_w = 0
aw.resize_h = 0
string.memset(c.Addr(aw.title), 0, 32)
string.memset(c.Addr(aw.orig_title), 0, 32)
for i in range(31):
ch: t.CChar = title[i]
if ch == 0: break
aw.title[i] = ch
aw.orig_title[i] = ch
aw.fb = mm.malloc(w * aw.content_h * 4)
aw.fb_w = w
aw.fb_h = aw.content_h
if aw.fb:
bg_col: t.CUInt32T = gfx.COLOR_RGB(25, 25, 35)
fb_size: t.CInt = w * aw.content_h
for fi in range(fb_size):
aw.fb[fi] = bg_col
_mark_rows_dirty(y, h)
_focused_win = idx
return idx
def draw_text(win_id: t.CInt, x: t.CInt, y: t.CInt, text: t.CConst | t.CChar | t.CPtr, color: t.CUInt32T) -> t.CInt:
if win_id < 0 or win_id >= _win_count: return -1
aw: AppWindow | t.CPtr = c.Addr(_wins[win_id])
if not aw.active: return -1
win_fb: UINT32PTR = aw.fb
if not win_fb: return -1
fbw: t.CInt = aw.fb_w
fbh: t.CInt = aw.fb_h
_draw_text(win_fb, fbw, fbh, x, y, text, color)
_mark_rows_dirty(aw.y + _get_title_height(aw.style) + 1 + y, 16)
return 0
def set_app_pid(pid: t.CInt):
global _current_app_pid
_current_app_pid = pid
def is_active(win_id: t.CInt) -> t.CInt:
if win_id < 0 or win_id >= _win_count: return 0
return _wins[win_id].active
def get_fb(win_id: t.CInt) -> UINT32PTR:
if win_id < 0 or win_id >= _win_count: return t.CVoid(0, t.CPtr)
aw: AppWindow | t.CPtr = c.Addr(_wins[win_id])
if not aw.active: return t.CVoid(0, t.CPtr)
if aw.old_fb:
mm.free(aw.old_fb)
aw.old_fb = t.CVoid(0, t.CPtr)
if aw.resize_w != 0 and (aw.fb_w != aw.resize_w or aw.fb_h != aw.resize_h):
target_w: t.CInt = aw.resize_w
target_h: t.CInt = aw.resize_h
if target_w <= 0 or target_h <= 0:
aw.resize_w = 0
aw.resize_h = 0
return aw.fb
new_fb_sz_gf: t.CInt = target_w * target_h
new_fb_gf: UINT32PTR = mm.malloc(new_fb_sz_gf * 4)
if not new_fb_gf:
aw.resize_w = 0
aw.resize_h = 0
return aw.fb
old_fb_gf: UINT32PTR = aw.fb
old_fbw_gf: t.CInt = aw.fb_w
old_fbh_gf: t.CInt = aw.fb_h
aw.fb = new_fb_gf
aw.fb_w = target_w
aw.fb_h = target_h
aw.w = target_w
aw.content_h = target_h
aw.resize_w = 0
aw.resize_h = 0
bg_gf: t.CUInt32T = gfx.COLOR_RGB(30, 30, 50)
for fi_gf in range(new_fb_sz_gf):
aw.fb[fi_gf] = bg_gf
if old_fb_gf:
cph_gf: t.CInt = old_fbh_gf
if cph_gf > target_h: cph_gf = target_h
for ri_gf in range(cph_gf):
rb_gf: t.CInt = old_fbw_gf
if rb_gf > target_w: rb_gf = target_w
string.memcpy(t.CVoid(t.CUInt64T(aw.fb) + ri_gf * target_w * 4, t.CPtr), t.CVoid(t.CUInt64T(old_fb_gf) + ri_gf * old_fbw_gf * 4, t.CPtr), rb_gf * 4)
if aw.old_fb:
mm.free(aw.old_fb)
aw.old_fb = old_fb_gf
return aw.fb
def get_size(win_id: t.CInt) -> t.CInt:
if win_id < 0 or win_id >= _win_count: return 0
aw: AppWindow | t.CPtr = c.Addr(_wins[win_id])
if not aw.active: return 0
return (aw.fb_w << 16) | (aw.fb_h & 0xFFFF)
_NO_RESP_SUFFIX: t.CArray[t.CChar, 14] = " - (No Resp)"
def _update_no_resp_title(win_id: t.CInt, mark: t.CInt):
if win_id < 0 or win_id >= _win_count: return
aw: AppWindow | t.CPtr = c.Addr(_wins[win_id])
if not aw.active: return
string.memset(c.Addr(aw.title), 0, 32)
if mark:
j: t.CInt = 0
for ch in aw.orig_title:
if j >= 31: break
if ch == 0: break
aw.title[j] = ch
j += 1
for sc in _NO_RESP_SUFFIX:
if j >= 13: break
if sc == 0: break
if j < 31:
aw.title[j] = sc
j += 1
else:
j2: t.CInt = 0
for ch2 in aw.orig_title:
if j2 >= 31: break
if ch2 == 0: break
aw.title[j2] = ch2
j2 += 1
_mark_rows_dirty(aw.y, _get_title_height(aw.style) + 2)
def _heartbeat_check():
global _hb_last_sec
cur_sec: t.CUInt32T = timer.timer_get_seconds()
if cur_sec - _hb_last_sec < HB_INTERVAL_SEC: return
_hb_last_sec = cur_sec
for i in range(_win_count):
aw: AppWindow | t.CPtr = c.Addr(_wins[i])
if not aw.active:
continue
if aw.pid == 0:
continue
if aw.ping_pending:
if not aw.no_resp:
aw.no_resp = 1
aw.no_resp_sec = cur_sec
_update_no_resp_title(i, 1)
else:
elapsed: t.CUInt32T = cur_sec - aw.no_resp_sec
if elapsed >= HB_NO_RESP_SEC:
aw.active = 0
_mark_rows_dirty(aw.y, aw.h)
p_ref: process.Process | t.CPtr = process.ProcessManager.get_process(aw.pid)
if p_ref is not None:
p_ref.exit()
else:
aw.ping_pending = 1
evt: InputEvent
string.memset(c.Addr(evt), 0, INPUT_EVENT_SIZE)
evt.type = EVENT_TYPE_PING
evt.win_id = i
_push_event(evt)
def flush(win_id: t.CInt):
if win_id < 0 or win_id >= _win_count: return
aw: AppWindow | t.CPtr = c.Addr(_wins[win_id])
if not aw.active: return
if aw.old_fb:
mm.free(aw.old_fb)
aw.old_fb = t.CVoid(0, t.CPtr)
if aw.ping_pending:
aw.ping_pending = 0
if aw.no_resp:
aw.no_resp = 0
aw.no_resp_sec = 0
_update_no_resp_title(win_id, 0)
_mark_rows_dirty(aw.y + _get_title_height(aw.style) + 1, _eff_h(aw))
def set_title(win_id: t.CInt, title: t.CConst | t.CChar | t.CPtr) -> t.CInt:
if win_id < 0 or win_id >= _win_count: return -1
aw: AppWindow | t.CPtr = c.Addr(_wins[win_id])
if not aw.active: return -1
string.memset(c.Addr(aw.title), 0, 32)
string.memset(c.Addr(aw.orig_title), 0, 32)
for i in range(31):
ch: t.CChar = title[i]
if ch == 0: break
aw.title[i] = ch
aw.orig_title[i] = ch
_mark_rows_dirty(aw.y, _get_title_height(aw.style) + 2)
return 0
def set_geometry(win_id: t.CInt, x: t.CInt, y: t.CInt, w: t.CInt, h: t.CInt) -> t.CInt:
if win_id < 0 or win_id >= _win_count: return -1
aw: AppWindow | t.CPtr = c.Addr(_wins[win_id])
if not aw.active: return -1
old_y: t.CInt = aw.y
old_h: t.CInt = aw.h
aw.x = x
aw.y = y
aw.resize_w = w
aw.resize_h = h - _get_title_height(aw.style) - 2
aw.h = h
_mark_rows_dirty(old_y, old_h)
_mark_rows_dirty(y, h)
return 0
def set_resizable(win_id: t.CInt, rw: t.CInt, rh: t.CInt) -> t.CInt:
if win_id < 0 or win_id >= _win_count: return -1
aw: AppWindow | t.CPtr = c.Addr(_wins[win_id])
if not aw.active: return -1
aw.resizable_w = rw
aw.resizable_h = rh
return 0
def destroy_by_pid(pid: t.CInt):
global _focused_win, _last_hover_win
for i in range(_win_count):
if _wins[i].pid == pid and _wins[i].active:
_wins[i].active = 0
_mark_rows_dirty(_wins[i].y, _wins[i].h)
if _focused_win == i:
_focused_win = -1
if _last_hover_win == i:
_last_hover_win = -1
if _focused_win < 0:
for j in range(_win_count - 1, -1, -1):
if _wins[j].active and not _wins[j].minimized:
_focused_win = j
break