Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 


Quelle  mod.rs   Sprache: unbekannt

 
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

//! A UI using the windows API.
//!
//! This UI contains some edge cases that aren't implemented, for instance:
//! * there are a few cases where specific hierarchies are handled differently (e.g. a Button
//!   containing Label, Scroll behavior, etc).
//! * not all controls handle all Property variants (e.g. Checkbox doesn't handle ReadOnly, TextBox
//!   doesn't handle Binding, etc).
//!
//! The error handling is also a _little_ fast-and-loose, as many functions return an error value
//! that is acceptable to following logic (though it still would be a good idea to improve this).
//!
//! The rendering treats VBox, HBox, and Scroll as strictly layout-only: they do not create any
//! associated windows, and the layout logic handles their behavior.

// Our windows-targets doesn't link uxtheme correctly for GetThemeSysFont/GetThemeSysColor.
// This was working in windows-sys 0.48.
#[link(name = "uxtheme", kind = "static")]
extern "C" {}

use super::model::{self, Application, Element, ElementStyle, TypedElement};
use crate::data::{Property, Synchronized};
use dpi::Dpi;
use font::Fonts;
use once_cell::sync::Lazy;
use quit_token::QuitToken;
use std::cell::RefCell;
use std::collections::HashMap;
use std::pin::Pin;
use std::rc::Rc;
use widestring::WideString;
use window::{CustomWindowClass, Window, WindowBuilder};
use windows_sys::Win32::{
    Foundation::{HINSTANCE, HWND, LPARAM, LRESULT, RECT, WPARAM},
    Graphics::Gdi,
    System::{LibraryLoader::GetModuleHandleW, SystemServices, Threading::GetCurrentThreadId},
    UI::{Controls, Input::KeyboardAndMouse, Shell, WindowsAndMessaging as win},
};

macro_rules! success {
    ( nonzero $e:expr ) => {{
        let value = $e;
        assert_ne!(value, 0);
        value
    }};
    ( lasterror $e:expr ) => {{
        unsafe { windows_sys::Win32::Foundation::SetLastError(0) };
        let value = $e;
        assert!(value != 0 || windows_sys::Win32::Foundation::GetLastError() == 0);
        value
    }};
    ( hresult $e:expr ) => {
        assert_eq!($e, windows_sys::Win32::Foundation::S_OK);
    };
    ( pointer $e:expr ) => {{
        let ptr = $e;
        assert_ne!(ptr, 0);
        ptr
    }};
}

mod dpi;
mod font;
mod gdi;
mod layout;
mod quit_token;
mod twoway;
mod widestring;
mod window;

/// A Windows API UI implementation.
pub struct UI {
    thread_id: u32,
}

/// Custom user messages.
#[repr(u32)]
enum UserMessage {
    Invoke = win::WM_USER,
}

fn get_invoke(msg: &win::MSG) -> Option<Box<model::InvokeFn>> {
    if msg.message == UserMessage::Invoke as u32 {
        Some(unsafe { Box::from_raw(msg.lParam as *mut model::InvokeFn) })
    } else {
        None
    }
}

impl UI {
    pub fn run_loop(&self, app: Application) {
        // Initialize common controls.
        {
            let icc = Controls::INITCOMMONCONTROLSEX {
                dwSize: std::mem::size_of::<Controls::INITCOMMONCONTROLSEX>() as _,
                // Buttons, edit controls, and static controls are all included in 'standard'.
                dwICC: Controls::ICC_STANDARD_CLASSES | Controls::ICC_PROGRESS_CLASS,
            };
            success!(nonzero unsafe { Controls::InitCommonControlsEx(&icc) });
        }

        // Enable font smoothing (per
        // https://learn.microsoft.com/en-us/windows/win32/gdi/cleartype-antialiasing ).
        unsafe {
            // We don't check for failure on these, they are best-effort.
            win::SystemParametersInfoW(
                win::SPI_SETFONTSMOOTHING,
                1,
                std::ptr::null_mut(),
                win::SPIF_UPDATEINIFILE | win::SPIF_SENDCHANGE,
            );
            win::SystemParametersInfoW(
                win::SPI_SETFONTSMOOTHINGTYPE,
                0,
                win::FE_FONTSMOOTHINGCLEARTYPE as _,
                win::SPIF_UPDATEINIFILE | win::SPIF_SENDCHANGE,
            );
        }

        // Enable correct layout direction.
        if unsafe { win::SetProcessDefaultLayout(if app.rtl { Gdi::LAYOUT_RTL } else { 0 }) } == 0 {
            log::warn!("failed to set process layout direction");
        }

        let module: HINSTANCE = unsafe { GetModuleHandleW(std::ptr::null()) };

        // Register custom classes.
        AppWindow::register(module).expect("failed to register AppWindow window class");

        {
            // The quit token is cloned for each top-level window and dropped at the end of this
            // scope.
            let quit_token = QuitToken::new();

            for window in app.windows {
                let name = WideString::new(window.element_type.title.as_str());
                let w = top_level_window(
                    module,
                    AppWindow::new(
                        WindowRenderer::new(module, window.element_type, &window.style),
                        Some(quit_token.clone()),
                    ),
                    &name,
                    &window.style,
                );

                unsafe { win::ShowWindow(w.handle, win::SW_NORMAL) };
                unsafe { Gdi::UpdateWindow(w.handle) };
            }
        }

        // Run the event loop.
        let mut msg = unsafe { std::mem::zeroed::<win::MSG>() };
        while unsafe { win::GetMessageW(&mut msg, 0, 0, 0) } > 0 {
            if let Some(f) = get_invoke(&msg) {
                f();
                continue;
            }

            unsafe {
                // IsDialogMessageW is necessary to handle niceties like tab navigation
                if win::IsDialogMessageW(win::GetAncestor(msg.hwnd, win::GA_ROOT), &mut msg) == 0 {
                    win::TranslateMessage(&msg);
                    win::DispatchMessageW(&msg);
                }
            }
        }

        // Flush queue to properly drop late invokes (this is a very unlikely case)
        while unsafe { win::PeekMessageW(&mut msg, 0, 0, 0, win::PM_REMOVE) } > 0 {
            if let Some(f) = get_invoke(&msg) {
                drop(f);
            }
        }
    }

    pub fn invoke(&self, f: model::InvokeFn) {
        let ptr: *mut model::InvokeFn = Box::into_raw(Box::new(f));
        if unsafe {
            win::PostThreadMessageW(self.thread_id, UserMessage::Invoke as u32, 0, ptr as _)
        } == 0
        {
            let _ = unsafe { Box::from_raw(ptr) };
            log::warn!("failed to invoke function on thread message queue");
        }
    }
}

impl Default for UI {
    fn default() -> Self {
        UI {
            thread_id: unsafe { GetCurrentThreadId() },
        }
    }
}

/// A reference to an Element.
#[derive(PartialEq, Eq, Hash, Clone, Copy)]
struct ElementRef(*const Element);

impl ElementRef {
    pub fn new(element: &Element) -> Self {
        ElementRef(element as *const Element)
    }

    /// # Safety
    /// You must ensure the reference is still valid.
    pub unsafe fn get(&self) -> &Element {
        &*self.0
    }
}

// Equivalent of win32 HIWORD macro
fn hiword(v: u32) -> u16 {
    (v >> 16) as u16
}

// Equivalent of win32 LOWORD macro
fn loword(v: u32) -> u16 {
    v as u16
}

// Equivalent of win32 MAKELONG macro
fn makelong(low: u16, high: u16) -> u32 {
    (high as u32) << 16 | low as u32
}

fn top_level_window<W: window::WindowClass + window::WindowData>(
    module: HINSTANCE,
    class: W,
    title: &WideString,
    style: &ElementStyle,
) -> Window<W> {
    class
        .builder(module)
        .name(title)
        .style(win::WS_OVERLAPPEDWINDOW)
        .pos(win::CW_USEDEFAULT, win::CW_USEDEFAULT)
        .size(
            style
                .horizontal_size_request
                .and_then(|i| i.try_into().ok())
                .unwrap_or(win::CW_USEDEFAULT),
            style
                .vertical_size_request
                .and_then(|i| i.try_into().ok())
                .unwrap_or(win::CW_USEDEFAULT),
        )
        .create()
}

window::basic_window_classes! {
    /// Static control (text, image, etc) class.
    struct Static => "STATIC";

    /// Button control class.
    struct Button => "BUTTON";

    /// Edit control class.
    struct Edit => "EDIT";

    /// Progress control class.
    struct Progress => "msctls_progress32";
}

/// A top-level application window.
///
/// This is used for the main window and modal windows.
struct AppWindow {
    renderer: WindowRenderer,
    _quit_token: Option<QuitToken>,
}

impl AppWindow {
    pub fn new(renderer: WindowRenderer, quit_token: Option<QuitToken>) -> Self {
        AppWindow {
            renderer,
            _quit_token: quit_token,
        }
    }
}

impl window::WindowClass for AppWindow {
    fn class_name() -> WideString {
        WideString::new("App Window")
    }
}

impl CustomWindowClass for AppWindow {
    fn icon() -> win::HICON {
        static ICON: Lazy<win::HICON> = Lazy::new(|| unsafe {
            // If CreateIconFromResource fails it returns NULL, which is fine (a default icon will be
            // used).
            win::CreateIconFromResource(
                // We take advantage of the fact that since Windows Vista, an RT_ICON resource entry
                // can simply be a PNG image.
                super::icon::PNG_DATA.as_ptr(),
                super::icon::PNG_DATA.len() as u32,
                true.into(),
                // The 0x00030000 constant isn't available anywhere; the docs basically say to just
                // pass it...
                0x00030000,
            )
        });

        *ICON
    }

    fn message(
        data: &RefCell<Self>,
        hwnd: HWND,
        umsg: u32,
        wparam: WPARAM,
        lparam: LPARAM,
    ) -> Option<LRESULT> {
        let me = data.borrow();
        let model = me.renderer.model();
        match umsg {
            win::WM_CREATE => {
                me.renderer.set_dpi(Dpi::for_window(hwnd));

                if let Some(close) = &model.close {
                    close.subscribe(move |&()| unsafe {
                        win::SendMessageW(hwnd, win::WM_CLOSE, 0, 0);
                    });
                }

                let mut renderer = me.renderer.child_renderer(hwnd);
                if let Some(child) = &model.content {
                    renderer.render_child(child);
                }

                drop(model);
                let children = std::mem::take(&mut me.renderer.model_mut().children);
                for child in children {
                    renderer.render_window(child);
                }

                // Set the window size to the initial (DPI-aware) min width/height. We don't have
                // the DPI information until we create the window, so we can't set the initial
                // window size correctly at that time.
                unsafe {
                    win::SetWindowPos(
                        hwnd,
                        win::HWND_TOP,
                        0,
                        0,
                        me.renderer.min_width().try_into().unwrap(),
                        me.renderer.min_height().try_into().unwrap(),
                        win::SWP_NOZORDER | win::SWP_NOACTIVATE | win::SWP_NOMOVE,
                    )
                };
            }
            win::WM_CLOSE => {
                if model.modal {
                    // Modal windows should hide themselves rather than closing/destroying.
                    unsafe { win::ShowWindow(hwnd, win::SW_HIDE) };
                    return Some(0);
                }
            }
            win::WM_SHOWWINDOW => {
                if model.modal {
                    // Modal windows should disable/enable their parent as they are shown/hid,
                    // respectively.
                    let shown = wparam != 0;
                    unsafe {
                        KeyboardAndMouse::EnableWindow(
                            win::GetWindow(hwnd, win::GW_OWNER),
                            (!shown).into(),
                        )
                    };
                    return Some(0);
                }
            }
            win::WM_GETMINMAXINFO => {
                let minmaxinfo = unsafe { (lparam as *mut win::MINMAXINFO).as_mut().unwrap() };
                minmaxinfo.ptMinTrackSize.x = me.renderer.min_width().try_into().unwrap();
                minmaxinfo.ptMinTrackSize.y = me.renderer.min_height().try_into().unwrap();
                return Some(0);
            }
            win::WM_SIZE => {
                // When resized, recompute the layout.
                let width = loword(lparam as _) as u32;
                let height = hiword(lparam as _) as u32;

                if let Some(child) = &model.content {
                    me.renderer.layout(child, width, height);
                    unsafe { Gdi::UpdateWindow(hwnd) };
                }
                return Some(0);
            }
            win::WM_DPICHANGED => {
                // When DPI changes, recompute the layout and move the window.
                let rect: &RECT = unsafe { (lparam as *const RECT).as_ref() }
                    .expect("null RECT pointer in WM_DPICHANGED");
                let dpi = loword(wparam as _) as u32;

                let width = rect.right - rect.left;
                let height = rect.bottom - rect.top;

                me.renderer.set_dpi(Dpi::new(dpi));
                // This wil send WM_SIZE, which will take care of the new layout.
                unsafe {
                    win::SetWindowPos(
                        hwnd,
                        win::HWND_TOP,
                        rect.left,
                        rect.top,
                        width,
                        height,
                        win::SWP_NOZORDER | win::SWP_NOACTIVATE,
                    )
                };
                return Some(0);
            }
            win::WM_GETFONT => return Some(me.renderer.font()),
            win::WM_COMMAND => {
                let child = lparam as HWND;
                let windows = me.renderer.windows.borrow();
                if let Some(&element) = windows.reverse().get(&child) {
                    // # Safety
                    // The ElementRefs all pertain to the model stored in the renderer.
                    let element = unsafe { element.get() };
                    // Handle button presses.
                    use model::ElementType::*;
                    match &element.element_type {
                        Button(model::Button { click, .. }) => {
                            let code = hiword(wparam as _) as u32;
                            if code == win::BN_CLICKED {
                                click.fire(&());
                                return Some(0);
                            }
                        }
                        Checkbox(model::Checkbox { checked, .. }) => {
                            let code = hiword(wparam as _) as u32;
                            if code == win::BN_CLICKED {
                                let check_state =
                                    unsafe { win::SendMessageW(child, win::BM_GETCHECK, 0, 0) };
                                if let Property::Binding(s) = checked {
                                    *s.borrow_mut() = check_state == Controls::BST_CHECKED as isize;
                                }
                                return Some(0);
                            }
                        }
                        _ => (),
                    }
                }
            }
            _ => (),
        }
        None
    }
}

/// State used while creating and updating windows.
struct WindowRenderer {
    // We wrap with an Rc to get weak references in property callbacks (like that of
    // `ElementStyle::visible`).
    inner: Rc<WindowRendererInner>,
}

impl std::ops::Deref for WindowRenderer {
    type Target = WindowRendererInner;

    fn deref(&self) -> &Self::Target {
        &self.inner
    }
}

struct WindowRendererInner {
    pub module: HINSTANCE,
    /// The model is pinned and boxed to ensure that references in `windows` remain valid.
    ///
    /// We need to keep the model around so we can correctly perform layout as the window size
    /// changes. Unfortunately the win32 API doesn't have any nice ways to automatically perform
    /// layout.
    pub model: RefCell<Pin<Box<model::Window>>>,
    min_size: (u32, u32),
    /// Mapping between model elements and windows.
    ///
    /// Element references pertain to elements in `model`.
    pub windows: RefCell<twoway::TwoWay<ElementRef, HWND>>,
    pub dpi: Synchronized<Dpi>,
    pub fonts: Synchronized<Fonts>,
}

impl WindowRenderer {
    pub fn new(module: HINSTANCE, model: model::Window, style: &model::ElementStyle) -> Self {
        let dpi: Synchronized<Dpi> = Default::default();
        let scale_factor = font::ScaleFactor::from_registry();
        let fonts = dpi.mapped(move |dpi| Fonts::new(*dpi, scale_factor));
        WindowRenderer {
            inner: Rc::new(WindowRendererInner {
                module,
                model: RefCell::new(Box::pin(model)),
                min_size: (
                    style.horizontal_size_request.unwrap_or(0),
                    style.vertical_size_request.unwrap_or(0),
                ),
                windows: Default::default(),
                dpi,
                fonts,
            }),
        }
    }

    pub fn child_renderer(&self, window: HWND) -> WindowChildRenderer {
        WindowChildRenderer {
            renderer: &self.inner,
            window,
            child_id: 0,
            scroll: false,
        }
    }

    pub fn set_dpi(&self, dpi: Dpi) {
        *self.dpi.borrow_mut() = dpi;
    }

    pub fn layout(&self, element: &Element, max_width: u32, max_height: u32) {
        layout::Layout::new(
            self.inner.windows.borrow().forward(),
            *self.inner.dpi.borrow(),
        )
        .layout(element, max_width, max_height);
    }

    pub fn model(&self) -> std::cell::Ref<'_, model::Window> {
        std::cell::Ref::map(self.inner.model.borrow(), |b| &**b)
    }

    pub fn model_mut(&self) -> std::cell::RefMut<'_, model::Window> {
        std::cell::RefMut::map(self.inner.model.borrow_mut(), |b| &mut **b)
    }

    pub fn font(&self) -> Gdi::HFONT {
        *self.inner.fonts.borrow().normal
    }

    pub fn min_width(&self) -> u32 {
        self.inner.dpi.borrow().scale(self.min_size.0)
    }

    pub fn min_height(&self) -> u32 {
        self.inner.dpi.borrow().scale(self.min_size.1)
    }
}

struct WindowChildRenderer<'a> {
    renderer: &'a Rc<WindowRendererInner>,
    window: HWND,
    child_id: i32,
    scroll: bool,
}

impl<'a> WindowChildRenderer<'a> {
    fn add_child<W: window::WindowClass>(&mut self, class: W) -> WindowBuilder<W> {
        let builder = class
            .builder(self.renderer.module)
            .style(win::WS_CHILD | win::WS_VISIBLE)
            .parent(self.window)
            .child_id(self.child_id);
        self.child_id += 1;
        builder
    }

    fn add_window<W: window::WindowClass>(&mut self, class: W) -> WindowBuilder<W> {
        class
            .builder(self.renderer.module)
            .style(win::WS_OVERLAPPEDWINDOW)
            .pos(win::CW_USEDEFAULT, win::CW_USEDEFAULT)
            .parent(self.window)
    }

    fn render_window(&mut self, model: TypedElement<model::Window>) -> Window {
        let name = WideString::new(model.element_type.title.as_str());
        let style = model.style;
        let w = self
            .add_window(AppWindow::new(
                WindowRenderer::new(self.renderer.module, model.element_type, &style),
                None,
            ))
            .size(
                style
                    .horizontal_size_request
                    .and_then(|i| i.try_into().ok())
                    .unwrap_or(win::CW_USEDEFAULT),
                style
                    .vertical_size_request
                    .and_then(|i| i.try_into().ok())
                    .unwrap_or(win::CW_USEDEFAULT),
            )
            .name(&name)
            .create();

        enabled_property(&style.enabled, w.handle);

        let hwnd = w.handle;
        let set_visible = move |visible| unsafe {
            win::ShowWindow(hwnd, if visible { win::SW_SHOW } else { win::SW_HIDE });
        };

        match &style.visible {
            Property::Static(false) => set_visible(false),
            Property::Binding(s) => {
                s.on_change(move |v| set_visible(*v));
                if !*s.borrow() {
                    set_visible(false);
                }
            }
            _ => (),
        }

        w.generic()
    }

    fn render_child(&mut self, element: &Element) {
        if let Some(mut window) = self.render_element_type(&element.element_type) {
            window.set_default_font(&self.renderer.fonts, |fonts| &fonts.normal);

            // Store the element to handle mapping.
            self.renderer
                .windows
                .borrow_mut()
                .insert(ElementRef::new(element), window.handle);

            enabled_property(&element.style.enabled, window.handle);
        }

        // Handle visibility properties.
        match &element.style.visible {
            Property::Static(false) => {
                set_visibility(element, false, self.renderer.windows.borrow().forward())
            }
            Property::Binding(s) => {
                let weak_renderer = Rc::downgrade(self.renderer);
                let element_ref = ElementRef::new(element);
                let parent = self.window;
                s.on_change(move |visible| {
                    let Some(renderer) = weak_renderer.upgrade() else {
                        return;
                    };
                    // # Safety
                    // ElementRefs are valid as long as the renderer is (and we have a strong
                    // reference to it).
                    let element = unsafe { element_ref.get() };
                    set_visibility(element, *visible, renderer.windows.borrow().forward());
                    // Send WM_SIZE so that the parent recomputes the layout.
                    unsafe {
                        let mut rect = std::mem::zeroed::<RECT>();
                        win::GetClientRect(parent, &mut rect);
                        win::SendMessageW(
                            parent,
                            win::WM_SIZE,
                            0,
                            makelong(
                                (rect.right - rect.left) as u16,
                                (rect.bottom - rect.top) as u16,
                            ) as isize,
                        );
                    }
                });
                if !*s.borrow() {
                    set_visibility(element, false, self.renderer.windows.borrow().forward());
                }
            }
            _ => (),
        }
    }

    fn render_element_type(&mut self, element_type: &model::ElementType) -> Option<Window> {
        use model::ElementType as ET;
        match element_type {
            ET::Label(model::Label { text, bold }) => {
                let mut window = match text {
                    Property::Static(text) => {
                        let text = WideString::new(text.as_str());
                        self.add_child(Static)
                            .name(&text)
                            .add_style(SystemServices::SS_LEFT | SystemServices::SS_NOPREFIX)
                            .create()
                    }
                    Property::Binding(b) => {
                        let text = WideString::new(b.borrow().as_str());
                        let window = self
                            .add_child(Static)
                            .name(&text)
                            .add_style(SystemServices::SS_LEFT | SystemServices::SS_NOPREFIX)
                            .create();
                        let handle = window.handle;
                        b.on_change(move |text| {
                            let text = WideString::new(text.as_str());
                            unsafe { win::SetWindowTextW(handle, text.pcwstr()) };
                        });
                        window
                    }
                    Property::ReadOnly(_) => {
                        unimplemented!("ReadOnly property not supported for Label::text")
                    }
                };
                if *bold {
                    window.set_font(&self.renderer.fonts, |fonts| &fonts.bold);
                }
                Some(window.generic())
            }
            ET::TextBox(model::TextBox {
                placeholder,
                content,
                editable,
            }) => {
                let scroll = self.scroll;
                let window = self
                    .add_child(Edit)
                    .add_style(
                        (win::ES_LEFT
                            | win::ES_MULTILINE
                            | win::ES_WANTRETURN
                            | if *editable { 0 } else { win::ES_READONLY })
                            as u32
                            | win::WS_BORDER
                            | win::WS_TABSTOP
                            | if scroll { win::WS_VSCROLL } else { 0 },
                    )
                    .create();

                fn to_control_text(s: &str) -> String {
                    s.replace("\n", "\r\n")
                }

                fn from_control_text(s: &str) -> String {
                    s.replace("\r\n", "\n")
                }

                struct SubClassData {
                    placeholder: Option<WideString>,
                }

                // EM_SETCUEBANNER doesn't work with multiline edit controls (for no particular
                // reason?), so we have to draw it ourselves.
                unsafe extern "system" fn subclass_proc(
                    hwnd: HWND,
                    msg: u32,
                    wparam: WPARAM,
                    lparam: LPARAM,
                    _uidsubclass: usize,
                    dw_ref_data: usize,
                ) -> LRESULT {
                    let ret = Shell::DefSubclassProc(hwnd, msg, wparam, lparam);
                    if msg == win::WM_PAINT
                        && KeyboardAndMouse::GetFocus() != hwnd
                        && win::GetWindowTextLengthW(hwnd) == 0
                    {
                        let data = (dw_ref_data as *const SubClassData).as_ref().unwrap();
                        if let Some(placeholder) = &data.placeholder {
                            let mut rect = std::mem::zeroed::<RECT>();
                            win::GetClientRect(hwnd, &mut rect);
                            Gdi::InflateRect(&mut rect, -2, -2);

                            let dc = gdi::DC::new(hwnd).expect("failed to create GDI DC");
                            dc.with_object_selected(
                                win::SendMessageW(hwnd, win::WM_GETFONT, 0, 0) as _,
                                |hdc| {
                                    Gdi::SetTextColor(
                                        hdc,
                                        Controls::GetThemeSysColor(0, Gdi::COLOR_GRAYTEXT),
                                    );
                                    Gdi::SetBkMode(hdc, Gdi::TRANSPARENT as i32);
                                    success!(nonzero Gdi::DrawTextW(
                                        hdc,
                                        placeholder.pcwstr(),
                                        -1,
                                        &mut rect,
                                        Gdi::DT_LEFT | Gdi::DT_TOP | Gdi::DT_WORDBREAK,
                                    ));
                                },
                            )
                            .expect("failed to select font gdi object");
                        }
                    }

                    // Multiline edit controls capture the tab key. We want it to work as usual in
                    // the dialog (focusing the next input control).
                    if msg == win::WM_GETDLGCODE && wparam == KeyboardAndMouse::VK_TAB as usize {
                        return 0;
                    }

                    if msg == win::WM_DESTROY {
                        drop(unsafe { Box::from_raw(dw_ref_data as *mut SubClassData) });
                    }
                    return ret;
                }

                let subclassdata = Box::into_raw(Box::new(SubClassData {
                    placeholder: placeholder
                        .as_ref()
                        .map(|s| WideString::new(to_control_text(s))),
                }));

                unsafe {
                    Shell::SetWindowSubclass(
                        window.handle,
                        Some(subclass_proc),
                        0,
                        subclassdata as _,
                    );
                }

                // Set up content property.
                match content {
                    Property::ReadOnly(od) => {
                        let handle = window.handle;
                        od.register(move |target| {
                            // GetWindowText requires the buffer be large enough for the terminating
                            // null character (otherwise it truncates the string), but
                            // GetWindowTextLength returns the length without the null character, so we
                            // add 1.
                            let length = unsafe { win::GetWindowTextLengthW(handle) } + 1;
                            let mut buf = vec![0u16; length as usize];
                            unsafe { win::GetWindowTextW(handle, buf.as_mut_ptr(), length) };
                            buf.pop(); // null character; `String` doesn't want that
                            *target = from_control_text(&String::from_utf16_lossy(&buf));
                        });
                    }
                    Property::Static(s) => {
                        let text = WideString::new(to_control_text(s));
                        unsafe { win::SetWindowTextW(window.handle, text.pcwstr()) };
                    }
                    Property::Binding(b) => {
                        let handle = window.handle;
                        b.on_change(move |text| {
                            let text = WideString::new(to_control_text(text.as_str()));
                            unsafe { win::SetWindowTextW(handle, text.pcwstr()) };
                        });
                        let text = WideString::new(to_control_text(b.borrow().as_str()));
                        unsafe { win::SetWindowTextW(window.handle, text.pcwstr()) };
                    }
                }
                Some(window.generic())
            }
            ET::Scroll(model::Scroll { content }) => {
                if let Some(content) = content {
                    // Scrolling is implemented in a cooperative, non-universal way right now.
                    self.scroll = true;
                    self.render_child(content);
                    self.scroll = false;
                }
                None
            }
            ET::Button(model::Button { content, .. }) => {
                if let Some(ET::Label(model::Label {
                    text: Property::Static(text),
                    ..
                })) = content.as_ref().map(|e| &e.element_type)
                {
                    let text = WideString::new(text);

                    let window = self
                        .add_child(Button)
                        .add_style(win::BS_PUSHBUTTON as u32 | win::WS_TABSTOP)
                        .name(&text)
                        .create();
                    Some(window.generic())
                } else {
                    None
                }
            }
            ET::Checkbox(model::Checkbox { checked, label }) => {
                let label = label.as_ref().map(WideString::new);
                let mut builder = self
                    .add_child(Button)
                    .add_style((win::BS_AUTOCHECKBOX | win::BS_MULTILINE) as u32 | win::WS_TABSTOP);
                if let Some(label) = &label {
                    builder = builder.name(label);
                }
                let window = builder.create();

                fn set_check(handle: HWND, value: bool) {
                    unsafe {
                        win::SendMessageW(
                            handle,
                            win::BM_SETCHECK,
                            if value {
                                Controls::BST_CHECKED
                            } else {
                                Controls::BST_UNCHECKED
                            } as usize,
                            0,
                        );
                    }
                }

                match checked {
                    Property::Static(checked) => set_check(window.handle, *checked),
                    Property::Binding(s) => {
                        let handle = window.handle;
                        s.on_change(move |v| {
                            set_check(handle, *v);
                        });
                        set_check(window.handle, *s.borrow());
                    }
                    _ => unimplemented!("ReadOnly properties not supported for Checkbox"),
                }

                Some(window.generic())
            }
            ET::Progress(model::Progress { amount }) => {
                let window = self
                    .add_child(Progress)
                    .add_style(Controls::PBS_MARQUEE)
                    .create();

                fn set_amount(handle: HWND, value: Option<f32>) {
                    match value {
                        None => unsafe {
                            win::SendMessageW(handle, Controls::PBM_SETMARQUEE, 1, 0);
                        },
                        Some(v) => unsafe {
                            win::SendMessageW(handle, Controls::PBM_SETMARQUEE, 0, 0);
                            win::SendMessageW(
                                handle,
                                Controls::PBM_SETPOS,
                                (v.clamp(0f32, 1f32) * 100f32) as usize,
                                0,
                            );
                        },
                    }
                }

                match amount {
                    Property::Static(v) => set_amount(window.handle, *v),
                    Property::Binding(s) => {
                        let handle = window.handle;
                        s.on_change(move |v| set_amount(handle, *v));
                        set_amount(window.handle, *s.borrow());
                    }
                    _ => unimplemented!("ReadOnly properties not supported for Progress"),
                }

                Some(window.generic())
            }
            // VBox/HBox are virtual, their behaviors are implemented entirely in the renderer layout.
            // No need for additional windows.
            ET::VBox(model::VBox { items, .. }) => {
                for item in items {
                    self.render_child(item);
                }
                None
            }
            ET::HBox(model::HBox { items, .. }) => {
                for item in items {
                    self.render_child(item);
                }
                None
            }
        }
    }
}

/// Handle the enabled property.
///
/// This function assumes the default state of the window is enabled.
fn enabled_property(enabled: &Property<bool>, window: HWND) {
    match enabled {
        Property::Static(false) => unsafe {
            KeyboardAndMouse::EnableWindow(window, false.into());
        },
        Property::Binding(s) => {
            let handle = window;
            s.on_change(move |enabled| {
                unsafe { KeyboardAndMouse::EnableWindow(handle, (*enabled).into()) };
            });
            if !*s.borrow() {
                unsafe { KeyboardAndMouse::EnableWindow(window, false.into()) };
            }
        }
        _ => (),
    }
}

/// Set the visibility of the given element. This recurses down the element tree and hides children
/// as necessary.
fn set_visibility(element: &Element, visible: bool, windows: &HashMap<ElementRef, HWND>) {
    if let Some(&hwnd) = windows.get(&ElementRef::new(element)) {
        unsafe {
            win::ShowWindow(hwnd, if visible { win::SW_SHOW } else { win::SW_HIDE });
        }
    } else {
        match &element.element_type {
            model::ElementType::VBox(model::VBox { items, .. }) => {
                for item in items {
                    set_visibility(item, visible, windows);
                }
            }
            model::ElementType::HBox(model::HBox { items, .. }) => {
                for item in items {
                    set_visibility(item, visible, windows);
                }
            }
            model::ElementType::Scroll(model::Scroll {
                content: Some(content),
            }) => {
                set_visibility(&*content, visible, windows);
            }
            _ => (),
        }
    }
}

[ Dauer der Verarbeitung: 0.43 Sekunden  (vorverarbeitet)  ]

                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Produkte
     Quellcodebibliothek

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....

Besucherstatistik

Besucherstatistik

Monitoring

Montastic status badge