窗口创建与控制

窗口由 WindowOption 创建,创建成功后返回当前窗口的 WindowId

use flor::windows::WindowOption;
use flor_lys::label::label;

let window_id = WindowOption {
    title: "Hello Flor".to_string(),
    width: 800,
    height: 600,
    ..WindowOption::default()
}
.open(move |_window_id| {
    label("hello flor ~")
})?;

WindowOption 负责窗口的初始配置:

pub struct WindowOption {
    pub title: String,
    pub width: u32,
    pub height: u32,
    pub rem_px: f32,
    pub wait_v_sync: bool,
    pub show_fps: bool,
    pub continuous_rendering: bool,
    pub background_color: Color,
    pub tooltip_delay: Duration,
    pub borderless: bool,
    pub corner_radius: Option<f32>,
    pub parent_window: Option<WindowId>,
    pub position: Option<(i32, i32)>,
    #[cfg(feature = "resize-layout-coalescing")]
    pub resize_layout_policy: ResizeLayoutPolicy,
}

默认值如下:

字段默认值说明
title"Window"窗口标题。
width800初始窗口宽度。
height600初始窗口高度。
rem_px16.0当前窗口中 1rem 对应的像素值。
wait_v_synctrue渲染后端是否等待垂直同步。
show_fpsfalse是否显示 FPS。
continuous_renderingfalse是否持续请求重绘。
background_colorColor::rgb(255, 255, 255)窗口背景色。
tooltip_delayDuration::from_millis(500)tooltip 响应延迟。
borderlessfalse是否创建无边框窗口。当前 Windows 实现使用 WS_POPUP
corner_radiusNone可选窗口圆角半径。当前 Windows 实现通过窗口 region 裁切,并在尺寸变化时更新。
parent_windowNone可选父窗口。适合弹窗、子窗口等需要绑定到已有窗口的场景。
positionNone可选初始屏幕坐标;未设置时普通窗口使用平台默认位置,无边框或父窗口场景会按工作区居中。
resize_layout_policyResizeLayoutPolicy::Immediate仅启用 resize-layout-coalescing feature 时存在,用于控制 resize 时布局刷新策略。

continuous_rendering 只控制事件循环是否持续触发重绘,不会改变 Flor 的界面模型。普通 GUI 应用保持默认值即可;动画、实时预览、游戏循环这类需要每帧刷新的场景再设为 true

resize_layout_policy 只有启用 resize-layout-coalescing feature 后才会编译进 WindowOption。它目前有两个值:

参数说明
ResizeLayoutPolicy::Immediate默认策略。窗口 resize 消息到来时立即刷新布局,行为最直接,适合普通窗口。
ResizeLayoutPolicy::Coalesced合并 resize 过程中的布局刷新请求,避免用户拖动窗口边缘时高频 resize 消息反复触发完整布局。适合复杂页面、长列表或 resize 期间布局成本明显偏高的窗口。

如果没有遇到 resize 期间明显卡顿,保持默认 Immediate。完整 feature 说明见 Resize 布局策略

open 的视图函数

open 的签名是:

pub fn open<F, V>(self, view_fn: F) -> Result<WindowId, Error>
where
    F: Fn(WindowId) -> V + Send + Sync + 'static,
    V: IntoViewIter,

view_fn 是窗口根视图的构建函数。Flor 创建平台窗口、渲染器和窗口入口后,会把当前窗口的 WindowId 传给它,并把返回值转换为窗口根控件树。

use flor::platform::WindowId;
use flor::view::View;
use flor_lys::label::label;

fn build_view(_window_id: WindowId) -> impl View {
    label("hello flor ~")
}

WindowOption::default().open(build_view)?;

如果不需要使用窗口 ID,就写成 _window_id

open 参数里的 WindowId 怎么用

open(move |window_id| { ... }) 里的 window_id 不建议在根视图构建阶段直接调用窗口控制方法。此时窗口正在完成初始化,Flor 还在挂载根视图、注册渲染器、初始化焦点和刷新布局;在这个闭包里直接 set_sizeset_window_moderequest_redrawdestroy 等,容易让初始化顺序超出预期,产生额外重绘、布局状态不同步或平台层行为问题。

这个 WindowId 更适合被捕获到控件事件里,用来在后续事件中操控当前窗口:

use flor::platform::base::WindowApi;
use flor::view::builder::EventBuilder;
use flor_lys::button::button;

WindowOption::default().open(move |window_id| {
    button("关闭窗口").on_click(move || {
        let _ = window_id.destroy();
    })
})?;

如果只是设置初始标题、尺寸、背景色、刷新模式,应优先写在 WindowOption 字段里,而不是在 open 的闭包里再改。

无边框窗口与初始位置

borderlesscorner_radiusparent_windowposition 会在创建平台窗口时一次性传入底层 WindowCreateOptions。这类参数应该写在 WindowOption,不要在 open 闭包里补救式修改。

use flor::types::Color;
use flor::windows::WindowOption;
use flor_lys::label::label;

let popup = WindowOption {
    title: "Popup".to_string(),
    width: 320,
    height: 180,
    borderless: true,
    corner_radius: Some(12.0),
    position: Some((100, 100)),
    background_color: Color::TRANSPARENT,
    ..WindowOption::default()
}
.open(|_| label("无边框窗口"))?;

如果设置了 parent_window 但没有设置 position,当前 Windows 实现会优先把窗口放到父窗口所在显示器的工作区内,并尽量相对父窗口居中。

WindowId

WindowId 是平台窗口的句柄封装。当前 Windows 平台实现中它是:

#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub struct WindowId(pub isize);

它实现了 WindowApiWindowOperations。调用这些方法时需要导入 trait:

use flor::platform::base::{WindowApi, WindowOperations};
use flor::platform::WindowId;

创建与生命周期

API作用使用建议
WindowOption::open(view_fn)创建 Flor 窗口并挂载根视图。应用侧优先使用这个入口。
WindowApi::create_window(title, width, height)只创建平台窗口。框架内部使用;普通应用不要绕过 WindowOption::open
WindowApi::create_window_with_options(title, width, height, options)WindowCreateOptions 创建平台窗口。框架内部用于承接 WindowOption 的无边框、圆角、父窗口和初始位置。
update_window()同步触发平台窗口更新。初始化流程内部会调用;应用侧通常不用手动调用。
destroy()销毁窗口。适合放在按钮点击、菜单命令等事件中调用。

显示与窗口模式

API作用
show()显示窗口。
hide()隐藏窗口。
set_window_mode(mode)设置窗口模式。
get_window_mode()读取当前窗口模式。

WindowMode 包含 NormalMinimizedMaximizedFullscreen。当前 Windows 实现里,Fullscreen 暂时按最大化处理。

位置与尺寸

API作用
get_left() / get_top()读取窗口左上角屏幕坐标。
set_left(left) / set_top(top)单独设置窗口横向或纵向位置。
set_position((x, y))同时设置窗口位置。
get_width() / get_height()读取整个窗口宽高。
set_width(width) / set_height(height)单独设置窗口宽度或高度。
set_size((width, height))同时设置窗口大小。
get_client_size()读取客户区大小。
get_client_rect()读取客户区在屏幕上的矩形。
get_window_rect()读取整个窗口在屏幕上的矩形。

位置使用 i32,允许多显示器环境里的负坐标;尺寸使用 u32

DPI、输入法与鼠标

API作用
get_scale_factor()读取 DPI 缩放因子;当前 Windows 实现按 dpi_x / 96.0 返回。
get_dpi()读取窗口 DPI。
set_ime_window_location(rect)设置 IME 候选窗口位置。
set_ime_open_state(is_open)打开或关闭输入法状态。
set_ime_allowed(allow)允许或禁用 IME。
set_cursor(cursor)设置当前光标。
drag_window()触发系统窗口拖动。
capture_mouse()捕获鼠标。
release_mouse()释放鼠标捕获。

重绘

API作用
request_redraw()异步请求窗口重绘。

普通控件状态变化会由 Flor 自动请求重绘。只有在你直接通过窗口或平台能力改了框架无法感知的外部状态时,才需要手动调用 request_redraw()

延迟窗口命令

WindowRequestApiWindowId 增加了一组 request_* 方法,把窗口操作放入 Flor 事件循环队列执行,而不是在当前调用点立即执行。完整方法清单、返回值和回调时机见 WindowRequestApi

它存在的主要原因是避免窗口操作在错误的调用栈里直接进入平台 API,造成重入、死锁或死循环。例如:在窗口消息处理、响应式更新、跨线程回调或事件循环正在刷新布局 / 绘制时,直接 set_sizeshowdestroydrag_window 可能再次触发窗口消息或唤醒逻辑,导致当前事件循环嵌套执行。request_* 会把这些操作延后到 Flor 事件循环的统一 flush 点,先结束当前派发,再按 FIFO 顺序执行窗口命令。

如果你的需求来自非事件循环线程,还需要看 跨线程窗口命令:这个 feature 会让命令入队后主动唤醒事件循环,避免请求已经入队但事件循环还在等待。

use flor::windows::WindowRequestApi;

window_id.request_set_size((1024, 768));
window_id.request_show().result(|result| {
    if let Err(err) = result {
        eprintln!("show failed: {err}");
    }
});

这里的重点是使用 request_* 表达“稍后由事件循环安全执行”。如果你需要逐项确认哪些窗口操作支持延迟执行,或需要处理 result(...) 回调,直接查 WindowRequestApi

锚定窗口

flor::windows 公开了锚定窗口辅助函数,用于让一个弹窗持续跟随父窗口里的某个 ViewId

API作用
register_anchored_window(parent_window_id, anchor_view_id, window_id, size, initial_position)注册弹窗与锚点控件的关系。
unregister_anchored_window(window_id)移除某个弹窗的锚定关系,不会销毁窗口。
anchored_window_position(parent_window_id, anchor_view_id, size)根据锚点控件当前窗口坐标计算弹窗左上角位置。

布局刷新后,Flor 会更新当前父窗口下已注册的锚定窗口。默认放置策略优先在锚点下方显示,空间不足时改放上方,并尽量把横向位置钳制在父窗口客户区内。锚点已经不属于父窗口、无法读取位置或完全离开客户区时,已注册弹窗会被请求销毁。