Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion client/linux/my_application.cc
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,9 @@ static void my_application_activate(GApplication* application) {
}

gtk_window_set_default_size(window, 1280, 720);
gtk_widget_show(GTK_WIDGET(window));
// Realize the native window without showing it so Dart can decide when to
// display and position the window.
gtk_widget_realize(GTK_WIDGET(window));

g_autoptr(FlDartProject) project = fl_dart_project_new();
fl_dart_project_set_dart_entrypoint_arguments(project, self->dart_entrypoint_arguments);
Expand Down
10 changes: 8 additions & 2 deletions client/windows/runner/flutter_window.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include <optional>

#include "flutter/generated_plugin_registrant.h"
#include "utils.h"

FlutterWindow::FlutterWindow(const flutter::DartProject& project)
: project_(project) {}
Expand All @@ -27,8 +28,13 @@ bool FlutterWindow::OnCreate() {
RegisterPlugins(flutter_controller_->engine());
SetChildContent(flutter_controller_->view()->GetNativeWindow());

flutter_controller_->engine()->SetNextFrameCallback([&]() {
this->Show();
const bool hide_window_on_start =
HasEnvironmentVariable(L"FLET_HIDE_WINDOW_ON_START");
flutter_controller_->engine()->SetNextFrameCallback([this,
hide_window_on_start]() {
if (!hide_window_on_start) {
Show();
}
});

// Flutter can complete the first frame before the "show window" callback is
Expand Down
6 changes: 6 additions & 0 deletions client/windows/runner/utils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,12 @@ std::vector<std::string> GetCommandLineArguments() {
return command_line_arguments;
}

bool HasEnvironmentVariable(const wchar_t* name) {
::SetLastError(ERROR_SUCCESS);
const DWORD value_length = ::GetEnvironmentVariableW(name, nullptr, 0);
return value_length > 0 || ::GetLastError() != ERROR_ENVVAR_NOT_FOUND;
}

std::string Utf8FromUtf16(const wchar_t* utf16_string) {
if (utf16_string == nullptr) {
return std::string();
Expand Down
4 changes: 4 additions & 0 deletions client/windows/runner/utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,8 @@ std::string Utf8FromUtf16(const wchar_t* utf16_string);
// encoded in UTF-8. Returns an empty std::vector<std::string> on failure.
std::vector<std::string> GetCommandLineArguments();

// Returns true when the given environment variable exists, even if it is set
// to an empty value.
bool HasEnvironmentVariable(const wchar_t* name);

#endif // RUNNER_UTILS_H_
160 changes: 122 additions & 38 deletions packages/flet/lib/src/services/window.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import 'package:window_manager/window_manager.dart';

import '../flet_backend.dart';
import '../flet_service.dart';
import '../models/window_state.dart';
import '../utils/alignment.dart';
import '../utils/colors.dart';
import '../utils/desktop.dart';
Expand Down Expand Up @@ -51,6 +52,10 @@ class WindowService extends FletService with WindowListener {
bool? _skipTaskBar;
double? _progressBar;
bool? _ignoreMouseEvents;
Alignment? _deferredAlignmentOnShow;
bool _deferredCenterOnShow = false;
bool _isClosing = false;
bool _applyingDeferredPlacement = false;
bool _listenersAttached = false;

WindowService({required super.control});
Expand All @@ -68,22 +73,7 @@ class WindowService extends FletService with WindowListener {
Future<void> _initWindowState() async {
try {
final windowState = await getWindowState();
_width = windowState.width;
_height = windowState.height;
_top = windowState.top;
_left = windowState.left;
_opacity = windowState.opacity;
_minimizable = windowState.minimizable;
_maximizable = windowState.maximizable;
_fullScreen = windowState.fullScreen;
_resizable = windowState.resizable;
_alwaysOnTop = windowState.alwaysOnTop;
_preventClose = windowState.preventClose;
_minimized = windowState.minimized;
_maximized = windowState.maximized;
_visible = windowState.visible;
_focused = windowState.focused;
_skipTaskBar = windowState.skipTaskBar;
_cacheWindowState(windowState);

if (!_listenersAttached) {
windowManager.addListener(this);
Expand Down Expand Up @@ -130,6 +120,77 @@ class WindowService extends FletService with WindowListener {
});
}

bool _shouldDeferPlacementUntilShow() {
return isLinuxDesktop() && (_visible == null || _visible == false);
}

void _cacheWindowState(WindowState state) {
_width = state.width;
_height = state.height;
_top = state.top;
_left = state.left;
_opacity = state.opacity;
_minimizable = state.minimizable;
_maximizable = state.maximizable;
_fullScreen = state.fullScreen;
_resizable = state.resizable;
_alwaysOnTop = state.alwaysOnTop;
_preventClose = state.preventClose;
_minimized = state.minimized;
_maximized = state.maximized;
_visible = state.visible;
_focused = state.focused;
_skipTaskBar = state.skipTaskBar;
}

WindowState _snapshotWindowState({bool? visible, bool? focused}) {
return WindowState(
maximized: _maximized ?? false,
minimized: _minimized ?? false,
fullScreen: _fullScreen ?? false,
alwaysOnTop: _alwaysOnTop ?? false,
focused: focused ?? _focused ?? false,
visible: visible ?? _visible ?? false,
minimizable: _minimizable ?? true,
maximizable: _maximizable ?? true,
resizable: _resizable ?? true,
preventClose: _preventClose ?? false,
skipTaskBar: _skipTaskBar ?? false,
width: _width ?? 0,
height: _height ?? 0,
top: _top ?? 0,
left: _left ?? 0,
opacity: _opacity ?? 1,
);
}

Future<void> _applyDeferredPlacementAfterShow() async {
if (!isLinuxDesktop() ||
_applyingDeferredPlacement ||
(_deferredAlignmentOnShow == null && !_deferredCenterOnShow)) {
return;
}

_applyingDeferredPlacement = true;
final deferredAlignment = _deferredAlignmentOnShow;
final deferredCenter = _deferredCenterOnShow;
_deferredAlignmentOnShow = null;
_deferredCenterOnShow = false;

try {
if (deferredAlignment != null) {
await setWindowAlignment(deferredAlignment, false);
}
if (deferredCenter) {
await centerWindow();
}
} catch (e) {
debugPrint("Error applying deferred window placement: $e");
} finally {
_applyingDeferredPlacement = false;
}
}

Future<void> _updateWindow(FletBackend backend) async {
if (!isDesktopPlatform()) {
return;
Expand Down Expand Up @@ -264,7 +325,12 @@ class WindowService extends FletService with WindowListener {
}

if (alignment != null && alignment != _alignment) {
await setWindowAlignment(alignment);
if (_shouldDeferPlacementUntilShow()) {
_deferredAlignmentOnShow = alignment;
} else {
await setWindowAlignment(alignment);
_deferredAlignmentOnShow = null;
}
_alignment = alignment;
}

Expand Down Expand Up @@ -386,12 +452,18 @@ class WindowService extends FletService with WindowListener {
break;
case "center":
await _pendingWindowUpdate;
await centerWindow();
if (_shouldDeferPlacementUntilShow()) {
_deferredAlignmentOnShow = null;
_deferredCenterOnShow = true;
} else {
await centerWindow();
}
break;
case "close":
await closeWindow();
break;
case "destroy":
_isClosing = true;
await destroyWindow();
break;
case "start_dragging":
Expand Down Expand Up @@ -427,25 +499,37 @@ class WindowService extends FletService with WindowListener {
if (["resize", "resized", "move"].contains(eventName)) {
return;
}
getWindowState().then((state) {
_width = state.width;
_height = state.height;
_top = state.top;
_left = state.left;
_opacity = state.opacity;
_minimized = state.minimized;
_maximized = state.maximized;
_minimizable = state.minimizable;
_maximizable = state.maximizable;
_fullScreen = state.fullScreen;
_resizable = state.resizable;
_alwaysOnTop = state.alwaysOnTop;
_preventClose = state.preventClose;
_visible = state.visible;
_focused = state.focused;
_skipTaskBar = state.skipTaskBar;

control.backend.onWindowEvent(eventName, state);
});
if (eventName == "close") {
_isClosing = !(_preventClose ?? false);
control.backend.onWindowEvent(
eventName, _snapshotWindowState(focused: false));
return;
}
if (eventName == "hide") {
_visible = false;
_focused = false;
control.backend.onWindowEvent(
eventName, _snapshotWindowState(visible: false, focused: false));
return;
}
if (_isClosing) {
control.backend.onWindowEvent(eventName, _snapshotWindowState());
return;
}

() async {
try {
if (eventName == "show") {
_visible = true;
await _applyDeferredPlacementAfterShow();
}

final state = await getWindowState();
_cacheWindowState(state);
control.backend.onWindowEvent(eventName, state);
} catch (e) {
debugPrint("Error handling window event $eventName: $e");
}
}();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include <optional>

#include "flutter/generated_plugin_registrant.h"
#include "utils.h"

FlutterWindow::FlutterWindow(const flutter::DartProject& project)
: project_(project) {}
Expand All @@ -27,8 +28,13 @@ bool FlutterWindow::OnCreate() {
RegisterPlugins(flutter_controller_->engine());
SetChildContent(flutter_controller_->view()->GetNativeWindow());

flutter_controller_->engine()->SetNextFrameCallback([&]() {
this->Show();
const bool hide_window_on_start =
HasEnvironmentVariable(L"FLET_HIDE_WINDOW_ON_START");
flutter_controller_->engine()->SetNextFrameCallback([this,
hide_window_on_start]() {
if (!hide_window_on_start) {
Show();
}
});

// Flutter can complete the first frame before the "show window" callback is
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,12 @@ std::vector<std::string> GetCommandLineArguments() {
return command_line_arguments;
}

bool HasEnvironmentVariable(const wchar_t* name) {
::SetLastError(ERROR_SUCCESS);
const DWORD value_length = ::GetEnvironmentVariableW(name, nullptr, 0);
return value_length > 0 || ::GetLastError() != ERROR_ENVVAR_NOT_FOUND;
}

std::string Utf8FromUtf16(const wchar_t* utf16_string) {
if (utf16_string == nullptr) {
return std::string();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,8 @@ std::string Utf8FromUtf16(const wchar_t* utf16_string);
// encoded in UTF-8. Returns an empty std::vector<std::string> on failure.
std::vector<std::string> GetCommandLineArguments();

// Returns true when the given environment variable exists, even if it is set
// to an empty value.
bool HasEnvironmentVariable(const wchar_t* name);

#endif // RUNNER_UTILS_H_
Loading