Initial support for hidpi (#293)

* Start work on hidpi support

* Introduce maplibre physical and logical size types
This commit is contained in:
Max Ammann 2023-10-12 10:42:58 +01:00 committed by GitHub
parent 10b0e10ad3
commit ad95f7665f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 114 additions and 66 deletions

View File

@ -59,11 +59,11 @@ impl InputController {
/// Process the given winit `[winit::event::WindowEvent]`.
/// Returns true if the event has been processed and false otherwise.
pub fn window_input(&mut self, event: &WindowEvent) -> bool {
pub fn window_input(&mut self, event: &WindowEvent, scale_factor: f64) -> bool {
match event {
WindowEvent::CursorMoved { position, .. } => {
let position: (f64, f64) = position.to_owned().into();
let position = Vector2::from(position);
let position = Vector2::from(position) / scale_factor;
self.pan_handler.process_window_position(&position, false);
self.query_handler.process_window_position(&position, false);
self.zoom_handler.process_window_position(&position, false);
@ -99,14 +99,11 @@ impl InputController {
}
TouchPhase::Moved => {
let position: (f64, f64) = touch.location.to_owned().into();
self.pan_handler
.process_window_position(&Vector2::from(position), true);
self.query_handler
.process_window_position(&Vector2::from(position), true);
self.zoom_handler
.process_window_position(&Vector2::from(position), true);
self.camera_handler
.process_window_position(&Vector2::from(position), true);
let position = Vector2::from(position) / scale_factor;
self.pan_handler.process_window_position(&position, true);
self.query_handler.process_window_position(&position, true);
self.zoom_handler.process_window_position(&position, true);
self.camera_handler.process_window_position(&position, true);
true
}
TouchPhase::Cancelled => false,

View File

@ -8,7 +8,7 @@ use maplibre::{
event_loop::{EventLoop, EventLoopProxy, SendEventError},
io::{apc::AsyncProcedureCall, scheduler::Scheduler, source_client::HttpClient},
map::Map,
window::{HeadedMapWindow, MapWindowConfig},
window::{HeadedMapWindow, MapWindowConfig, PhysicalSize},
};
use winit::{
event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent},
@ -77,6 +77,7 @@ impl<ET: 'static + PartialEq + Debug> EventLoop<ET> for WinitEventLoop<ET> {
let mut current_frame: u64 = 0;
let mut input_controller = InputController::new(0.2, 100.0, 0.1);
let mut scale_factor = 1.0;
self.event_loop
.run(move |event, _window_target, control_flow| {
@ -104,7 +105,7 @@ impl<ET: 'static + PartialEq + Debug> EventLoop<ET> for WinitEventLoop<ET> {
ref event,
window_id,
} if window_id == map.window().id().into() => {
if !input_controller.window_input(event) {
if !input_controller.window_input(event, scale_factor) {
match event {
WindowEvent::CloseRequested
| WindowEvent::KeyboardInput {
@ -116,14 +117,17 @@ impl<ET: 'static + PartialEq + Debug> EventLoop<ET> for WinitEventLoop<ET> {
},
..
} => *control_flow = ControlFlow::Exit,
WindowEvent::Resized(physical_size) => {
if let Ok(map_context) = map.context_mut() {
map_context.resize(physical_size.width, physical_size.height);
WindowEvent::Resized(winit::dpi::PhysicalSize { width, height}) => {
if let Ok(map_context) = map.context_mut() {
let size = PhysicalSize::new(*width, *height).expect("window values should not be zero");
map_context.resize(size, scale_factor);
}
}
WindowEvent::ScaleFactorChanged { new_inner_size, .. } => {
WindowEvent::ScaleFactorChanged { new_inner_size: winit::dpi::PhysicalSize { width, height}, scale_factor: new_scale_factor } => {
if let Ok(map_context) = map.context_mut() {
map_context.resize(new_inner_size.width, new_inner_size.height);
scale_factor = *new_scale_factor;
let size = PhysicalSize::new(*width, *height).expect("window values should not be zero");
map_context.resize(size, scale_factor);
}
}
_ => {}
@ -178,6 +182,7 @@ impl<ET: 'static + PartialEq + Debug> EventLoop<ET> for WinitEventLoop<ET> {
}
}
}
pub struct WinitEventLoopProxy<ET: 'static> {
proxy: RawEventLoopProxy<ET>,
}

View File

@ -16,7 +16,7 @@ use maplibre::{
},
render::{builder::RendererBuilder, settings::WgpuSettings, RenderPlugin},
style::Style,
window::{MapWindow, MapWindowConfig, WindowSize},
window::{MapWindow, MapWindowConfig, PhysicalSize},
};
use winit::window::WindowBuilder;
@ -54,17 +54,17 @@ impl<ET> WinitMapWindowConfig<ET> {
}
impl<ET> MapWindow for WinitMapWindow<ET> {
fn size(&self) -> WindowSize {
fn size(&self) -> PhysicalSize {
let size = self.window.inner_size();
#[cfg(target_os = "android")]
// On android we can not get the dimensions of the window initially. Therefore, we use a
// fallback until the window is ready to deliver its correct bounds.
let window_size =
WindowSize::new(size.width, size.height).unwrap_or(WindowSize::new(100, 100).unwrap());
let window_size = PhysicalSize::new(size.width, size.height)
.unwrap_or(PhysicalSize::new(100, 100).unwrap());
#[cfg(not(target_os = "android"))]
let window_size =
WindowSize::new(size.width, size.height).expect("failed to get window dimensions.");
PhysicalSize::new(size.width, size.height).expect("failed to get window dimensions.");
window_size
}
}

View File

@ -1,6 +1,6 @@
use std::marker::PhantomData;
use maplibre::window::{MapWindow, MapWindowConfig, WindowSize};
use maplibre::window::{MapWindow, MapWindowConfig, PhysicalSize};
use winit::{platform::web::WindowBuilderExtWebSys, window::WindowBuilder};
use super::WinitMapWindow;
@ -44,10 +44,10 @@ impl<ET: 'static + Clone> MapWindowConfig for WinitMapWindowConfig<ET> {
}
impl<ET: 'static> MapWindow for WinitMapWindow<ET> {
fn size(&self) -> WindowSize {
fn size(&self) -> PhysicalSize {
let size = self.window.inner_size();
WindowSize::new(size.width, size.height).expect("failed to get window dimensions.")
PhysicalSize::new(size.width, size.height).expect("failed to get window dimensions.")
}
}

View File

@ -2,6 +2,7 @@ use crate::{
render::{view_state::ViewState, Renderer},
style::Style,
tcs::world::World,
window::PhysicalSize,
};
/// Stores the context of the map.
@ -16,8 +17,8 @@ pub struct MapContext {
}
impl MapContext {
pub fn resize(&mut self, width: u32, height: u32) {
self.view_state.resize(width, height);
self.renderer.resize_surface(width, height)
pub fn resize(&mut self, size: PhysicalSize, scale_factor: f64) {
self.view_state.resize(size.to_logical(scale_factor));
self.renderer.resize_surface(size)
}
}

View File

@ -17,7 +17,7 @@ use crate::{
},
schedule::Schedule,
tcs::{system::SystemContainer, world::World},
window::{MapWindowConfig, WindowSize},
window::{MapWindowConfig, PhysicalSize},
};
mod graph_node;
@ -34,7 +34,7 @@ pub async fn create_headless_renderer(
let client = ReqwestHttpClient::new(cache_path);
let mut kernel = KernelBuilder::new()
.with_map_window_config(HeadlessMapWindowConfig::new(
WindowSize::new(tile_size, tile_size).unwrap(),
PhysicalSize::new(tile_size, tile_size).unwrap(),
))
.with_http_client(client.clone())
.with_apc(SchedulerAsyncProcedureCall::new(TokioScheduler::new()))

View File

@ -1,12 +1,12 @@
use crate::window::{MapWindow, MapWindowConfig, WindowSize};
use crate::window::{MapWindow, MapWindowConfig, PhysicalSize};
#[derive(Clone)]
pub struct HeadlessMapWindowConfig {
size: WindowSize,
size: PhysicalSize,
}
impl HeadlessMapWindowConfig {
pub fn new(size: WindowSize) -> Self {
pub fn new(size: PhysicalSize) -> Self {
Self { size }
}
}
@ -20,11 +20,11 @@ impl MapWindowConfig for HeadlessMapWindowConfig {
}
pub struct HeadlessMapWindow {
size: WindowSize,
size: PhysicalSize,
}
impl MapWindow for HeadlessMapWindow {
fn size(&self) -> WindowSize {
fn size(&self) -> PhysicalSize {
self.size
}
}

View File

@ -67,10 +67,13 @@ pub mod view_state;
pub use shaders::ShaderVertex;
use crate::render::{
render_phase::{LayerItem, RenderPhase, TileMaskItem},
systems::{graph_runner_system::GraphRunnerSystem, upload_system::upload_system},
tile_view_pattern::{ViewTileSources, WgpuTileViewPattern},
use crate::{
render::{
render_phase::{LayerItem, RenderPhase, TileMaskItem},
systems::{graph_runner_system::GraphRunnerSystem, upload_system::upload_system},
tile_view_pattern::{ViewTileSources, WgpuTileViewPattern},
},
window::PhysicalSize,
};
pub(crate) const INDEX_FORMAT: wgpu::IndexFormat = wgpu::IndexFormat::Uint32; // Must match IndexDataType
@ -240,8 +243,8 @@ impl Renderer {
})
}
pub fn resize_surface(&mut self, width: u32, height: u32) {
self.resources.surface.resize(width, height)
pub fn resize_surface(&mut self, size: PhysicalSize) {
self.resources.surface.resize(size)
}
/// Requests a device
@ -421,12 +424,12 @@ impl Renderer {
mod tests {
use crate::{
tcs::world::World,
window::{MapWindow, MapWindowConfig, WindowSize},
window::{MapWindow, MapWindowConfig, PhysicalSize},
};
#[derive(Clone)]
pub struct HeadlessMapWindowConfig {
size: WindowSize,
size: PhysicalSize,
}
impl MapWindowConfig for HeadlessMapWindowConfig {
@ -438,11 +441,11 @@ mod tests {
}
pub struct HeadlessMapWindow {
size: WindowSize,
size: PhysicalSize,
}
impl MapWindow for HeadlessMapWindow {
fn size(&self) -> WindowSize {
fn size(&self) -> PhysicalSize {
self.size
}
}
@ -486,7 +489,7 @@ mod tests {
let render_state = RenderResources::new(Surface::from_image(
&device,
&HeadlessMapWindow {
size: WindowSize::new(100, 100).expect("invalid headless map size"),
size: PhysicalSize::new(100, 100).expect("invalid headless map size"),
},
&RendererSettings::default(),
));

View File

@ -12,7 +12,7 @@ use crate::{
resource::texture::TextureView,
settings::{Msaa, RendererSettings},
},
window::{HeadedMapWindow, MapWindow, WindowSize},
window::{HeadedMapWindow, MapWindow, PhysicalSize},
};
pub struct BufferDimensions {
@ -23,7 +23,7 @@ pub struct BufferDimensions {
}
impl BufferDimensions {
fn new(size: WindowSize) -> Self {
fn new(size: PhysicalSize) -> Self {
let bytes_per_pixel = size_of::<u32>() as u32;
let unpadded_bytes_per_row = size.width() * bytes_per_pixel;
@ -41,7 +41,7 @@ impl BufferDimensions {
pub struct WindowHead {
surface: wgpu::Surface,
size: WindowSize,
size: PhysicalSize,
texture_format: wgpu::TextureFormat,
present_mode: wgpu::PresentMode,
@ -50,7 +50,7 @@ pub struct WindowHead {
impl WindowHead {
pub fn resize_and_configure(&mut self, width: u32, height: u32, device: &wgpu::Device) {
self.size = WindowSize::new(width, height).unwrap();
self.size = PhysicalSize::new(width, height).unwrap();
self.configure(device);
}
@ -164,7 +164,7 @@ pub enum Head {
}
pub struct Surface {
size: WindowSize,
size: PhysicalSize,
head: Head,
}
@ -289,12 +289,12 @@ impl Surface {
}
}
pub fn size(&self) -> WindowSize {
pub fn size(&self) -> PhysicalSize {
self.size
}
pub fn resize(&mut self, width: u32, height: u32) {
self.size = WindowSize::new(width, height).expect("Invalid size for resizing the surface.");
pub fn resize(&mut self, size: PhysicalSize) {
self.size = size;
}
pub fn reconfigure(&mut self, device: &wgpu::Device) {

View File

@ -15,7 +15,7 @@ use crate::{
math::{bounds_from_points, Aabb2, Aabb3, Plane},
ChangeObserver,
},
window::WindowSize,
window::{LogicalSize, PhysicalSize},
};
const VIEW_REGION_PADDING: i32 = 1;
@ -33,7 +33,7 @@ pub struct ViewState {
impl ViewState {
pub fn new<F: Into<Rad<f64>>, P: Into<Deg<f64>>>(
window_size: WindowSize,
window_size: PhysicalSize,
position: WorldCoords,
zoom: Zoom,
pitch: P,
@ -65,9 +65,9 @@ impl ViewState {
&self.edge_insets
}
pub fn resize(&mut self, width: u32, height: u32) {
self.width = width as f64;
self.height = height as f64;
pub fn resize(&mut self, size: LogicalSize) {
self.width = size.width() as f64;
self.height = size.height() as f64;
}
pub fn create_view_region(&self, visible_level: ZoomLevel) -> Option<ViewRegion> {
@ -469,14 +469,14 @@ mod tests {
use crate::{
coords::{WorldCoords, Zoom},
render::view_state::ViewState,
window::WindowSize,
window::PhysicalSize,
};
#[test]
fn conform_transformation() {
let fov = Deg(60.0);
let mut state = ViewState::new(
WindowSize::new(800, 600).unwrap(),
PhysicalSize::new(800, 600).unwrap(),
WorldCoords::at_ground(0.0, 0.0),
Zoom::new(10.0),
Deg(0.0),

View File

@ -4,9 +4,9 @@ use std::num::NonZeroU32;
use raw_window_handle::{HasRawDisplayHandle, HasRawWindowHandle};
/// Window of a certain [`WindowSize`]. This can either be a proper window or a headless one.
/// Window of a certain [`PhysicalSize`]. This can either be a proper window or a headless one.
pub trait MapWindow {
fn size(&self) -> WindowSize;
fn size(&self) -> PhysicalSize;
}
/// Window which references a physical `RawWindow`. This is only implemented by headed windows and
@ -31,13 +31,13 @@ pub trait MapWindowConfig: 'static + Clone {
}
/// Window size with a width and an height in pixels.
#[derive(Clone, Copy, Eq, PartialEq)]
pub struct WindowSize {
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub struct PhysicalSize {
width: NonZeroU32,
height: NonZeroU32,
}
impl WindowSize {
impl PhysicalSize {
pub fn new(width: u32, height: u32) -> Option<Self> {
Some(Self {
width: NonZeroU32::new(width)?,
@ -61,3 +61,45 @@ impl WindowSize {
self.height
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub struct LogicalSize {
width: NonZeroU32,
height: NonZeroU32,
}
impl LogicalSize {
pub fn new(width: u32, height: u32) -> Option<Self> {
Some(Self {
width: NonZeroU32::new(width)?,
height: NonZeroU32::new(height)?,
})
}
pub fn width(&self) -> u32 {
self.width.get()
}
pub fn width_non_zero(&self) -> NonZeroU32 {
self.width
}
pub fn height(&self) -> u32 {
self.height.get()
}
pub fn height_non_zero(&self) -> NonZeroU32 {
self.height
}
}
impl PhysicalSize {
pub fn to_logical(&self, scale_factor: f64) -> LogicalSize {
let width = self.width.get() as f64 / scale_factor;
let height = self.height.get() as f64 / scale_factor;
LogicalSize {
width: NonZeroU32::new(width as u32).expect("impossible to reach"),
height: NonZeroU32::new(height as u32).expect("impossible to reach"),
}
}
}