进行界面原型设计与实时预览)
SerenityOS GML Playground使用 GUI 标记语言GML进行界面原型设计与实时预览【免费下载链接】serenityThe Serenity Operating System 项目地址: https://gitcode.com/GitHub_Trending/se/serenityGML Playground 是 SerenityOS 内置的 GMLGUI Markup Language可视化编辑器它以「编辑即预览」的方式加速 Serenity 应用 GUI 的开发左侧编辑 GML 源码右侧实时渲染对应控件布局并支持语法高亮、自动补全、一键格式化等能力。读完本文你将掌握 GML Playground 的启动参数、界面结构、实时预览与格式化等核心用法并能结合仓库中的源码理解其底层实现从命令行解析到load_from_gml动态渲染的完整调用链。GML Playground 是什么GML Playground 是面向 Serenity 应用开发者的 GUI 标记语言GUI Markup Language, GML编辑器对应系统手册页 Applications/GMLPlayground。它的核心用途是使用 GML 文本描述 GUI 控件的组成与属性将所描述的控件自动渲染到实时预览窗口实现 GUI 的快速原型设计与迭代开发。该工具在系统中注册为应用GMLPlayground图标位于 app-gml-playground.png可从系统启动器直接打开。其构建入口位于 Userland/DevTools/GMLPlayground/main.cpp属于DevTools组件构建配置见 Userland/DevTools/GMLPlayground/CMakeLists.txt。启动方式与命令行参数手册页给出的命令格式如下$ GMLPlayground [file]参数说明file要加载的 GML 文件路径可省略。用法示例$ GMLPlayground /home/anon/example.gml命令行参数在 main.cpp 中通过Core::ArgsParser解析StringView path; Core::ArgsParser args_parser; args_parser.add_positional_argument(path, GML file to edit, file, Core::ArgsParser::Required::No); args_parser.parse(arguments);file是一个可选的位置参数Required::No。当不提供任何参数时程序不会直接退出而是新建一个未命名文档并预填一段 GML 模板作为起点见下文「启动即用的模板」当提供了路径时则通过FileSystemAccessClient申请只读打开该文件并载入编辑器。此外进程启动阶段还通过pledge/unveil声明了严格的沙箱权限main.cpppledge只允许stdio thread recvfd sendfd cpath rpath wpath unix等必要能力unveil仅开放/res读取资源、launch 与 filesystemaccess 两个 portal用于打开帮助页和文件访问这体现了 Serenity 应用最小权限的典型实践。启动即用的模板不带参数启动时编辑器会自动填入一段最小可用的 GML 模板main.cppGUI::Frame { layout: GUI::VerticalBoxLayout { } // Now add some widgets! }同时将光标定位到// Now add some widgets!注释之后set_cursor(4, 28)引导用户直接在模板基础上继续添加控件。窗口标题此时为Untitled[*] - GML Playground[*]表示文档有未保存修改参见 MainWidget.cpp。界面布局Playground 自身也是用 GML 写的GML Playground 的主窗口界面本身就是由一份 GML 文件 GMLPlaygroundWindow.gml 定义的并通过 CMake 的stringify_gml宏在构建期嵌入为 C 代码见 CMakeLists.txtGUI::Widget { layout: GUI::VerticalBoxLayout {} fill_with_background_color: true GUI::ToolbarContainer { GUI::Toolbar { name: toolbar } } GUI::HorizontalSplitter { layout: GUI::HorizontalBoxLayout {} name: splitter GUI::TextEditor { name: text_editor } GUI::Frame { name: preview_frame } } GUI::Statusbar { name: statusbar } }窗口整体呈「工具栏 水平分隔的编辑器/预览 状态栏」结构各控件通过name属性被MainWidget::try_create用find_descendant_of_type_named找到并绑定MainWidget.cpptoolbar工具栏承载打开、保存、剪切/复制/粘贴、撤销/重做、格式化等常用动作splitter水平分隔条左侧是编辑器右侧是预览 Frametext_editorGML 源码编辑器preview_frame内嵌预览区域statusbar状态栏用于显示菜单动作的提示文本status_tip。默认窗口尺寸为 800×600且支持记忆上次关闭时的窗口位置与大小restore_size_and_position/save_size_and_position_on_close见 main.cpp。这段窗口结构代码同时也是学习 GML 布局嵌套VerticalBoxLayoutHorizontalSplitterToolbarContainer的极佳范例。核心工作流编辑即实时预览GML Playground 最核心的能力是「实时预览」。编辑器文本的任何变化都会立刻驱动预览区域重新渲染相关实现位于 MainWidget.cppm_editor-on_change [main_widget main_widget.ptr()] { main_widget-m_preview-remove_all_children(); // FIXME: Parsing errors happen while the user is typing. What should we do about them? (void)main_widget-m_preview-load_from_gml(main_widget-m_editor-text(), [](StringView class_name) - ErrorOrNonnullRefPtrCore::EventReceiver { return UnregisteredWidget::try_create(class_name); }); };每次编辑触发on_change时清空预览区域的所有子控件remove_all_children用编辑器当前文本调用load_from_gml重新解析并构建控件树。未注册控件占位符如果 GML 中引用了尚未注册的控件类load_from_gml的兜底回调会构造一个UnregisteredWidgetMainWidget.cpp它以深红色背景 白色文字居中显示「类名not registered」明确提示开发者该控件类不可用。源码注释也指出输入过程中产生的解析错误目前会静默忽略FIXME因此预览只会反映「当前可解析的部分」。底层原理从文本到控件树load_from_gml是 LibGUI 提供的运行时加载接口其解析链路由 LibGUI/GML 目录 实现Lexer词法分析→Parser语法分析→ AST抽象语法树再依据 AST 实例化控件。Widget::load_from_gml的具体实现位于 LibGUI/Widget.cpp。GML 语法本身受 QML 影响详见 GML(5) 手册控件以类名开头、属性以key: value形式声明layout属性取值为另一个布局对象。值得注意的是手册 GML/Usage 指出正式应用中更推荐构建期compile_gml()生成 C 代码的方式而运行时load_from_gml仅用于 GMLPlayground 这类特殊场景——这正是 Playground 存在的重要意义之一它让 GML 可以脱离编译流程直接试跑。编辑器能力语法高亮、自动补全与智能编辑编辑器在 MainWidget.cpp 中被配置了完整的开发体验m_editor-set_syntax_highlighter(TRY(try_makeGUI::GML::SyntaxHighlighter())); m_editor-set_autocomplete_provider(TRY(try_makeGUI::GML::AutocompleteProvider())); m_editor-set_should_autocomplete_automatically(true); m_editor-set_automatic_indentation_enabled(true); m_editor-set_ruler_visible(true);语法高亮由 LibGUI/GML/SyntaxHighlighter.cpp 提供基于 GML 的Lexer分词结果着色自动补全由 LibGUI/GML/AutocompleteProvider.cpp 提供。它通过维护一个有限状态机Free、InClassName、AfterClassName、InIdentifier、AfterIdentifier跟踪光标处的 GML 上下文然后在类名位置遍历GUI::ObjectClassRegistration注册表模糊匹配所有继承自GUI::Widget或GUI::Layout的类补全为类名在属性名位置实例化当前控件类并枚举其可写属性is_readonly过滤补全为属性名:对layout属性特殊处理补全布局类对GUI::ScrollableContainerWidget的content_widget属性也有专门补全规则所有候选按名称排序后交给编辑器弹出补全列表。自动缩进与行号标尺ruler也默认开启。智能格式化Format GML编辑菜单与工具栏都提供了「Format GML」动作快捷键为CtrlShiftI点击图标为resource://icons/16x16/reformat.png。其实现MainWidget.cpp调用GUI::GML::format_gml见 LibGUI/GML/Formatter.h对全文重新格式化并通过replace_all_text_without_resetting_undo_stack原地替换不破坏撤销历史格式化失败时弹出错误对话框。该格式化能力与独立的命令行工具gml-format同源关于gml-format的完整用法见下文「与 gml-format 联动」。编辑引擎普通模式与 Vim 模拟编辑菜单提供可勾选的「Vim Emulation」动作快捷键 CtrlShiftAltVMainWidget.cpp勾选后编辑器切换为GUI::VimEditingEngine取消则回到GUI::RegularEditingEngine满足不同编辑习惯的开发者。预览模式Frame 内嵌与独立窗口GML Playground 支持两种预览呈现方式通过「View」菜单在互斥的复选动作间切换MainWidget.cpp模式说明Frame默认预览渲染在窗口右侧的preview_frame内宽度自动取分隔条的一半切换回该模式时会隐藏独立窗口Window预览渲染到一个独立的 400×300 弹出窗口Preview - GML Playground中便于在更大空间观察效果关闭该窗口会自动切回 Frame 模式两种模式共享同一份on_change渲染逻辑m_preview指向当前激活的预览容器切换后立即重渲染一次以同步内容。文件操作打开、保存与拖放「File」菜单与工具栏提供完整的文件生命周期管理MainWidget.cpp打开Open默认定位到/usr/src/serenity/Userland/Applications目录文件类型过滤器限定为GML Files (*.gml)或全部文件保存Save已命名文件直接写回未命名文件Untitled会先触发「另存为」另存为Save As默认文件名Untitled.gml通过FileSystemAccessClient保存最近文件列表自动维护最近打开的文件可一键重新打开未保存修改提醒关闭窗口或切换文件时若文档被修改会弹出「是否保存更改」对话框ask_about_unsaved_changes见 MainWidget.cpp。此外主窗口支持拖放打开MainWidget.cpp从文件管理器拖入一个 GML 文件即可加载一次只能打开一个文件拖入多个文件会提示错误。窗口标题会在文件加载后更新为文件名[*] - GML Playground[*]标记未保存状态与 Serenity 应用的编辑习惯一致。与 gml-format 联动手册「See also」中推荐的配套工具是 gml-format(1)——GML 的自动化格式化命令行工具$ gml-format [--inplace] [path...]选项-i, --inplace将格式化结果直接写回文件而不是输出到标准输出。示例$ gml-format -i /home/anon/example.gml在 GML Playground 内按 CtrlShiftI 即可完成同样的格式化而在脚本、CI 或批量处理场景下则可以使用gml-format -i对多个文件原地格式化。两者都基于LibGUI::GML::Formatter的同一套格式化逻辑保证编辑器内与命令行行为一致。GML 语法速览Playground 中的试写对象要在 Playground 中高效试写需要掌握 GML 的基本语法完整规范见 GML(5) 及其子页 GML Syntax。要点如下每个控件以开头后跟完整的 C 类名含命名空间如GUI::Button属性与子控件写在{ }内属性格式为key: value值通常是 JSON 值layout是特殊属性接受一个布局对象而非 JSON 值支持//单行注释属性常见类型包括int、bool、string、enum带引号的枚举字符串、rect、size、margins等详见语法手册的属性类型列表。例如在 Playground 模板中补全一个带两个按钮的 FrameGUI::Frame { min_height: 16 min_width: 128 layout: GUI::HorizontalBoxLayout { spacing: 5 } GUI::Button { text: Im the left button... } GUI::Button { text: ...and Im the right button! } }输入时语法高亮立即生效右侧预览同步渲染输入GUI::后自动补全会列出所有可用的控件类输入text:时则会列出该控件的可写属性——这正是 Playground「所见即所得」的完整闭环。小结GML Playground 以极低的上手成本打通了「写 GML → 看效果 → 调属性」的 GUI 原型迭代循环GMLPlayground [file]一条命令即可打开编辑与实时预览编辑器内置语法高亮、基于类注册表的自动补全、自动缩进、Vim 模拟与 CtrlShiftI 一键格式化预览支持 Frame 内嵌与独立窗口两种模式文件侧具备打开、保存、最近文件与拖放能力。它自身也演示了 GML 描述复杂界面工具栏、分隔条、状态栏的完整能力是学习与调试 GML 的必备工具。进一步阅读GML Playground 手册页gml-format 手册页GML(5) 手册总览GML 语法详解GML 在 C 应用中的使用GML Playground 源码GML 解析器与 ASTLibGUI/GML【免费下载链接】serenityThe Serenity Operating System 项目地址: https://gitcode.com/GitHub_Trending/se/serenity创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考