Compare commits

..

11 Commits

Author SHA1 Message Date
9882b6a578 setwindowsize 2020-05-18 17:56:22 +02:00
71c98d0ade Revert "cheat"
This reverts commit 132203fe10.
2020-05-18 17:56:14 +02:00
132203fe10 cheat 2020-05-18 17:45:49 +02:00
2d6302f635 morelogs 2020-05-18 17:39:48 +02:00
e1118e6b18 more logs 2020-05-18 17:37:31 +02:00
3b41c6dd62 morelogs 2020-05-18 16:48:08 +02:00
fe2ef83a1e Manually position and scale the content
Position and scale the content "manually" instead of relying on the
renderer "logical size".

This avoids possible rounding differences between the computed window
size and the content size, causing one row or column of black pixels on
the bottom or on the right.

This also avoids HiDPI scale issues, by computing the scaling manually.

This will also enable to draw items at their expected size on the screen
(unscaled).
2020-05-18 16:15:56 +02:00
5d5d3b7865 Extract optimal window size detection
Extract the computation to detect whether the current size of the window
is already optimal.

This will allow to reuse it for rendering.
2020-05-15 19:03:22 +02:00
eb189ceaab Disable "resize to fit" in maximized state
In maximized state (but not fullscreen), it was possible to resize to
fit the device screen (with Ctrl+x or double-clicking on black borders).

This caused problems on macOS with the "expand to fullscreen" feature,
which behaves like a fullscreen mode but is seen as maximized by SDL.
In that state, resizing to fit causes unexpected results.

To keep the behavior consistent on all platforms, just disable "resize
to fit" when the window is maximized.
2020-05-15 18:56:28 +02:00
b19d708a68 Workaround maximized+fullscreen on Windows
On Windows, in maximized+fullscreen state, disabling fullscreen mode
unexpectedly triggers the "restored" then "maximized" events, leaving
the window in a weird state (maximized according to the events, but not
maximized visually).

Moreover, apply_pending_resize() asserts that fullscreen is disabled.

To avoid the issue, if fullscreen is set, just ignore the "restored"
event.
2020-05-12 22:33:57 +02:00
6dc113e67b Simplify size changes in fullscreen or maximized
If the content size changes (due to rotation for example) while the
window is maximized or fullscreen, the resize must be applied once
fullscreen and maximized are disabled.

The previous strategy consisted in storing the windowed size, computing
the target size on rotation, and applying it on window restoration. But
tracking the windowed size (while ignoring the non-windowed size) was
tricky, due to unspecified order of SDL events (e.g. size changes can be
notified before "maximized" events), race conditions when reading window
flags, different behaviors on different platforms...

To simplify the whole resize management, store the old content size (the
frame size, possibly rotated) when it changes while the window is
maximized or fullscreen, so that the new optimal size can be computed on
window restoration.
2020-05-12 22:33:51 +02:00
4 changed files with 173 additions and 124 deletions

View File

@ -7,33 +7,6 @@
#include "util/lock.h"
#include "util/log.h"
// Convert window coordinates (as provided by SDL_GetMouseState() to renderer
// coordinates (as provided in SDL mouse events)
//
// See my question:
// <https://stackoverflow.com/questions/49111054/how-to-get-mouse-position-on-mouse-wheel-event>
static void
convert_to_renderer_coordinates(SDL_Renderer *renderer, int *x, int *y) {
SDL_Rect viewport;
float scale_x, scale_y;
SDL_RenderGetViewport(renderer, &viewport);
SDL_RenderGetScale(renderer, &scale_x, &scale_y);
*x = (int) (*x / scale_x) - viewport.x;
*y = (int) (*y / scale_y) - viewport.y;
}
static struct point
get_mouse_point(struct screen *screen) {
int x;
int y;
SDL_GetMouseState(&x, &y);
convert_to_renderer_coordinates(screen->renderer, &x, &y);
return (struct point) {
.x = x,
.y = y,
};
}
static const int ACTION_DOWN = 1;
static const int ACTION_UP = 1 << 1;
@ -487,11 +460,17 @@ convert_touch(const SDL_TouchFingerEvent *from, struct screen *screen,
to->inject_touch_event.pointer_id = from->fingerId;
to->inject_touch_event.position.screen_size = screen->frame_size;
int ww;
int wh;
SDL_GL_GetDrawableSize(screen->window, &ww, &wh);
// SDL touch event coordinates are normalized in the range [0; 1]
float x = from->x * screen->content_size.width;
float y = from->y * screen->content_size.height;
int32_t x = from->x * ww;
int32_t y = from->y * wh;
to->inject_touch_event.position.point =
screen_convert_to_frame_coords(screen, x, y);
to->inject_touch_event.pressure = from->pressure;
to->inject_touch_event.buttons = 0;
return true;
@ -508,13 +487,6 @@ input_manager_process_touch(struct input_manager *im,
}
}
static bool
is_outside_device_screen(struct input_manager *im, int x, int y)
{
return x < 0 || x >= im->screen->content_size.width ||
y < 0 || y >= im->screen->content_size.height;
}
static bool
convert_mouse_button(const SDL_MouseButtonEvent *from, struct screen *screen,
struct control_msg *to) {
@ -552,10 +524,15 @@ input_manager_process_mouse_button(struct input_manager *im,
action_home(im->controller, ACTION_DOWN | ACTION_UP);
return;
}
// double-click on black borders resize to fit the device screen
if (event->button == SDL_BUTTON_LEFT && event->clicks == 2) {
bool outside =
is_outside_device_screen(im, event->x, event->y);
int32_t x = event->x;
int32_t y = event->y;
screen_hidpi_scale_coords(im->screen, &x, &y);
SDL_Rect *r = &im->screen->rect;
bool outside = x < r->x || x >= r->x + r->w
|| y < r->y || y >= r->y + r->h;
if (outside) {
screen_resize_to_fit(im->screen);
return;
@ -579,9 +556,15 @@ input_manager_process_mouse_button(struct input_manager *im,
static bool
convert_mouse_wheel(const SDL_MouseWheelEvent *from, struct screen *screen,
struct control_msg *to) {
// mouse_x and mouse_y are expressed in pixels relative to the window
int mouse_x;
int mouse_y;
SDL_GetMouseState(&mouse_x, &mouse_y);
struct position position = {
.screen_size = screen->frame_size,
.point = get_mouse_point(screen),
.point = screen_convert_to_frame_coords(screen, mouse_x, mouse_y),
};
to->type = CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT;

View File

@ -133,10 +133,10 @@ static int
event_watcher(void *data, SDL_Event *event) {
(void) data;
if (event->type == SDL_WINDOWEVENT
&& event->window.event == SDL_WINDOWEVENT_SIZE_CHANGED) {
&& event->window.event == SDL_WINDOWEVENT_RESIZED) {
// In practice, it seems to always be called from the same thread in
// that specific case. Anyway, it's just a workaround.
screen_render(&screen);
screen_render(&screen, true);
}
return 0;
}

View File

@ -68,6 +68,16 @@ get_preferred_display_bounds(struct size *bounds) {
return true;
}
static bool
is_optimal_size(struct size current_size, struct size content_size) {
// The size is optimal if we can recompute one dimension of the current
// size from the other
return current_size.height == current_size.width * content_size.height
/ content_size.width
|| current_size.width == current_size.height * content_size.width
/ content_size.height;
}
// return the optimal size of the window, with the following constraints:
// - it attempts to keep at least one dimension of the current_size (i.e. it
// crops the black borders)
@ -80,40 +90,36 @@ get_optimal_size(struct size current_size, struct size content_size) {
return current_size;
}
struct size display_size;
// 32 bits because we need to multiply two 16 bits values
uint32_t w;
uint32_t h;
struct size window_size;
struct size display_size;
if (!get_preferred_display_bounds(&display_size)) {
// could not get display bounds, do not constraint the size
w = current_size.width;
h = current_size.height;
window_size.width = current_size.width;
window_size.height = current_size.height;
} else {
w = MIN(current_size.width, display_size.width);
h = MIN(current_size.height, display_size.height);
window_size.width = MIN(current_size.width, display_size.width);
window_size.height = MIN(current_size.height, display_size.height);
}
if (h == w * content_size.height / content_size.width
|| w == h * content_size.width / content_size.height) {
// The size is already optimal, if we ignore rounding errors due to
// integer window dimensions
return (struct size) {w, h};
if (is_optimal_size(window_size, content_size)) {
return window_size;
}
bool keep_width = content_size.width * h > content_size.height * w;
bool keep_width = content_size.width * window_size.height
> content_size.height * window_size.width;
if (keep_width) {
// remove black borders on top and bottom
h = content_size.height * w / content_size.width;
window_size.height = content_size.height * window_size.width
/ content_size.width;
} else {
// remove black borders on left and right (or none at all if it already
// fits)
w = content_size.width * h / content_size.height;
window_size.width = content_size.width * window_size.height
/ content_size.height;
}
// w and h must fit into 16 bits
assert(w < 0x10000 && h < 0x10000);
return (struct size) {w, h};
return window_size;
}
// same as get_optimal_size(), but read the current size from the window
@ -150,6 +156,51 @@ get_initial_optimal_size(struct size content_size, uint16_t req_width,
return window_size;
}
static void
screen_update_content_rect(struct screen *screen) {
int dw;
int dh;
SDL_GL_GetDrawableSize(screen->window, &dw, &dh);
int ww, wh;
SDL_GetWindowSize(screen->window, &ww, &wh);
struct size content_size = screen->content_size;
// The drawable size is the window size * the HiDPI scale
struct size drawable_size = {dw, dh};
LOGI("update_content_rect: window=%dx%d, drawable=%dx%d content=%ux%u", ww, wh, dw, dh, content_size.width, content_size.height);
SDL_Rect *rect = &screen->rect;
if (is_optimal_size(drawable_size, content_size)) {
rect->x = 0;
rect->y = 0;
rect->w = drawable_size.width;
rect->h = drawable_size.height;
LOGI(" --> (optimal) rect (%d, %d) %dx%d", rect->x, rect->y, rect->w, rect->h);
return;
}
bool keep_width = content_size.width * drawable_size.height
> content_size.height * drawable_size.width;
if (keep_width) {
rect->x = 0;
rect->w = drawable_size.width;
rect->h = drawable_size.width * content_size.height
/ content_size.width;
rect->y = (drawable_size.height - rect->h) / 2;
} else {
rect->y = 0;
rect->h = drawable_size.height;
rect->w = drawable_size.height * content_size.width
/ content_size.height;
rect->x = (drawable_size.width - rect->w) / 2;
}
LOGI(" --> rect (%d, %d) %dx%d", rect->x, rect->y, rect->w, rect->h);
}
void
screen_init(struct screen *screen) {
*screen = (struct screen) SCREEN_INITIALIZER;
@ -239,13 +290,6 @@ screen_init_rendering(struct screen *screen, const char *window_title,
const char *renderer_name = r ? NULL : renderer_info.name;
LOGI("Renderer: %s", renderer_name ? renderer_name : "(unknown)");
if (SDL_RenderSetLogicalSize(screen->renderer, content_size.width,
content_size.height)) {
LOGE("Could not set renderer logical size: %s", SDL_GetError());
screen_destroy(screen);
return false;
}
// starts with "opengl"
screen->use_opengl = renderer_name && !strncmp(renderer_name, "opengl", 6);
if (screen->use_opengl) {
@ -289,6 +333,10 @@ screen_init_rendering(struct screen *screen, const char *window_title,
return false;
}
SDL_SetWindowSize(screen->window, window_size.width, window_size.height);
screen_update_content_rect(screen);
return true;
}
@ -359,18 +407,12 @@ screen_set_rotation(struct screen *screen, unsigned rotation) {
struct size new_content_size =
get_rotated_size(screen->frame_size, rotation);
if (SDL_RenderSetLogicalSize(screen->renderer,
new_content_size.width,
new_content_size.height)) {
LOGE("Could not set renderer logical size: %s", SDL_GetError());
return;
}
set_content_size(screen, new_content_size);
screen->rotation = rotation;
LOGI("Display rotation set to %u", rotation);
screen_render(screen);
screen_render(screen, true);
}
// recreate the texture and resize the window if the frame size has changed
@ -378,21 +420,17 @@ static bool
prepare_for_frame(struct screen *screen, struct size new_frame_size) {
if (screen->frame_size.width != new_frame_size.width
|| screen->frame_size.height != new_frame_size.height) {
struct size new_content_size =
get_rotated_size(new_frame_size, screen->rotation);
if (SDL_RenderSetLogicalSize(screen->renderer,
new_content_size.width,
new_content_size.height)) {
LOGE("Could not set renderer logical size: %s", SDL_GetError());
return false;
}
// frame dimension changed, destroy texture
SDL_DestroyTexture(screen->texture);
set_content_size(screen, new_content_size);
screen->frame_size = new_frame_size;
struct size new_content_size =
get_rotated_size(new_frame_size, screen->rotation);
set_content_size(screen, new_content_size);
screen_update_content_rect(screen);
LOGI("New texture: %" PRIu16 "x%" PRIu16,
screen->frame_size.width, screen->frame_size.height);
screen->texture = create_texture(screen);
@ -433,15 +471,19 @@ screen_update_frame(struct screen *screen, struct video_buffer *vb) {
update_texture(screen, frame);
mutex_unlock(vb->mutex);
screen_render(screen);
screen_render(screen, false);
return true;
}
void
screen_render(struct screen *screen) {
screen_render(struct screen *screen, bool update_content_rect) {
if (update_content_rect) {
screen_update_content_rect(screen);
}
SDL_RenderClear(screen->renderer);
if (screen->rotation == 0) {
SDL_RenderCopy(screen->renderer, screen->texture, NULL, NULL);
SDL_RenderCopy(screen->renderer, screen->texture, NULL, &screen->rect);
} else {
// rotation in RenderCopyEx() is clockwise, while screen->rotation is
// counterclockwise (to be consistent with --lock-video-orientation)
@ -451,12 +493,14 @@ screen_render(struct screen *screen) {
SDL_Rect *dstrect = NULL;
SDL_Rect rect;
if (screen->rotation & 1) {
struct size size = screen->content_size;
rect.x = (size.width - size.height) / 2;
rect.y = (size.height - size.width) / 2;
rect.w = size.height;
rect.h = size.width;
rect.x = screen->rect.x + (screen->rect.w - screen->rect.h) / 2;
rect.y = screen->rect.y + (screen->rect.h - screen->rect.w) / 2;
rect.w = screen->rect.h;
rect.h = screen->rect.w;
dstrect = &rect;
} else {
assert(screen->rotation == 2);
dstrect = &screen->rect;
}
SDL_RenderCopyEx(screen->renderer, screen->texture, NULL, dstrect,
@ -479,20 +523,15 @@ screen_switch_fullscreen(struct screen *screen) {
}
LOGD("Switched to %s mode", screen->fullscreen ? "fullscreen" : "windowed");
screen_render(screen);
screen_render(screen, true);
}
void
screen_resize_to_fit(struct screen *screen) {
if (screen->fullscreen) {
if (screen->fullscreen || screen->maximized) {
return;
}
if (screen->maximized) {
SDL_RestoreWindow(screen->window);
screen->maximized = false;
}
struct size optimal_size =
get_optimal_window_size(screen, screen->content_size);
SDL_SetWindowSize(screen->window, optimal_size.width, optimal_size.height);
@ -517,41 +556,31 @@ screen_resize_to_pixel_perfect(struct screen *screen) {
content_size.height);
}
static inline bool
is_fullscreen(const struct screen *screen) {
uint32_t flags = SDL_GetWindowFlags(screen->window);
return !!(flags & (SDL_WINDOW_FULLSCREEN | SDL_WINDOW_FULLSCREEN_DESKTOP));
}
static void
update_fullscreen_state(struct screen *screen) {
// There is no SDL event to detect fullscreen changes, so store the
// state in a field and compare on every "size changed" event
bool fullscreen = is_fullscreen(screen);
if (fullscreen != screen->fullscreen) {
// Fullscreen state have changed
screen->fullscreen = fullscreen;
if (!fullscreen && !screen->maximized) {
apply_pending_resize(screen);
}
}
}
void
screen_handle_window_event(struct screen *screen,
const SDL_WindowEvent *event) {
switch (event->event) {
case SDL_WINDOWEVENT_EXPOSED:
screen_render(screen);
LOGI("EXPOSED");
screen_render(screen, true);
break;
case SDL_WINDOWEVENT_SIZE_CHANGED:
update_fullscreen_state(screen);
screen_render(screen);
LOGI("SIZE_CHANGED %dx%d", event->data1, event->data2);
screen_render(screen, true);
break;
case SDL_WINDOWEVENT_MAXIMIZED:
LOGI("MAXIMIZED");
screen->maximized = true;
break;
case SDL_WINDOWEVENT_RESTORED:
LOGI("RESTORED");
if (screen->fullscreen) {
// On Windows, in maximized+fullscreen, disabling fullscreen
// mode unexpectedly triggers the "restored" then "maximized"
// events, leaving the window in a weird state (maximized
// according to the events, but not maximized visually).
break;
}
screen->maximized = false;
apply_pending_resize(screen);
break;
@ -565,6 +594,13 @@ screen_convert_to_frame_coords(struct screen *screen, int32_t x, int32_t y) {
int32_t w = screen->content_size.width;
int32_t h = screen->content_size.height;
screen_hidpi_scale_coords(screen, &x, &y);
x = (int64_t) (x - screen->rect.x) * w / screen->rect.w;
y = (int64_t) (y - screen->rect.y) * h / screen->rect.h;
// rotate
struct point result;
switch (rotation) {
case 0:
@ -587,3 +623,15 @@ screen_convert_to_frame_coords(struct screen *screen, int32_t x, int32_t y) {
}
return result;
}
void
screen_hidpi_scale_coords(struct screen *screen, int32_t *x, int32_t *y) {
// take the HiDPI scaling (dw/ww and dh/wh) into account
int ww, wh, dw, dh;
SDL_GetWindowSize(screen->window, &ww, &wh);
SDL_GL_GetDrawableSize(screen->window, &dw, &dh);
// scale for HiDPI (64 bits for intermediate multiplications)
*x = (int64_t) *x * dw / ww;
*y = (int64_t) *y * dh / wh;
}

View File

@ -29,6 +29,8 @@ struct screen {
// client rotation: 0, 1, 2 or 3 (x90 degrees counterclockwise)
unsigned rotation;
// rectangle of the content (excluding black borders)
struct SDL_Rect rect;
bool has_frame;
bool fullscreen;
bool maximized;
@ -56,6 +58,12 @@ struct screen {
.height = 0, \
}, \
.rotation = 0, \
.rect = { \
.x = 0, \
.y = 0, \
.w = 0, \
.h = 0, \
}, \
.has_frame = false, \
.fullscreen = false, \
.maximized = false, \
@ -89,8 +97,11 @@ bool
screen_update_frame(struct screen *screen, struct video_buffer *vb);
// render the texture to the renderer
//
// Set the update_content_rect flag if the window or content size may have
// changed, so that the content rectangle is recomputed
void
screen_render(struct screen *screen);
screen_render(struct screen *screen, bool update_content_rect);
// switch the fullscreen mode
void
@ -117,4 +128,11 @@ screen_handle_window_event(struct screen *screen, const SDL_WindowEvent *event);
struct point
screen_convert_to_frame_coords(struct screen *screen, int32_t x, int32_t y);
// Convert coordinates from window to drawable.
// Events are expressed in window coordinates, but content is expressed in
// drawable coordinates. They are the same if HiDPI scaling is 1, but differ
// otherwise.
void
screen_hidpi_scale_coords(struct screen *screen, int32_t *x, int32_t *y);
#endif