Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add API for minimize/maximize on Window component #4581

Merged
merged 17 commits into from Feb 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
12 changes: 12 additions & 0 deletions api/cpp/include/slint-platform.h
Expand Up @@ -249,6 +249,18 @@ class WindowAdapter
return cbindgen_private::slint_window_properties_get_fullscreen(inner());
}

/// Returns true if the window should be minimized; false otherwise
bool minimized() const
{
return cbindgen_private::slint_window_properties_get_minimized(inner());
}

/// Returns true if the window should be maximized; false otherwise
bool maximized() const
{
return cbindgen_private::slint_window_properties_get_maximized(inner());
}

/// This struct describes the layout constraints of a window.
///
/// It is the return value of WindowProperties::layout_constraints().
Expand Down
12 changes: 11 additions & 1 deletion api/cpp/platform.rs
Expand Up @@ -113,7 +113,17 @@ pub extern "C" fn slint_window_properties_get_background(

#[no_mangle]
pub extern "C" fn slint_window_properties_get_fullscreen(wp: &WindowProperties) -> bool {
wp.fullscreen()
wp.is_fullscreen()
}

#[no_mangle]
pub extern "C" fn slint_window_properties_get_minimized(wp: &WindowProperties) -> bool {
wp.is_minimized()
}

#[no_mangle]
pub extern "C" fn slint_window_properties_get_maximized(wp: &WindowProperties) -> bool {
wp.is_maximized()
}

#[repr(C)]
Expand Down
12 changes: 9 additions & 3 deletions api/node/index.ts
Expand Up @@ -61,6 +61,15 @@ export interface Window {
/** Gets or sets the physical size of the window on the screen, */
physicalSize: Size;

/** Gets or sets the window's fullscreen state **/
fullscreen: boolean;

/** Gets or sets the window's maximized state **/
maximized: boolean;

/** Gets or sets teh window's minimized state **/
minimized: boolean;

/**
* Returns the visibility state of the window. This function can return false even if you previously called show()
* on it, for example if the user minimized the window.
Expand All @@ -78,9 +87,6 @@ export interface Window {

/** Issues a request to the windowing system to re-render the contents of the window. */
requestRedraw(): void;

/** Set or unset the window to display fullscreen. */
set fullscreen(enable: boolean);
}

/**
Expand Down
30 changes: 30 additions & 0 deletions api/node/src/interpreter/window.rs
Expand Up @@ -124,9 +124,39 @@ impl JsWindow {
self.inner.request_redraw();
}

/// Returns if the window is currently fullscreen
#[napi(getter)]
pub fn get_fullscreen(&self) -> bool {
self.inner.window().is_fullscreen()
}

/// Set or unset the window to display fullscreen.
#[napi(setter)]
pub fn set_fullscreen(&self, enable: bool) {
self.inner.window().set_fullscreen(enable)
}

/// Returns if the window is currently maximized
#[napi(getter)]
pub fn get_maximized(&self) -> bool {
self.inner.window().is_maximized()
}

/// Maximize or unmaximize the window.
#[napi(setter)]
pub fn set_maximized(&self, maximized: bool) {
self.inner.window().set_maximized(maximized)
}

/// Returns if the window is currently minimized
#[napi(getter)]
pub fn get_minimized(&self) -> bool {
self.inner.window().is_minimized()
}

/// Minimize or unminimze the window.
#[napi(setter)]
pub fn set_minimized(&self, minimized: bool) {
self.inner.window().set_minimized(minimized)
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

change in this files need to be reflected ion index.ts, i think

}
2 changes: 1 addition & 1 deletion internal/backends/android-activity/androidwindowadapter.rs
Expand Up @@ -51,7 +51,7 @@ impl WindowAdapter for AndroidWindowAdapter {
}

fn update_window_properties(&self, properties: WindowProperties<'_>) {
let f = properties.fullscreen();
let f = properties.is_fullscreen();
if self.fullscreen.replace(f) != f {
self.resize();
}
Expand Down
86 changes: 75 additions & 11 deletions internal/backends/qt/qt_window.rs
Expand Up @@ -113,7 +113,11 @@ cpp! {{
void resizeEvent(QResizeEvent *event) override {
if (!rust_window)
return;
QSize size = event->size();

// On windows, the size in the event is not reliable during
// fullscreen changes. Querying the widget itself seems to work
// better, see: https://stackoverflow.com/questions/52157587/why-qresizeevent-qwidgetsize-gives-different-when-fullscreen
QSize size = this->size();
rust!(Slint_resizeEvent [rust_window: &QtWindow as "void*", size: qttypes::QSize as "QSize"] {
rust_window.resize_event(size)
});
Expand Down Expand Up @@ -231,6 +235,7 @@ cpp! {{
void changeEvent(QEvent *event) override {
if (!rust_window)
return;

if (event->type() == QEvent::ActivationChange) {
bool active = isActiveWindow();
rust!(Slint_updateWindowActivation [rust_window: &QtWindow as "void*", active: bool as "bool"] {
Expand All @@ -244,6 +249,18 @@ cpp! {{
}
});
}

// Entering fullscreen, maximizing or minimizing the window will
// trigger a change event. We need to update the internal window
// state to match the actual window state.
if (event->type() == QEvent::WindowStateChange)
{
rust!(Slint_syncWindowState [rust_window: &QtWindow as "void*"]{
rust_window.window_state_event();
});
}


QWidget::changeEvent(event);
}

Expand Down Expand Up @@ -1554,6 +1571,40 @@ impl QtWindow {
fn close_popup_on_click(&self) -> bool {
WindowInner::from_pub(&self.window).close_popup_on_click()
}

fn window_state_event(&self) {
let widget_ptr = self.widget_ptr();

// This function is called from the changeEvent slot which triggers whenever
// one of these properties changes. To prevent recursive call issues (e.g.,
// set_fullscreen -> update_window_properties -> changeEvent ->
// window_state_event -> set_fullscreen), we avoid resetting the internal state
// when it already matches the Qt state.

let minimized = cpp! { unsafe [widget_ptr as "QWidget*"] -> bool as "bool" {
return widget_ptr->isMinimized();
}};

if minimized != self.window().is_minimized() {
self.window().set_minimized(minimized);
}

let maximized = cpp! { unsafe [widget_ptr as "QWidget*"] -> bool as "bool" {
return widget_ptr->isMaximized();
}};

if maximized != self.window().is_maximized() {
self.window().set_maximized(maximized);
}

let fullscreen = cpp! { unsafe [widget_ptr as "QWidget*"] -> bool as "bool" {
return widget_ptr->isFullScreen();
}};

if fullscreen != self.window().is_fullscreen() {
self.window().set_fullscreen(fullscreen);
}
}
}

impl WindowAdapter for QtWindow {
Expand Down Expand Up @@ -1629,6 +1680,7 @@ impl WindowAdapter for QtWindow {
let logical_size = size.to_logical(self.window().scale_factor());
let widget_ptr = self.widget_ptr();
let sz: qttypes::QSize = into_qsize(logical_size);

// Qt uses logical units!
cpp! {unsafe [widget_ptr as "QWidget*", sz as "QSize"] {
widget_ptr->resize(sz);
Expand Down Expand Up @@ -1664,6 +1716,7 @@ impl WindowAdapter for QtWindow {
width: window_item.width().get().ceil() as _,
height: window_item.height().get().ceil() as _,
};

if size.width == 0 || size.height == 0 {
let existing_size = cpp!(unsafe [widget_ptr as "QWidget*"] -> qttypes::QSize as "QSize" {
return widget_ptr->size();
Expand Down Expand Up @@ -1692,25 +1745,36 @@ impl WindowAdapter for QtWindow {
}
};

let fullscreen: bool = properties.fullscreen();
let fullscreen: bool = properties.is_fullscreen();
let minimized: bool = properties.is_minimized();
let maximized: bool = properties.is_maximized();

cpp! {unsafe [widget_ptr as "QWidget*", title as "QString", size as "QSize", background as "QBrush", no_frame as "bool", always_on_top as "bool",
fullscreen as "bool"] {
fullscreen as "bool", minimized as "bool", maximized as "bool"] {

if (size != widget_ptr->size()) {
widget_ptr->resize(size.expandedTo({1, 1}));
}

widget_ptr->setWindowFlag(Qt::FramelessWindowHint, no_frame);
widget_ptr->setWindowFlag(Qt::WindowStaysOnTopHint, always_on_top);

{
// Depending on the request, we either set or clear the fullscreen bits.
// Depending on the request, we either set or clear the bits.
// See also: https://doc.qt.io/qt-6/qt.html#WindowState-enum
const auto state = widget_ptr->windowState();
if (fullscreen) {
widget_ptr->setWindowState(state | Qt::WindowFullScreen);
} else {
widget_ptr->setWindowState(state & ~Qt::WindowFullScreen);
auto state = widget_ptr->windowState();

if (fullscreen != widget_ptr->isFullScreen()) {
state = state ^ Qt::WindowFullScreen;
}
if (minimized != widget_ptr->isMinimized()) {
state = state ^ Qt::WindowMinimized;
}
if (maximized != widget_ptr->isMaximized()) {
state = state ^ Qt::WindowMaximized;
}

widget_ptr->setWindowState(state);
}

widget_ptr->setWindowTitle(title);
Expand Down Expand Up @@ -1738,10 +1802,10 @@ impl WindowAdapter for QtWindow {
into_qsize,
);

let widget_size_max: u32 = 16_777_215;
const WIDGET_SIZE_MAX: u32 = 16_777_215;

let max_size: qttypes::QSize = constraints.max.map_or_else(
|| qttypes::QSize { width: widget_size_max, height: widget_size_max },
|| qttypes::QSize { width: WIDGET_SIZE_MAX, height: WIDGET_SIZE_MAX },
into_qsize,
);

Expand Down
11 changes: 10 additions & 1 deletion internal/backends/winit/event_loop.rs
Expand Up @@ -251,9 +251,18 @@ impl EventLoopState {
fn process_window_event(&mut self, window: Rc<WinitWindowAdapter>, event: WindowEvent) {
let runtime_window = WindowInner::from_pub(window.window());
match event {
WindowEvent::RedrawRequested => self.loop_error = window.draw().err(),
WindowEvent::RedrawRequested => {
self.loop_error = window.draw().err();
}
WindowEvent::Resized(size) => {
self.loop_error = window.resize_event(size).err();

// Entering fullscreen, maximizing or minimizing the window will
// trigger a resize event. We need to update the internal window
// state to match the actual window state. We simulate a "window
// state event" since there is not an official event for it yet.
// See: https://github.com/rust-windowing/winit/issues/2334
window.window_state_event();
}
WindowEvent::CloseRequested => {
window.window().dispatch_event(corelib::platform::WindowEvent::CloseRequested);
Expand Down
44 changes: 40 additions & 4 deletions internal/backends/winit/winitwindowadapter.rs
Expand Up @@ -305,6 +305,34 @@ impl WinitWindowAdapter {
.as_ref()
.set(dark_mode)
}

pub fn window_state_event(&self) {
if let Some(minimized) = self.winit_window.is_minimized() {
if minimized != self.window().is_minimized() {
self.window().set_minimized(minimized);
}
}

// The method winit::Window::is_maximized returns false when the window
// is minimized, even if it was previously maximized. We have to ensure
// that we only update the internal maximized state when the window is
// not minimized. Otherwise, the window would be restored in a
// non-maximized state even if it was maximized before being minimized.
if !self.window().is_minimized() {
let maximized = self.winit_window.is_maximized();
if maximized != self.window().is_maximized() {
self.window().set_maximized(maximized);
}
}

// NOTE: Fullscreen overrides maximized so if both are true then the
// window will remain in fullscreen. Fullscreen must be false to switch
// to maximized.
let fullscreen = self.winit_window.fullscreen().is_some();
if fullscreen != self.window().is_fullscreen() {
self.window().set_fullscreen(fullscreen);
}
}
}

impl WindowAdapter for WinitWindowAdapter {
Expand Down Expand Up @@ -460,6 +488,13 @@ impl WindowAdapter for WinitWindowAdapter {
winit_window.set_window_level(new_window_level);
}

// Only set the window as maximized if the window is not already fullscreen
if winit_window.fullscreen().is_none() {
winit_window.set_maximized(properties.is_maximized());
}

winit_window.set_minimized(properties.is_minimized());

if width <= 0. || height <= 0. {
must_resize = true;

Expand Down Expand Up @@ -499,7 +534,7 @@ impl WindowAdapter for WinitWindowAdapter {
}

self.with_window_handle(&mut |winit_window| {
if properties.fullscreen() {
if properties.is_fullscreen() {
if winit_window.fullscreen().is_none() {
winit_window.set_fullscreen(Some(winit::window::Fullscreen::Borderless(None)));
}
Expand All @@ -509,9 +544,10 @@ impl WindowAdapter for WinitWindowAdapter {
}
}

// If we're in fullscreen state, don't try to resize the window but maintain the surface
// size we've been assigned to from the windowing system. Weston/Wayland don't like it
// when we create a surface that's bigger than the screen due to constraints (#532).
// If we're in fullscreen, don't try to resize the window but
// maintain the surface size we've been assigned to from the
// windowing system. Weston/Wayland don't like it when we create a
// surface that's bigger than the screen due to constraints (#532).
if winit_window.fullscreen().is_some() {
return;
}
Expand Down
25 changes: 25 additions & 0 deletions internal/core/api.rs
Expand Up @@ -446,11 +446,36 @@ impl Window {
crate::window::WindowAdapter::set_size(&*self.0.window_adapter(), size);
}

/// Returns if the window is currently fullscreen
pub fn is_fullscreen(&self) -> bool {
self.0.is_fullscreen()
}

/// Set or unset the window to display fullscreen.
pub fn set_fullscreen(&self, fullscreen: bool) {
self.0.set_fullscreen(fullscreen);
}

/// Returns if the window is currently maximized
pub fn is_maximized(&self) -> bool {
self.0.is_maximized()
}

/// Maximize or unmaximize the window.
pub fn set_maximized(&self, maximized: bool) {
self.0.set_maximized(maximized);
}

/// Returns if the window is currently minimized
pub fn is_minimized(&self) -> bool {
self.0.is_minimized()
}

/// Minimize or unminimze the window.
pub fn set_minimized(&self, minimized: bool) {
self.0.set_minimized(minimized);
}

/// Dispatch a window event to the scene.
///
/// Use this when you're implementing your own backend and want to forward user input events.
Expand Down