microsoft-webui-desktop 3.0.3

Configurable native desktop SDK for WebUI applications
Documentation
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

#![allow(clippy::disallowed_methods)]

use super::*;
use crate::windows::create::FrameWindow;
use windows::Win32::UI::HiDpi::GetDpiForWindow;

#[test]
fn prepared_overlay_geometry_matches_native_caption_client_after_attachment() {
    let _bootstrap_lock = super::BOOTSTRAP_TEST_LOCK.lock().unwrap();
    let _com = crate::windows::initialize_com().unwrap();
    let runtime = Runtime::initialize().unwrap();
    for titlebar in [
        TitlebarStyle::Overlay { height: 48 },
        TitlebarStyle::HiddenInset,
    ] {
        for caption_button_size in [CaptionButtonSize::Standard, CaptionButtonSize::Tall] {
            for (resizable, maximized) in [(true, false), (false, false), (true, true)] {
                let options = WindowOptions {
                    titlebar: titlebar.clone(),
                    caption_button_size,
                    resizable,
                    maximized,
                    center: false,
                    ..WindowOptions::default()
                };
                let frame = FrameWindow::new(&options, None).unwrap();
                crate::windows::nonclient::prepare_overlay_startup(frame.hwnd).unwrap();
                frame.show().unwrap();
                let mut before = RECT::default();
                // SAFETY: The fixture owns the window and writable RECT storage.
                unsafe { WindowsAndMessaging::GetClientRect(frame.hwnd, &mut before).unwrap() };
                let sdk = WindowFrame::attach(&runtime, frame.hwnd, &options).unwrap();
                crate::windows::nonclient::finish_overlay_startup(frame.hwnd).unwrap();
                let mut after = RECT::default();
                // SAFETY: The same live window and writable output rectangle.
                unsafe { WindowsAndMessaging::GetClientRect(frame.hwnd, &mut after).unwrap() };
                assert_eq!(
                    before, after,
                    "{titlebar:?}, {caption_button_size:?}, {resizable}, {maximized}"
                );
                assert!(sdk
                    .overlay
                    .as_ref()
                    .unwrap()
                    .titlebar
                    .ExtendsContentIntoTitleBar()
                    .unwrap());
            }
        }
    }
}

#[test]
fn caption_button_size_selects_supported_native_sizes() {
    assert_eq!(
        height_option(CaptionButtonSize::Standard),
        TitleBarHeightOption::Standard
    );
    assert_eq!(
        height_option(CaptionButtonSize::Tall),
        TitleBarHeightOption::Tall
    );
}

#[test]
fn native_sdk_attaches_hidden_tall_caption_and_restores_fullscreen() {
    let _bootstrap_lock = super::BOOTSTRAP_TEST_LOCK.lock().unwrap();
    let _com = crate::windows::initialize_com().unwrap();
    let runtime = Runtime::initialize().unwrap();
    let options = WindowOptions {
        titlebar: TitlebarStyle::Overlay { height: 48 },
        caption_button_size: CaptionButtonSize::Tall,
        center: false,
        width: 800,
        height: 600,
        ..WindowOptions::default()
    };
    let frame = FrameWindow::new(&options, None).unwrap();
    let sdk = WindowFrame::attach(&runtime, frame.hwnd, &options).unwrap();
    let caption = sdk.overlay.as_ref().unwrap();
    assert!(caption.titlebar.ExtendsContentIntoTitleBar().unwrap());
    assert_eq!(
        caption.titlebar.PreferredHeightOption().unwrap(),
        TitleBarHeightOption::Tall
    );
    // SAFETY: The test owns this live window and only reads its state.
    unsafe {
        assert!(!WindowsAndMessaging::IsWindowVisible(frame.hwnd).as_bool());
        let dpi = GetDpiForWindow(frame.hwnd);
        assert_eq!(
            caption.titlebar.Height().unwrap(),
            i32::try_from(48 * dpi / 96).unwrap()
        );
    }

    let size = sdk.window.Size().unwrap();
    sdk.set_fullscreen(true).unwrap();
    // SAFETY: Changing the presenter must not reveal an uninitialized window.
    assert!(!unsafe { WindowsAndMessaging::IsWindowVisible(frame.hwnd) }.as_bool());
    assert_eq!(
        sdk.window.Presenter().unwrap().Kind().unwrap(),
        AppWindowPresenterKind::FullScreen
    );
    sdk.set_fullscreen(false).unwrap();
    assert_eq!(
        sdk.window.Presenter().unwrap().Kind().unwrap(),
        AppWindowPresenterKind::Overlapped
    );
    assert_eq!(sdk.window.Size().unwrap(), size);
}

#[test]
fn standard_buttons_keep_a_taller_application_bar() {
    let _bootstrap_lock = super::BOOTSTRAP_TEST_LOCK.lock().unwrap();
    let _com = crate::windows::initialize_com().unwrap();
    let runtime = Runtime::initialize().unwrap();
    let options = WindowOptions {
        titlebar: TitlebarStyle::Overlay { height: 48 },
        center: false,
        ..WindowOptions::default()
    };
    let frame = FrameWindow::new(&options, None).unwrap();
    let sdk = WindowFrame::attach(&runtime, frame.hwnd, &options).unwrap();
    let caption = sdk.overlay.as_ref().unwrap();
    assert_eq!(
        caption.titlebar.PreferredHeightOption().unwrap(),
        TitleBarHeightOption::Standard
    );
    // SAFETY: The test owns this live window and reads its native DPI.
    let dpi = unsafe { GetDpiForWindow(frame.hwnd) };
    assert_eq!(
        caption.titlebar.Height().unwrap(),
        i32::try_from(32 * dpi / 96).unwrap()
    );
    assert_eq!(caption.metrics.get().unwrap().minimum_height, 48);
}

#[test]
fn sdk_native_controls_preserve_frame_capabilities_and_input_safe_areas() {
    use windows::Win32::Foundation::{LPARAM, WPARAM};
    let _bootstrap_lock = super::BOOTSTRAP_TEST_LOCK.lock().unwrap();
    let _com = crate::windows::initialize_com().unwrap();
    let runtime = Runtime::initialize().unwrap();
    for titlebar in [
        TitlebarStyle::Overlay { height: 48 },
        TitlebarStyle::HiddenInset,
        TitlebarStyle::None,
        TitlebarStyle::Native,
    ] {
        for resizable in [true, false] {
            let options = WindowOptions {
                titlebar: titlebar.clone(),
                resizable,
                center: false,
                ..WindowOptions::default()
            };
            let frame = FrameWindow::new(&options, None).unwrap();
            let sdk = WindowFrame::attach(&runtime, frame.hwnd, &options).unwrap();
            let mut outer = RECT::default();
            // SAFETY: The fixture owns the HWND and all synchronous message storage.
            let hit = unsafe {
                WindowsAndMessaging::GetWindowRect(frame.hwnd, &mut outer).unwrap();
                let x = outer.left.cast_unsigned() & 0xffff;
                let y = outer.top.cast_unsigned() & 0xffff;
                WindowsAndMessaging::SendMessageW(
                    frame.hwnd,
                    WindowsAndMessaging::WM_NCHITTEST,
                    Some(WPARAM(0)),
                    Some(LPARAM(
                        isize::try_from(((y << 16) | x).cast_signed()).unwrap(),
                    )),
                )
                .0
            };
            let is_resize = (isize::try_from(WindowsAndMessaging::HTLEFT).unwrap()
                ..=isize::try_from(WindowsAndMessaging::HTBOTTOMRIGHT).unwrap())
                .contains(&hit);
            assert_eq!(is_resize, resizable, "{titlebar:?}: hit {hit}");
            if let Some(caption) = &sdk.overlay {
                let metrics = caption.metrics.get().unwrap();
                assert!(metrics.left + metrics.right > 0);
                let regions = caption
                    .input
                    .GetRegionRects(NonClientRegionKind::Passthrough)
                    .unwrap();
                assert_eq!(regions.len(), 1);
                assert_eq!(regions[0].X, metrics.left);
                assert_eq!(regions[0].Height, metrics.height);
                assert_eq!(
                    regions[0].X + regions[0].Width + metrics.right,
                    sdk.window.ClientSize().unwrap().Width
                );
            }
            let before = crate::windows::state::window_style_bits(
                frame.hwnd,
                WindowsAndMessaging::GWL_STYLE,
            );
            sdk.set_fullscreen(true).unwrap();
            sdk.refresh(frame.hwnd).unwrap();
            if let Some(caption) = &sdk.overlay {
                assert_eq!(caption.metrics.get().unwrap().height, 0);
            }
            sdk.set_fullscreen(false).unwrap();
            let after = crate::windows::state::window_style_bits(
                frame.hwnd,
                WindowsAndMessaging::GWL_STYLE,
            );
            let capabilities =
                WindowsAndMessaging::WS_THICKFRAME.0 | WindowsAndMessaging::WS_MAXIMIZEBOX.0;
            assert_eq!(before & capabilities, after & capabilities);
        }
    }
}

#[test]
fn overlay_keeps_all_eight_resize_hit_targets() {
    use windows::Win32::Foundation::{LPARAM, WPARAM};

    let _bootstrap_lock = super::BOOTSTRAP_TEST_LOCK.lock().unwrap();
    let _com = crate::windows::initialize_com().unwrap();
    let runtime = Runtime::initialize().unwrap();
    let options = WindowOptions {
        titlebar: TitlebarStyle::Overlay { height: 48 },
        center: false,
        width: 800,
        height: 600,
        ..WindowOptions::default()
    };
    let frame = FrameWindow::new(&options, None).unwrap();
    let _sdk = WindowFrame::attach(&runtime, frame.hwnd, &options).unwrap();
    let mut rect = RECT::default();
    // SAFETY: This test owns the live window and supplies writable RECT storage.
    unsafe { WindowsAndMessaging::GetWindowRect(frame.hwnd, &mut rect).unwrap() };
    let mid_x = (rect.left + rect.right) / 2;
    let mid_y = (rect.top + rect.bottom) / 2;
    let hit = |x: i32, y: i32| {
        let bits = ((y.cast_unsigned() & 0xffff) << 16) | (x.cast_unsigned() & 0xffff);
        // SAFETY: This test owns the live window; WM_NCHITTEST reads the packed screen point.
        unsafe {
            WindowsAndMessaging::SendMessageW(
                frame.hwnd,
                WindowsAndMessaging::WM_NCHITTEST,
                Some(WPARAM(0)),
                Some(LPARAM(isize::try_from(bits).unwrap())),
            )
            .0
        }
    };
    for ((x, y), expected) in [
        ((rect.left, rect.top), WindowsAndMessaging::HTTOPLEFT),
        ((mid_x, rect.top), WindowsAndMessaging::HTTOP),
        ((rect.right - 1, rect.top), WindowsAndMessaging::HTTOPRIGHT),
        ((rect.left, mid_y), WindowsAndMessaging::HTLEFT),
        ((rect.right - 1, mid_y), WindowsAndMessaging::HTRIGHT),
        (
            (rect.left, rect.bottom - 1),
            WindowsAndMessaging::HTBOTTOMLEFT,
        ),
        ((mid_x, rect.bottom - 1), WindowsAndMessaging::HTBOTTOM),
        (
            (rect.right - 1, rect.bottom - 1),
            WindowsAndMessaging::HTBOTTOMRIGHT,
        ),
    ] {
        assert_eq!(
            hit(x, y),
            isize::try_from(expected).unwrap(),
            "at ({x}, {y})"
        );
    }
}