From bddf6061e43af59d8a3235e4986be0d11c90f760 Mon Sep 17 00:00:00 2001 From: TermiNexus Date: Tue, 1 Sep 2026 21:23:18 +0800 Subject: [PATCH] =?UTF-8?q?feat(fltk):=20=E5=9B=9E=E8=B0=83=E6=94=AF?= =?UTF-8?q?=E6=8C=81=EF=BC=88set=5Fcallback/add=5Ftimeout/awake/=E8=8F=9C?= =?UTF-8?q?=E5=8D=95=E5=9B=9E=E8=B0=83=EF=BC=89+=20=E8=A1=A5=E9=BD=90?= =?UTF-8?q?=E6=8E=A7=E4=BB=B6=E7=B1=BB=EF=BC=88Toggle/Round=20=E6=8C=89?= =?UTF-8?q?=E9=92=AE=E3=80=81Multiline/Secret/File=20=E8=BE=93=E5=85=A5?= =?UTF-8?q?=E3=80=81Spinner/Clock/Nice=5FSlider=E3=80=81Pack/Tile/Flex?= =?UTF-8?q?=E3=80=81Menu=5FButton=E3=80=81Browser/Tree=E3=80=81Text=5FEdit?= =?UTF-8?q?or/Terminal=E3=80=81File=5FChooser/Help=5FDialog=E3=80=81Input?= =?UTF-8?q?=5FChoice/Color=5FChooser=E3=80=81PNG/JPEG/GIF=20=E5=9B=BE?= =?UTF-8?q?=E7=89=87=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- fltk/__init__.vp | 523 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 515 insertions(+), 8 deletions(-) diff --git a/fltk/__init__.vp b/fltk/__init__.vp index 9318b57..bf7212c 100644 --- a/fltk/__init__.vp +++ b/fltk/__init__.vp @@ -14,8 +14,14 @@ # * 本包只做 extern FFI(fltk._cfl* 模块)+ 少量 Pythonic 包装类; # 所有 FLTK 对象都是不透明指针,方法即 cfltk 的 Fl_<类>_<动作>。 # * 完整 C API 通过 `from fltk._cfl import *` 等直接可用。 -# * 回调(Fl_Callback 等)在 FFI 中按 bytes 不透明处理:Viper 端 -# 暂无法传函数指针,事件驱动请用 Fl.check()/Fl.event*() 轮询。 +# * 回调支持:Viper 模块级函数可直接作为 Fl_Callback 传给 FLTK—— +# TPV 把函数名自动 bitcast 成函数指针(如 CreateThread 传函数)。 +# 回调签名约定: def cb(w: bytes, data: bytes): ...(w=控件指针,data=用户数据) +# def on_click(w: bytes, data: bytes): +# Fl_Widget_set_label(w, "Clicked!") +# btn.set_callback(on_click, 0) +# Fl.add_timeout(1.0, on_tick, 0) +# * 事件驱动也可用 Fl.check()/Fl.event*() 轮询。 # ============================================================ import t, c @@ -169,8 +175,59 @@ class Fl: def scheme() -> int: return Fl_scheme() - # 注意: 回调(Fl_Callback 等)Viper 端无法提供函数指针,add_timeout 等 - # 需要真实回调的接口请直接使用 extern FFI 并配合 C 层钩子(_cfl.vp)。 + # ----- 定时器 / 线程唤醒 ----- + @staticmethod + def add_timeout(seconds: t.CDouble, cb: bytes, data: bytes = 0): + """注册一次性定时回调(seconds 秒后触发)。回调签名: def cb(w: bytes, data: bytes)""" + Fl_add_timeout(seconds, cb, data) + + @staticmethod + def repeat_timeout(seconds: t.CDouble, cb: bytes, data: bytes = 0): + """注册重复定时回调(每 seconds 秒触发,回调内需再调 repeat_timeout 保持)。""" + Fl_repeat_timeout(seconds, cb, data) + + @staticmethod + def awake(): + """唤醒另一线程的 Fl.run/Fl.wait 事件循环。""" + Fl_awake() + + @staticmethod + def awake_callback(handler: bytes, data: bytes = 0) -> int: + """注册跨线程唤醒回调(另一线程 Fl.awake() 后主循环调用 handler)。""" + return Fl_awake_callback(handler, data) + + # ----- 补全事件查询 ----- + @staticmethod + def event_original_key() -> int: + return Fl_event_original_key() + + @staticmethod + def event_clicks() -> int: + return Fl_event_clicks() + + @staticmethod + def event_x_root() -> int: + return Fl_event_x_root() + + @staticmethod + def event_y_root() -> int: + return Fl_event_y_root() + + @staticmethod + def event_dx() -> int: + return Fl_event_dx() + + @staticmethod + def event_dy() -> int: + return Fl_event_dy() + + @staticmethod + def event_is_click() -> int: + return Fl_event_is_click() + + @staticmethod + def event_key_down(k: int) -> int: + return Fl_event_key_down(k) # ============================================================ @@ -259,6 +316,41 @@ class Fl_Widget: def delete(self): Fl_Widget_delete(self._ptr) + # ----- 回调 ----- + def set_callback(self, cb: bytes, data: bytes = 0): + """注册控件回调(如点击/变更触发)。cb 传 Viper 模块级函数名,自动转函数指针。 + 回调签名: def cb(w: bytes, data: bytes): ...(w=控件指针,可转回本类指针用)。 + 触发时机由 set_when 控制(默认 Fl_WHEN_RELEASE)。""" + Fl_Widget_set_callback(self._ptr, cb, data) + + def callback(self) -> bytes: + return Fl_Widget_callback(self._ptr) + + def do_callback(self): + """手动触发本控件回调(不经过事件循环)。""" + Fl_Widget_do_callback(self._ptr) + + def set_when(self, when: int): + """设置回调触发条件(Fl_WHEN_* 常量见 _enums.vp)。""" + Fl_Widget_set_when(self._ptr, when) + + def when(self) -> int: + return Fl_Widget_when(self._ptr) + + def set_user_data(self, data: bytes): + """设置回调用户数据(回调的第二个参数 data)。""" + Fl_Widget_set_user_data(self._ptr, data) + + def user_data(self) -> bytes: + return Fl_Widget_user_data(self._ptr) + + def set_image(self, img: bytes): + """设置控件图标(传 Fl_PNG_Image 等图片对象的 _ptr)。""" + Fl_Widget_set_image(self._ptr, img) + + def image(self) -> bytes: + return Fl_Widget_image(self._ptr) + # ============================================================ # Fl_Window — 窗口包装 @@ -733,14 +825,14 @@ class Fl_Menu_Bar(Fl_Widget): """FLTK 菜单栏 add 走 cfltk 完整 6 参签名(name/shortcut/回调/数据/flags), - Viper 端回调按 bytes 无法传函数指针,回调/数据位固定传 t.CPtr(0); - 简单下拉建议直接用 add_choice。""" + cb 可传 Viper 模块级函数名(回调签名: def cb(w: bytes, data: bytes)); + 不传回调(默认)时菜单项无动作。简单下拉建议用 add_choice。""" def __init__(self, x: int, y: int, w: int, h: int, label: str = ""): self._ptr = Fl_Menu_Bar_new(x, y, w, h, label) - def add(self, path: str, shortcut: int, flags: int): - Fl_Menu_Bar_add(self._ptr, path, shortcut, t.CPtr(0), t.CPtr(0), flags) + def add(self, path: str, shortcut: int, flags: int, cb: bytes = 0): + Fl_Menu_Bar_add(self._ptr, path, shortcut, cb, t.CPtr(0), flags) def add_choice(self, text: str) -> int: return Fl_Menu_Bar_add_choice(self._ptr, text) @@ -816,3 +908,418 @@ class Fl_Native_File_Chooser: def fltk_init(): """初始化 FLTK(多线程环境需先 Fl_lock 再 init_all)。""" Fl_init_all() + + +# ============================================================ +# 补充按钮类 +# ============================================================ +@t.NoVTable +class Fl_Toggle_Button(Fl_Button): + """FLTK 切换按钮(按下保持)""" + + def __init__(self, x: int, y: int, w: int, h: int, label: str = ""): + self._ptr = Fl_Toggle_Button_new(x, y, w, h, label) + + +@t.NoVTable +class Fl_Round_Button(Fl_Button): + """FLTK 圆形按钮""" + + def __init__(self, x: int, y: int, w: int, h: int, label: str = ""): + self._ptr = Fl_Round_Button_new(x, y, w, h, label) + + +@t.NoVTable +class Fl_Radio_Round_Button(Fl_Button): + """FLTK 圆形单选按钮""" + + def __init__(self, x: int, y: int, w: int, h: int, label: str = ""): + self._ptr = Fl_Radio_Round_Button_new(x, y, w, h, label) + + +@t.NoVTable +class Fl_Radio_Light_Button(Fl_Button): + """FLTK 指示灯单选按钮""" + + def __init__(self, x: int, y: int, w: int, h: int, label: str = ""): + self._ptr = Fl_Radio_Light_Button_new(x, y, w, h, label) + + +@t.NoVTable +class Fl_Shortcut_Button(Fl_Button): + """FLTK 快捷键录入按钮""" + + def __init__(self, x: int, y: int, w: int, h: int, label: str = ""): + self._ptr = Fl_Shortcut_Button_new(x, y, w, h, label) + + +# ============================================================ +# 补充输入类 +# ============================================================ +@t.NoVTable +class Fl_Multiline_Input(Fl_Input): + """FLTK 多行文本输入框""" + + def __init__(self, x: int, y: int, w: int, h: int, label: str = ""): + self._ptr = Fl_Multiline_Input_new(x, y, w, h, label) + + +@t.NoVTable +class Fl_Secret_Input(Fl_Input): + """FLTK 密码输入框(显示为 *)""" + + def __init__(self, x: int, y: int, w: int, h: int, label: str = ""): + self._ptr = Fl_Secret_Input_new(x, y, w, h, label) + + +@t.NoVTable +class Fl_File_Input(Fl_Input): + """FLTK 文件路径输入框""" + + def __init__(self, x: int, y: int, w: int, h: int, label: str = ""): + self._ptr = Fl_File_Input_new(x, y, w, h, label) + + +@t.NoVTable +class Fl_Multiline_Output(Fl_Output): + """FLTK 多行文本输出框""" + + def __init__(self, x: int, y: int, w: int, h: int, label: str = ""): + self._ptr = Fl_Multiline_Output_new(x, y, w, h, label) + + +# ============================================================ +# Fl_Nice_Slider / Fl_Spinner / Fl_Clock +# ============================================================ +@t.NoVTable +class Fl_Nice_Slider(Fl_Slider): + """FLTK 美观滑块""" + + def __init__(self, x: int, y: int, w: int, h: int, label: str = ""): + self._ptr = Fl_Nice_Slider_new(x, y, w, h, label) + + +@t.NoVTable +class Fl_Spinner(Fl_Widget): + """FLTK 数字微调框""" + + def __init__(self, x: int, y: int, w: int, h: int, label: str = ""): + self._ptr = Fl_Spinner_new(x, y, w, h, label) + + def value(self) -> t.CDouble: + return Fl_Spinner_value(self._ptr) + + def set_value(self, v: t.CDouble): + Fl_Spinner_set_value(self._ptr, v) + + def minimum(self) -> t.CDouble: + return Fl_Spinner_minimum(self._ptr) + + def set_minimum(self, v: t.CDouble): + Fl_Spinner_set_minimum(self._ptr, v) + + def maximum(self) -> t.CDouble: + return Fl_Spinner_maximum(self._ptr) + + def set_maximum(self, v: t.CDouble): + Fl_Spinner_set_maximum(self._ptr, v) + + def step(self) -> t.CDouble: + return Fl_Spinner_step(self._ptr) + + def set_step(self, v: t.CDouble): + Fl_Spinner_set_step(self._ptr, v) + + +@t.NoVTable +class Fl_Clock(Fl_Widget): + """FLTK 时钟(模拟表盘)""" + + def __init__(self, x: int, y: int, w: int, h: int, label: str = ""): + self._ptr = Fl_Clock_new(x, y, w, h, label) + + +# ============================================================ +# 补充容器类 +# ============================================================ +@t.NoVTable +class Fl_Pack(Fl_Group): + """FLTK 自动布局容器(子控件按序排列)""" + + def __init__(self, x: int, y: int, w: int, h: int, label: str = ""): + self._ptr = Fl_Pack_new(x, y, w, h, label) + + +@t.NoVTable +class Fl_Tile(Fl_Group): + """FLTK 可拖动分隔容器""" + + def __init__(self, x: int, y: int, w: int, h: int, label: str = ""): + self._ptr = Fl_Tile_new(x, y, w, h, label) + + +@t.NoVTable +class Fl_Flex(Fl_Group): + """FLTK 弹性布局容器""" + + def __init__(self, x: int, y: int, w: int, h: int, label: str = ""): + self._ptr = Fl_Flex_new(x, y, w, h, label) + + +# ============================================================ +# Fl_Menu_Button — 弹出菜单按钮 +# ============================================================ +@t.NoVTable +class Fl_Menu_Button(Fl_Widget): + """FLTK 弹出菜单按钮(右键/长按弹出)""" + + def __init__(self, x: int, y: int, w: int, h: int, label: str = ""): + self._ptr = Fl_Menu_Button_new(x, y, w, h, label) + + def add_choice(self, text: str) -> int: + return Fl_Menu_Button_add_choice(self._ptr, text) + + +# ============================================================ +# Fl_Browser — 列表浏览 +# ============================================================ +@t.NoVTable +class Fl_Browser(Fl_Widget): + """FLTK 列表浏览框""" + + def __init__(self, x: int, y: int, w: int, h: int, label: str = ""): + self._ptr = Fl_Browser_new(x, y, w, h, label) + + def value(self) -> int: + """当前选中行号(1 起;0=未选中)""" + return Fl_Browser_value(self._ptr) + + def add(self, newtext: str): + Fl_Browser_add(self._ptr, newtext) + + def clear(self): + Fl_Browser_clear(self._ptr) + + def text(self, line: int) -> str: + return Fl_Browser_text(self._ptr, line) + + +# ============================================================ +# Fl_Tree — 树形控件 +# ============================================================ +@t.NoVTable +class Fl_Tree(Fl_Widget): + """FLTK 树形控件""" + + def __init__(self, x: int, y: int, w: int, h: int, label: str = ""): + self._ptr = Fl_Tree_new(x, y, w, h, label) + + def add(self, name: str) -> Fl_Tree_ItemPtr: + """添加节点('a/b/c' 路径自动建父子层级)""" + return Fl_Tree_add(self._ptr, name) + + def clear(self): + Fl_Tree_clear(self._ptr) + + +# ============================================================ +# Fl_Text_Editor / Fl_Terminal +# ============================================================ +@t.NoVTable +class Fl_Text_Editor(Fl_Text_Display): + """FLTK 文本编辑器""" + + def __init__(self, x: int, y: int, w: int, h: int, label: str = ""): + self._ptr = Fl_Text_Editor_new(x, y, w, h, label) + + def insert(self, text: str): + """在光标处插入文本""" + Fl_Text_Editor_insert(self._ptr, text) + + def overstrike(self, text: str): + """在光标处覆盖插入文本""" + Fl_Text_Editor_overstrike(self._ptr, text) + + +@t.NoVTable +class Fl_Terminal(Fl_Widget): + """FLTK 终端模拟控件(ANSI 转义支持)""" + + def __init__(self, x: int, y: int, w: int, h: int, label: str = ""): + self._ptr = Fl_Terminal_new(x, y, w, h, label) + + def append(self, txt: str): + Fl_Terminal_append(self._ptr, txt) + + +# ============================================================ +# Fl_File_Chooser — 文件选择对话框 +# ============================================================ +@t.NoVTable +class Fl_File_Chooser: + """FLTK 文件选择对话框(cfltk 封装,非原生) + + new(directory, pattern, type, title);type 用 Fl_File_Chooser_* 常量 + (见 _enums.vp:SINGLE/MULTI/DIRECTORY/CREATE/SAVE 等)。""" + + _ptr: Fl_File_ChooserPtr + + def __init__(self, d: str, p: str, t: int, title: str): + self._ptr = Fl_File_Chooser_new(d, p, t, title) + + def set_callback(self, cb: bytes, data: bytes = 0): + Fl_File_Chooser_set_callback(self._ptr, cb, data) + + def count(self) -> int: + return Fl_File_Chooser_count(self._ptr) + + def set_directory(self, d: str): + Fl_File_Chooser_set_directory(self._ptr, d) + + def directory(self) -> str: + return Fl_File_Chooser_directory(self._ptr) + + def set_filter(self, p: str): + Fl_File_Chooser_set_filter(self._ptr, p) + + def filter(self) -> str: + return Fl_File_Chooser_filter(self._ptr) + + def filter_value(self) -> int: + return Fl_File_Chooser_filter_value(self._ptr) + + def set_filter_value(self, f: int): + Fl_File_Chooser_set_filter_value(self._ptr, f) + + def set_type(self, t: int): + Fl_File_Chooser_set_type(self._ptr, t) + + def set_label(self, l: str): + Fl_File_Chooser_set_label(self._ptr, l) + + def set_ok_label(self, l: str): + Fl_File_Chooser_set_ok_label(self._ptr, l) + + def show(self): + Fl_File_Chooser_show(self._ptr) + + def shown(self) -> int: + return Fl_File_Chooser_shown(self._ptr) + + def hide(self): + Fl_File_Chooser_hide(self._ptr) + + def value(self, f: int) -> str: + """选中的第 f 个文件名(0 起;多选时用 count 遍历)""" + return Fl_File_Chooser_value(self._ptr, f) + + def delete(self): + Fl_File_Chooser_delete(self._ptr) + + +# ============================================================ +# Fl_Help_Dialog — HTML 帮助对话框 +# ============================================================ +@t.NoVTable +class Fl_Help_Dialog: + """FLTK HTML 帮助对话框""" + + _ptr: Fl_Help_DialogPtr + + def __init__(self): + self._ptr = Fl_Help_Dialog_new() + + def load(self, f: str) -> int: + """加载 HTML 文件""" + return Fl_Help_Dialog_load(self._ptr, f) + + def set_value(self, f: str): + """直接设置 HTML 内容""" + Fl_Help_Dialog_set_value(self._ptr, f) + + def value(self) -> str: + return Fl_Help_Dialog_value(self._ptr) + + def show(self): + Fl_Help_Dialog_show(self._ptr) + + def set_text_size(self, s: int): + Fl_Help_Dialog_set_text_size(self._ptr, s) + + def hide(self): + Fl_Help_Dialog_hide(self._ptr) + + def visible(self) -> int: + return Fl_Help_Dialog_visible(self._ptr) + + def delete(self): + Fl_Help_Dialog_delete(self._ptr) + + +# ============================================================ +# Fl_Input_Choice — 输入+下拉组合 +# ============================================================ +@t.NoVTable +class Fl_Input_Choice(Fl_Group): + """FLTK 文本输入 + 下拉选择组合控件""" + + def __init__(self, x: int, y: int, w: int, h: int, label: str = ""): + self._ptr = Fl_Input_Choice_new(x, y, w, h, label) + + def add(self, s: str): + Fl_Input_Choice_add(self._ptr, s) + + def value(self) -> str: + """当前输入框文本""" + return Fl_Input_Choice_value(self._ptr) + + +# ============================================================ +# Fl_Color_Chooser — 颜色选择 +# ============================================================ +@t.NoVTable +class Fl_Color_Chooser(Fl_Group): + """FLTK 颜色选择器""" + + def __init__(self, x: int, y: int, w: int, h: int, label: str = ""): + self._ptr = Fl_Color_Chooser_new(x, y, w, h, label) + + def set_rgb(self, r: t.CDouble, g: t.CDouble, b: t.CDouble) -> int: + """设置 RGB 值(0.0~1.0),返回 1=值变更""" + return Fl_Color_Chooser_set_rgb(self._ptr, r, g, b) + + def show(self): + Fl_Color_Chooser_show(self._ptr) + + +# ============================================================ +# 图片类 +# ============================================================ +@t.NoVTable +class Fl_PNG_Image: + """FLTK PNG 图片(从文件加载)""" + + _ptr: Fl_PNG_ImagePtr + + def __init__(self, filename: str): + self._ptr = Fl_PNG_Image_new(filename) + + +@t.NoVTable +class Fl_JPEG_Image: + """FLTK JPEG 图片""" + + _ptr: Fl_JPEG_ImagePtr + + def __init__(self, filename: str): + self._ptr = Fl_JPEG_Image_new(filename) + + +@t.NoVTable +class Fl_GIF_Image: + """FLTK GIF 图片""" + + _ptr: Fl_GIF_ImagePtr + + def __init__(self, filename: str): + self._ptr = Fl_GIF_Image_new(filename)