/* This Source Code Form is subject to the terms of the Mozilla Public *License,v.2.0.IfacopyoftheMPLwasnotdistributedwiththis
* 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" {}
usesuper::model::{self, Application, Element, ElementStyle, TypedElement}; usecrate::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!(!ptr.is_null());
ptr
}};
}
mod dpi; mod font; mod gdi; mod layout; mod quit_token; mod twoway; mod widestring; mod window;
/// A Windows API UI implementation. pubstruct UI {
thread_id: u32,
}
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 { pubfn 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. ifunsafe { 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,
);
/// 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 CustomWindowClass for AppWindow { fn icon() -> win::HICON { // SAFETY: Windows HICON handles are safe to send and share across threads. struct SendSyncIcon(win::HICON); unsafeimpl Send for SendSyncIcon {} unsafeimpl Sync for SendSyncIcon {}
static ICON: Lazy<SendSyncIcon> = Lazy::new(|| unsafe { // If CreateIconFromResource fails it returns NULL, which is fine (a default icon will be // used).
SendSyncIcon(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.0
}
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));
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;
iflet 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() as _),
win::WM_COMMAND => { let child = lparam as HWND; let windows = me.renderer.windows.borrow(); iflet 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) }; iflet 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;
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>,
}
// 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 { letmut 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(&mutself, element_type: &model::ElementType) -> Option<Window> { use model::ElementType as ET; match element_type {
ET::Label(model::Label { text, bold }) => { letmut 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();
// EM_SETCUEBANNER doesn't work with multiline edit controls (for no particular // reason?), so we have to draw it ourselves. unsafeextern"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(); iflet Some(placeholder) = &data.placeholder { letmut 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 { return0;
}
if msg == win::WM_DESTROY {
drop(unsafe { Box::from_raw(dw_ref_data as *mut SubClassData) });
} return ret;
}
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>) { iflet 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);
}
_ => (),
}
}
}
Messung V0.5 in Prozent
¤ Dauer der Verarbeitung: 0.32 Sekunden
(vorverarbeitet am 2026-08-26)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.