зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1521450 - Enable per-monitor DPI scaling V2 in the Windows GUI r=gsvelto,glandium
This is quite an improvement on the quirks of the previous GDI scaling. It also mostly supports the windows 10+ "Make text bigger" setting: it reads the value from the registry (albeit at an unofficial location), but doesn't register a key change listener to update the value if it changes while the application is open. I think this is very, very likely to be good enough; I will be surprised if someone notices this deficiency! The official API is part of UWP and is accessible through C++ libraries, but not conveniently through win32 APIs, which is why I use the registry. Differential Revision: https://phabricator.services.mozilla.com/D221544
This commit is contained in:
Родитель
d9fa881390
Коммит
0ad3c6a678
|
@ -216,11 +216,13 @@ features = [
|
||||||
"Win32_System_Memory",
|
"Win32_System_Memory",
|
||||||
"Win32_System_Pipes",
|
"Win32_System_Pipes",
|
||||||
"Win32_System_ProcessStatus",
|
"Win32_System_ProcessStatus",
|
||||||
|
"Win32_System_Registry",
|
||||||
"Win32_System_SystemInformation",
|
"Win32_System_SystemInformation",
|
||||||
"Win32_System_SystemServices",
|
"Win32_System_SystemServices",
|
||||||
"Win32_System_Threading",
|
"Win32_System_Threading",
|
||||||
"Win32_System_WindowsProgramming",
|
"Win32_System_WindowsProgramming",
|
||||||
"Win32_UI_Controls",
|
"Win32_UI_Controls",
|
||||||
|
"Win32_UI_HiDpi",
|
||||||
"Win32_UI_Input_KeyboardAndMouse",
|
"Win32_UI_Input_KeyboardAndMouse",
|
||||||
"Win32_UI_Shell",
|
"Win32_UI_Shell",
|
||||||
"Win32_UI_WindowsAndMessaging",
|
"Win32_UI_WindowsAndMessaging",
|
||||||
|
|
|
@ -47,9 +47,11 @@ features = [
|
||||||
"Win32_Graphics_Gdi",
|
"Win32_Graphics_Gdi",
|
||||||
"Win32_System_Com",
|
"Win32_System_Com",
|
||||||
"Win32_System_LibraryLoader",
|
"Win32_System_LibraryLoader",
|
||||||
|
"Win32_System_Registry",
|
||||||
"Win32_System_SystemServices",
|
"Win32_System_SystemServices",
|
||||||
"Win32_System_Threading",
|
"Win32_System_Threading",
|
||||||
"Win32_UI_Controls",
|
"Win32_UI_Controls",
|
||||||
|
"Win32_UI_HiDpi",
|
||||||
"Win32_UI_Input_KeyboardAndMouse",
|
"Win32_UI_Input_KeyboardAndMouse",
|
||||||
"Win32_UI_Shell",
|
"Win32_UI_Shell",
|
||||||
"Win32_UI_WindowsAndMessaging"
|
"Win32_UI_WindowsAndMessaging"
|
||||||
|
|
|
@ -25,9 +25,9 @@ fn windows_manifest() {
|
||||||
// Use legacy active code page because GDI doesn't support per-process UTF8 (and older
|
// Use legacy active code page because GDI doesn't support per-process UTF8 (and older
|
||||||
// win10 may not support this setting anyway).
|
// win10 may not support this setting anyway).
|
||||||
.active_code_page(manifest::ActiveCodePage::Legacy)
|
.active_code_page(manifest::ActiveCodePage::Legacy)
|
||||||
// GDI scaling is not enabled by default but we need it to make the GDI-drawn text look
|
// We support WM_DPICHANGED for scaling, but need to set our DPI awareness to receive the
|
||||||
// nice on high-DPI displays.
|
// messages.
|
||||||
.gdi_scaling(manifest::Setting::Enabled);
|
.dpi_awareness(manifest::DpiAwareness::PerMonitorV2);
|
||||||
|
|
||||||
embed_manifest(manifest).expect("unable to embed windows manifest file");
|
embed_manifest(manifest).expect("unable to embed windows manifest file");
|
||||||
|
|
||||||
|
|
|
@ -135,6 +135,15 @@ impl<T> Synchronized<T> {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Immediately call the closure on the current value and call it whenever the value changes.
|
||||||
|
pub fn map_with<F: Fn(&T) + 'static>(&self, f: F) {
|
||||||
|
// Hold the borrow to guarantee the value doesn't change until we are subscribed (just as a
|
||||||
|
// measure of extra sanity; this should never occur).
|
||||||
|
let borrow = self.borrow();
|
||||||
|
f(&*borrow);
|
||||||
|
self.on_change(f);
|
||||||
|
}
|
||||||
|
|
||||||
/// Create a new synchronized value which will update when this one changes.
|
/// Create a new synchronized value which will update when this one changes.
|
||||||
pub fn mapped<U: 'static, F: Fn(&T) -> U + 'static>(&self, f: F) -> Synchronized<U> {
|
pub fn mapped<U: 'static, F: Fn(&T) -> U + 'static>(&self, f: F) -> Synchronized<U> {
|
||||||
let s = Synchronized::new(f(&*self.borrow()));
|
let s = Synchronized::new(f(&*self.borrow()));
|
||||||
|
|
|
@ -0,0 +1,63 @@
|
||||||
|
/* 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/. */
|
||||||
|
|
||||||
|
//! DPI management utilities.
|
||||||
|
|
||||||
|
use windows_sys::Win32::{
|
||||||
|
Foundation::HWND,
|
||||||
|
UI::{HiDpi::GetDpiForWindow, WindowsAndMessaging::USER_DEFAULT_SCREEN_DPI as DEFAULT_DPI},
|
||||||
|
};
|
||||||
|
|
||||||
|
// To simplify layout code (avoiding passing values through many functions), we provide an API
|
||||||
|
// which can set a contextual Dpi.
|
||||||
|
thread_local! {
|
||||||
|
static CONTEXT_DPI: std::cell::Cell<Dpi> = Dpi::default().into();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A DPI value.
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub struct Dpi(u32);
|
||||||
|
|
||||||
|
impl Default for Dpi {
|
||||||
|
fn default() -> Self {
|
||||||
|
Dpi(DEFAULT_DPI)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Dpi {
|
||||||
|
/// Create a new Dpi.
|
||||||
|
pub fn new(dpi: u32) -> Self {
|
||||||
|
Dpi(dpi)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the Dpi for the given window.
|
||||||
|
pub fn for_window(hwnd: HWND) -> Self {
|
||||||
|
Dpi(unsafe { GetDpiForWindow(hwnd) })
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Scale a pixel value according to this Dpi.
|
||||||
|
pub fn scale(&self, value: u32) -> u32 {
|
||||||
|
if self.0 == DEFAULT_DPI {
|
||||||
|
value
|
||||||
|
} else {
|
||||||
|
(value as u64 * self.0 as u64 / DEFAULT_DPI as u64) as u32
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Call the given capture with this Dpi set as the contextual Dpi.
|
||||||
|
pub fn with_context<F, R>(&self, f: F) -> R
|
||||||
|
where
|
||||||
|
F: FnOnce() -> R,
|
||||||
|
{
|
||||||
|
let old = CONTEXT_DPI.replace(*self);
|
||||||
|
let ret = f();
|
||||||
|
CONTEXT_DPI.set(old);
|
||||||
|
ret
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Scale a pixel value according to the contextual Dpi.
|
||||||
|
pub fn context_scale(value: u32) -> u32 {
|
||||||
|
CONTEXT_DPI.get().scale(value)
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,21 +2,101 @@
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
* 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/. */
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
use windows_sys::Win32::{Foundation::S_OK, Graphics::Gdi, UI::Controls};
|
use super::Dpi;
|
||||||
|
use super::WideString;
|
||||||
|
use windows_sys::Win32::{
|
||||||
|
Foundation::{ERROR_SUCCESS, S_OK},
|
||||||
|
Graphics::Gdi,
|
||||||
|
System::Registry,
|
||||||
|
UI::Controls,
|
||||||
|
};
|
||||||
|
|
||||||
/// Windows font handle (`HFONT`).
|
/// The default font size to use.
|
||||||
pub struct Font(Gdi::HFONT);
|
///
|
||||||
|
/// `GetThemeSysFont` scales the font based on DPI and accessibility settings, however it only takes
|
||||||
|
/// those active at application startup into account (it won't scale correctly across monitors, nor
|
||||||
|
/// if the DPI of the current monitor changes). So we use a fixed size instead and scale that.
|
||||||
|
const DEFAULT_FONT_SIZE: i32 = -12;
|
||||||
|
|
||||||
impl Font {
|
/// The set of fonts to use.
|
||||||
|
pub struct Fonts {
|
||||||
|
pub normal: Font,
|
||||||
|
pub bold: Font,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The scale factor set by the windows 10 "make text bigger" accessibility setting.
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub struct ScaleFactor(f32);
|
||||||
|
|
||||||
|
impl ScaleFactor {
|
||||||
|
/// Create a new scale factor.
|
||||||
|
///
|
||||||
|
/// The factor will be clamped to [1,2.25], to match the
|
||||||
|
/// [documentation](https://learn.microsoft.com/en-us/uwp/api/windows.ui.viewmanagement.uisettings.textscalefactor)
|
||||||
|
/// and avoid any surprises.
|
||||||
|
pub fn new(factor: f32) -> Self {
|
||||||
|
ScaleFactor(factor.clamp(1., 2.25))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the current scale factor setting from the registry.
|
||||||
|
pub fn from_registry() -> Self {
|
||||||
|
let key = WideString::new("SOFTWARE\\Microsoft\\Accessibility");
|
||||||
|
let value = WideString::new("TextScaleFactor");
|
||||||
|
let mut scale_factor: [u8; 4] = Default::default();
|
||||||
|
let mut size: u32 = std::mem::size_of_val(&scale_factor) as u32;
|
||||||
|
let mut reg_type: u32 = 0;
|
||||||
|
let result = unsafe {
|
||||||
|
Registry::RegGetValueW(
|
||||||
|
Registry::HKEY_CURRENT_USER,
|
||||||
|
key.pcwstr(),
|
||||||
|
value.pcwstr(),
|
||||||
|
Registry::RRF_RT_REG_DWORD,
|
||||||
|
&mut reg_type,
|
||||||
|
&mut scale_factor as *mut u8 as _,
|
||||||
|
&mut size,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
let percent = if result == ERROR_SUCCESS {
|
||||||
|
if reg_type == Registry::REG_DWORD_BIG_ENDIAN {
|
||||||
|
u32::from_be_bytes(scale_factor)
|
||||||
|
} else {
|
||||||
|
u32::from_le_bytes(scale_factor)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
100
|
||||||
|
};
|
||||||
|
Self::new(percent as f32 / 100.)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Fonts {
|
||||||
|
pub fn new(dpi: Dpi, scale_factor: ScaleFactor) -> Self {
|
||||||
|
let builder = FontBuilder { dpi, scale_factor };
|
||||||
|
Fonts {
|
||||||
|
normal: builder.caption(),
|
||||||
|
bold: builder.caption_bold().unwrap_or_else(|| builder.caption()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct FontBuilder {
|
||||||
|
dpi: Dpi,
|
||||||
|
scale_factor: ScaleFactor,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FontBuilder {
|
||||||
/// Get the system theme caption font.
|
/// Get the system theme caption font.
|
||||||
///
|
///
|
||||||
/// Panics if the font cannot be retrieved.
|
/// Panics if the font cannot be retrieved.
|
||||||
pub fn caption() -> Self {
|
pub fn caption(&self) -> Font {
|
||||||
unsafe {
|
unsafe {
|
||||||
let mut font = std::mem::zeroed::<Gdi::LOGFONTW>();
|
let mut font = std::mem::zeroed::<Gdi::LOGFONTW>();
|
||||||
success!(hresult
|
success!(hresult
|
||||||
Controls::GetThemeSysFont(0, Controls::TMT_CAPTIONFONT as i32, &mut font)
|
Controls::GetThemeSysFont(0, Controls::TMT_CAPTIONFONT as i32, &mut font)
|
||||||
);
|
);
|
||||||
|
font.lfHeight = DEFAULT_FONT_SIZE;
|
||||||
|
self.scale_font_height(&mut font.lfHeight);
|
||||||
|
font.lfWidth = 0;
|
||||||
Font(success!(pointer Gdi::CreateFontIndirectW(&font)))
|
Font(success!(pointer Gdi::CreateFontIndirectW(&font)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -24,12 +104,15 @@ impl Font {
|
||||||
/// Get the system theme bold caption font.
|
/// Get the system theme bold caption font.
|
||||||
///
|
///
|
||||||
/// Returns `None` if the font cannot be retrieved.
|
/// Returns `None` if the font cannot be retrieved.
|
||||||
pub fn caption_bold() -> Option<Self> {
|
pub fn caption_bold(&self) -> Option<Font> {
|
||||||
unsafe {
|
unsafe {
|
||||||
let mut font = std::mem::zeroed::<Gdi::LOGFONTW>();
|
let mut font = std::mem::zeroed::<Gdi::LOGFONTW>();
|
||||||
if Controls::GetThemeSysFont(0, Controls::TMT_CAPTIONFONT as i32, &mut font) != S_OK {
|
if Controls::GetThemeSysFont(0, Controls::TMT_CAPTIONFONT as i32, &mut font) != S_OK {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
font.lfHeight = DEFAULT_FONT_SIZE;
|
||||||
|
self.scale_font_height(&mut font.lfHeight);
|
||||||
|
font.lfWidth = 0;
|
||||||
font.lfWeight = Gdi::FW_BOLD as i32;
|
font.lfWeight = Gdi::FW_BOLD as i32;
|
||||||
|
|
||||||
let ptr = Gdi::CreateFontIndirectW(&font);
|
let ptr = Gdi::CreateFontIndirectW(&font);
|
||||||
|
@ -39,8 +122,16 @@ impl Font {
|
||||||
Some(Font(ptr))
|
Some(Font(ptr))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn scale_font_height(&self, height: &mut i32) {
|
||||||
|
*height = (self.dpi.scale(height.abs() as u32) as f32 * self.scale_factor.0) as i32
|
||||||
|
* if height.is_negative() { -1 } else { 1 };
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Windows font handle (`HFONT`).
|
||||||
|
pub struct Font(Gdi::HFONT);
|
||||||
|
|
||||||
impl std::ops::Deref for Font {
|
impl std::ops::Deref for Font {
|
||||||
type Target = Gdi::HFONT;
|
type Target = Gdi::HFONT;
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
model::{self, Alignment, Element, ElementStyle, Margin},
|
model::{self, Alignment, Element, ElementStyle, Margin},
|
||||||
ElementRef, WideString,
|
Dpi, ElementRef, WideString,
|
||||||
};
|
};
|
||||||
use crate::data::Property;
|
use crate::data::Property;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
@ -29,6 +29,7 @@ pub(super) type ElementMapping = HashMap<ElementRef, HWND>;
|
||||||
/// disparate locations.
|
/// disparate locations.
|
||||||
pub struct Layout<'a> {
|
pub struct Layout<'a> {
|
||||||
elements: &'a ElementMapping,
|
elements: &'a ElementMapping,
|
||||||
|
dpi: Dpi,
|
||||||
sizes: HashMap<ElementRef, Size>,
|
sizes: HashMap<ElementRef, Size>,
|
||||||
last_positioned: Option<HWND>,
|
last_positioned: Option<HWND>,
|
||||||
}
|
}
|
||||||
|
@ -49,9 +50,10 @@ const CHECKBOX_MARGIN: Margin = Margin {
|
||||||
};
|
};
|
||||||
|
|
||||||
impl<'a> Layout<'a> {
|
impl<'a> Layout<'a> {
|
||||||
pub(super) fn new(elements: &'a ElementMapping) -> Self {
|
pub(super) fn new(elements: &'a ElementMapping, dpi: Dpi) -> Self {
|
||||||
Layout {
|
Layout {
|
||||||
elements,
|
elements,
|
||||||
|
dpi,
|
||||||
sizes: Default::default(),
|
sizes: Default::default(),
|
||||||
last_positioned: None,
|
last_positioned: None,
|
||||||
}
|
}
|
||||||
|
@ -59,12 +61,14 @@ impl<'a> Layout<'a> {
|
||||||
|
|
||||||
/// Perform a layout of the element and all child elements.
|
/// Perform a layout of the element and all child elements.
|
||||||
pub fn layout(mut self, element: &Element, max_width: u32, max_height: u32) {
|
pub fn layout(mut self, element: &Element, max_width: u32, max_height: u32) {
|
||||||
let max_size = Size {
|
self.dpi.clone().with_context(|| {
|
||||||
width: max_width,
|
let max_size = Size {
|
||||||
height: max_height,
|
width: max_width,
|
||||||
};
|
height: max_height,
|
||||||
self.resize(element, &max_size);
|
};
|
||||||
self.reposition(element, &Position::default(), &max_size);
|
self.resize(element, &max_size);
|
||||||
|
self.reposition(element, &Position::default(), &max_size);
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn resize(&mut self, element: &Element, max_size: &Size) -> Size {
|
fn resize(&mut self, element: &Element, max_size: &Size) -> Size {
|
||||||
|
@ -126,6 +130,7 @@ impl<'a> Layout<'a> {
|
||||||
content_size = Some(size);
|
content_size = Some(size);
|
||||||
}
|
}
|
||||||
VBox(model::VBox { items, spacing }) => {
|
VBox(model::VBox { items, spacing }) => {
|
||||||
|
let spacing = Dpi::context_scale(*spacing);
|
||||||
let mut height = 0;
|
let mut height = 0;
|
||||||
let mut max_width = 0;
|
let mut max_width = 0;
|
||||||
let mut remaining_size = inner_size.clone();
|
let mut remaining_size = inner_size.clone();
|
||||||
|
@ -160,6 +165,7 @@ impl<'a> Layout<'a> {
|
||||||
spacing,
|
spacing,
|
||||||
affirmative_order: _,
|
affirmative_order: _,
|
||||||
}) => {
|
}) => {
|
||||||
|
let spacing = Dpi::context_scale(*spacing);
|
||||||
let mut width = 0;
|
let mut width = 0;
|
||||||
let mut max_height = 0;
|
let mut max_height = 0;
|
||||||
let mut remaining_size = inner_size.clone();
|
let mut remaining_size = inner_size.clone();
|
||||||
|
@ -197,8 +203,8 @@ impl<'a> Layout<'a> {
|
||||||
Progress(model::Progress { .. }) => {
|
Progress(model::Progress { .. }) => {
|
||||||
// Min size recommended by windows uxguide
|
// Min size recommended by windows uxguide
|
||||||
content_size = Some(Size {
|
content_size = Some(Size {
|
||||||
width: 160,
|
width: Dpi::context_scale(160),
|
||||||
height: 15,
|
height: Dpi::context_scale(15),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
// We don't support sizing by textbox content yet (need to read from the HWND due to
|
// We don't support sizing by textbox content yet (need to read from the HWND due to
|
||||||
|
@ -273,7 +279,7 @@ impl<'a> Layout<'a> {
|
||||||
let mut size = inner_size;
|
let mut size = inner_size;
|
||||||
for item in items {
|
for item in items {
|
||||||
self.reposition(item, &position, &size);
|
self.reposition(item, &position, &size);
|
||||||
let consumed = self.get_size(item).height + spacing;
|
let consumed = self.get_size(item).height + Dpi::context_scale(*spacing);
|
||||||
if item.style.vertical_alignment != Alignment::End {
|
if item.style.vertical_alignment != Alignment::End {
|
||||||
position.top += consumed;
|
position.top += consumed;
|
||||||
}
|
}
|
||||||
|
@ -290,7 +296,7 @@ impl<'a> Layout<'a> {
|
||||||
let mut size = inner_size;
|
let mut size = inner_size;
|
||||||
for item in items {
|
for item in items {
|
||||||
self.reposition(item, &position, &inner_size);
|
self.reposition(item, &position, &inner_size);
|
||||||
let consumed = self.get_size(item).width + spacing;
|
let consumed = self.get_size(item).width + Dpi::context_scale(*spacing);
|
||||||
if item.style.horizontal_alignment != Alignment::End {
|
if item.style.horizontal_alignment != Alignment::End {
|
||||||
position.start += consumed;
|
position.start += consumed;
|
||||||
}
|
}
|
||||||
|
@ -377,21 +383,21 @@ impl Size {
|
||||||
pub fn inner_size(&self, style: &ElementStyle) -> Self {
|
pub fn inner_size(&self, style: &ElementStyle) -> Self {
|
||||||
let mut ret = self.less_margin(&style.margin);
|
let mut ret = self.less_margin(&style.margin);
|
||||||
if let Some(width) = style.horizontal_size_request {
|
if let Some(width) = style.horizontal_size_request {
|
||||||
ret.width = width;
|
ret.width = Dpi::context_scale(width);
|
||||||
}
|
}
|
||||||
if let Some(height) = style.vertical_size_request {
|
if let Some(height) = style.vertical_size_request {
|
||||||
ret.height = height;
|
ret.height = Dpi::context_scale(height);
|
||||||
}
|
}
|
||||||
ret
|
ret
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn from_content_size(&mut self, style: &ElementStyle, content_size: &Self) {
|
pub fn from_content_size(&mut self, style: &ElementStyle, content_size: &Self) {
|
||||||
if style.horizontal_size_request < Some(content_size.width)
|
if style.horizontal_size_request.map(Dpi::context_scale) < Some(content_size.width)
|
||||||
&& style.horizontal_alignment != Alignment::Fill
|
&& style.horizontal_alignment != Alignment::Fill
|
||||||
{
|
{
|
||||||
self.width = content_size.width;
|
self.width = content_size.width;
|
||||||
}
|
}
|
||||||
if style.vertical_size_request < Some(content_size.height)
|
if style.vertical_size_request.map(Dpi::context_scale) < Some(content_size.height)
|
||||||
&& style.vertical_alignment != Alignment::Fill
|
&& style.vertical_alignment != Alignment::Fill
|
||||||
{
|
{
|
||||||
self.height = content_size.height;
|
self.height = content_size.height;
|
||||||
|
@ -400,15 +406,19 @@ impl Size {
|
||||||
|
|
||||||
pub fn plus_margin(&self, margin: &Margin) -> Self {
|
pub fn plus_margin(&self, margin: &Margin) -> Self {
|
||||||
let mut ret = self.clone();
|
let mut ret = self.clone();
|
||||||
ret.width += margin.start + margin.end;
|
ret.width += Dpi::context_scale(margin.start + margin.end);
|
||||||
ret.height += margin.top + margin.bottom;
|
ret.height += Dpi::context_scale(margin.top + margin.bottom);
|
||||||
ret
|
ret
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn less_margin(&self, margin: &Margin) -> Self {
|
pub fn less_margin(&self, margin: &Margin) -> Self {
|
||||||
let mut ret = self.clone();
|
let mut ret = self.clone();
|
||||||
ret.width = ret.width.saturating_sub(margin.start + margin.end);
|
ret.width = ret
|
||||||
ret.height = ret.height.saturating_sub(margin.top + margin.bottom);
|
.width
|
||||||
|
.saturating_sub(Dpi::context_scale(margin.start + margin.end));
|
||||||
|
ret.height = ret
|
||||||
|
.height
|
||||||
|
.saturating_sub(Dpi::context_scale(margin.top + margin.bottom));
|
||||||
ret
|
ret
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -423,15 +433,15 @@ impl Position {
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub fn plus_margin(&self, margin: &Margin) -> Self {
|
pub fn plus_margin(&self, margin: &Margin) -> Self {
|
||||||
let mut ret = self.clone();
|
let mut ret = self.clone();
|
||||||
ret.start = ret.start.saturating_sub(margin.start);
|
ret.start = ret.start.saturating_sub(Dpi::context_scale(margin.start));
|
||||||
ret.top = ret.top.saturating_sub(margin.top);
|
ret.top = ret.top.saturating_sub(Dpi::context_scale(margin.top));
|
||||||
ret
|
ret
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn less_margin(&self, margin: &Margin) -> Self {
|
pub fn less_margin(&self, margin: &Margin) -> Self {
|
||||||
let mut ret = self.clone();
|
let mut ret = self.clone();
|
||||||
ret.start += margin.start;
|
ret.start += Dpi::context_scale(margin.start);
|
||||||
ret.top += margin.top;
|
ret.top += Dpi::context_scale(margin.top);
|
||||||
ret
|
ret
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,8 +22,9 @@
|
||||||
extern "C" {}
|
extern "C" {}
|
||||||
|
|
||||||
use super::model::{self, Application, Element, ElementStyle, TypedElement};
|
use super::model::{self, Application, Element, ElementStyle, TypedElement};
|
||||||
use crate::data::Property;
|
use crate::data::{Property, Synchronized};
|
||||||
use font::Font;
|
use dpi::Dpi;
|
||||||
|
use font::Fonts;
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use quit_token::QuitToken;
|
use quit_token::QuitToken;
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
|
@ -61,6 +62,7 @@ macro_rules! success {
|
||||||
}};
|
}};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mod dpi;
|
||||||
mod font;
|
mod font;
|
||||||
mod gdi;
|
mod gdi;
|
||||||
mod layout;
|
mod layout;
|
||||||
|
@ -318,6 +320,8 @@ impl CustomWindowClass for AppWindow {
|
||||||
let model = me.renderer.model();
|
let model = me.renderer.model();
|
||||||
match umsg {
|
match umsg {
|
||||||
win::WM_CREATE => {
|
win::WM_CREATE => {
|
||||||
|
me.renderer.set_dpi(Dpi::for_window(hwnd));
|
||||||
|
|
||||||
if let Some(close) = &model.close {
|
if let Some(close) = &model.close {
|
||||||
close.subscribe(move |&()| unsafe {
|
close.subscribe(move |&()| unsafe {
|
||||||
win::SendMessageW(hwnd, win::WM_CLOSE, 0, 0);
|
win::SendMessageW(hwnd, win::WM_CLOSE, 0, 0);
|
||||||
|
@ -358,8 +362,8 @@ impl CustomWindowClass for AppWindow {
|
||||||
}
|
}
|
||||||
win::WM_GETMINMAXINFO => {
|
win::WM_GETMINMAXINFO => {
|
||||||
let minmaxinfo = unsafe { (lparam as *mut win::MINMAXINFO).as_mut().unwrap() };
|
let minmaxinfo = unsafe { (lparam as *mut win::MINMAXINFO).as_mut().unwrap() };
|
||||||
minmaxinfo.ptMinTrackSize.x = me.renderer.min_size.0.try_into().unwrap();
|
minmaxinfo.ptMinTrackSize.x = me.renderer.min_width().try_into().unwrap();
|
||||||
minmaxinfo.ptMinTrackSize.y = me.renderer.min_size.1.try_into().unwrap();
|
minmaxinfo.ptMinTrackSize.y = me.renderer.min_height().try_into().unwrap();
|
||||||
return Some(0);
|
return Some(0);
|
||||||
}
|
}
|
||||||
win::WM_SIZE => {
|
win::WM_SIZE => {
|
||||||
|
@ -373,7 +377,31 @@ impl CustomWindowClass for AppWindow {
|
||||||
}
|
}
|
||||||
return Some(0);
|
return Some(0);
|
||||||
}
|
}
|
||||||
win::WM_GETFONT => return Some(**me.renderer.font() as _),
|
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 => {
|
win::WM_COMMAND => {
|
||||||
let child = lparam as HWND;
|
let child = lparam as HWND;
|
||||||
let windows = me.renderer.windows.borrow();
|
let windows = me.renderer.windows.borrow();
|
||||||
|
@ -435,17 +463,20 @@ struct WindowRendererInner {
|
||||||
/// changes. Unfortunately the win32 API doesn't have any nice ways to automatically perform
|
/// changes. Unfortunately the win32 API doesn't have any nice ways to automatically perform
|
||||||
/// layout.
|
/// layout.
|
||||||
pub model: RefCell<Pin<Box<model::Window>>>,
|
pub model: RefCell<Pin<Box<model::Window>>>,
|
||||||
pub min_size: (u32, u32),
|
min_size: (u32, u32),
|
||||||
/// Mapping between model elements and windows.
|
/// Mapping between model elements and windows.
|
||||||
///
|
///
|
||||||
/// Element references pertain to elements in `model`.
|
/// Element references pertain to elements in `model`.
|
||||||
pub windows: RefCell<twoway::TwoWay<ElementRef, HWND>>,
|
pub windows: RefCell<twoway::TwoWay<ElementRef, HWND>>,
|
||||||
pub font: Font,
|
pub dpi: Synchronized<Dpi>,
|
||||||
pub bold_font: Font,
|
pub fonts: Synchronized<Fonts>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WindowRenderer {
|
impl WindowRenderer {
|
||||||
pub fn new(module: HINSTANCE, model: model::Window, style: &model::ElementStyle) -> Self {
|
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 {
|
WindowRenderer {
|
||||||
inner: Rc::new(WindowRendererInner {
|
inner: Rc::new(WindowRendererInner {
|
||||||
module,
|
module,
|
||||||
|
@ -455,8 +486,8 @@ impl WindowRenderer {
|
||||||
style.vertical_size_request.unwrap_or(0),
|
style.vertical_size_request.unwrap_or(0),
|
||||||
),
|
),
|
||||||
windows: Default::default(),
|
windows: Default::default(),
|
||||||
font: Font::caption(),
|
dpi,
|
||||||
bold_font: Font::caption_bold().unwrap_or_else(Font::caption),
|
fonts,
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -470,9 +501,16 @@ impl WindowRenderer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_dpi(&self, dpi: Dpi) {
|
||||||
|
*self.dpi.borrow_mut() = dpi;
|
||||||
|
}
|
||||||
|
|
||||||
pub fn layout(&self, element: &Element, max_width: u32, max_height: u32) {
|
pub fn layout(&self, element: &Element, max_width: u32, max_height: u32) {
|
||||||
layout::Layout::new(self.inner.windows.borrow().forward())
|
layout::Layout::new(
|
||||||
.layout(element, max_width, max_height);
|
self.inner.windows.borrow().forward(),
|
||||||
|
*self.inner.dpi.borrow(),
|
||||||
|
)
|
||||||
|
.layout(element, max_width, max_height);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn model(&self) -> std::cell::Ref<'_, model::Window> {
|
pub fn model(&self) -> std::cell::Ref<'_, model::Window> {
|
||||||
|
@ -483,8 +521,16 @@ impl WindowRenderer {
|
||||||
std::cell::RefMut::map(self.inner.model.borrow_mut(), |b| &mut **b)
|
std::cell::RefMut::map(self.inner.model.borrow_mut(), |b| &mut **b)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn font(&self) -> &Font {
|
pub fn font(&self) -> Gdi::HFONT {
|
||||||
&self.inner.font
|
*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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -558,7 +604,7 @@ impl<'a> WindowChildRenderer<'a> {
|
||||||
|
|
||||||
fn render_child(&mut self, element: &Element) {
|
fn render_child(&mut self, element: &Element) {
|
||||||
if let Some(mut window) = self.render_element_type(&element.element_type) {
|
if let Some(mut window) = self.render_element_type(&element.element_type) {
|
||||||
window.set_default_font(&self.renderer.font);
|
window.set_default_font(&self.renderer.fonts, |fonts| &fonts.normal);
|
||||||
|
|
||||||
// Store the element to handle mapping.
|
// Store the element to handle mapping.
|
||||||
self.renderer
|
self.renderer
|
||||||
|
@ -641,7 +687,7 @@ impl<'a> WindowChildRenderer<'a> {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
if *bold {
|
if *bold {
|
||||||
window.set_font(&self.renderer.bold_font);
|
window.set_font(&self.renderer.fonts, |fonts| &fonts.bold);
|
||||||
}
|
}
|
||||||
Some(window.generic())
|
Some(window.generic())
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,8 +4,9 @@
|
||||||
|
|
||||||
//! Types and helpers relating to windows and window classes.
|
//! Types and helpers relating to windows and window classes.
|
||||||
|
|
||||||
use super::Font;
|
use super::font::{Font, Fonts};
|
||||||
use super::WideString;
|
use super::WideString;
|
||||||
|
use crate::data::Synchronized;
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
use windows_sys::Win32::{
|
use windows_sys::Win32::{
|
||||||
Foundation::{HINSTANCE, HWND, LPARAM, LRESULT, WPARAM},
|
Foundation::{HINSTANCE, HWND, LPARAM, LRESULT, WPARAM},
|
||||||
|
@ -297,15 +298,26 @@ impl<W> Window<W> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set a window's font.
|
/// Set a window's font.
|
||||||
pub fn set_font(&mut self, font: &Font) {
|
pub fn set_font(
|
||||||
unsafe { win::SendMessageW(self.handle, win::WM_SETFONT, **font as _, 1 as _) };
|
&mut self,
|
||||||
|
fonts: &Synchronized<Fonts>,
|
||||||
|
font: impl Fn(&Fonts) -> &Font + 'static,
|
||||||
|
) {
|
||||||
|
let handle = self.handle;
|
||||||
|
fonts.map_with(move |fonts| unsafe {
|
||||||
|
win::SendMessageW(handle, win::WM_SETFONT, **font(fonts) as _, 1 as _);
|
||||||
|
});
|
||||||
self.font_set = true;
|
self.font_set = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set a window's font if not already set.
|
/// Set a window's font if not already set.
|
||||||
pub fn set_default_font(&mut self, font: &Font) {
|
pub fn set_default_font(
|
||||||
|
&mut self,
|
||||||
|
fonts: &Synchronized<Fonts>,
|
||||||
|
font: impl Fn(&Fonts) -> &Font + 'static,
|
||||||
|
) {
|
||||||
if !self.font_set {
|
if !self.font_set {
|
||||||
self.set_font(font);
|
self.set_font(fonts, font);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Загрузка…
Ссылка в новой задаче