/* 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.35 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.
sprechenden Kalenders
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.