Skip to content

Commit 0fe23c2

Browse files
authored
Merge pull request flutter#10 from canonical/multi-window-dialogs
Add support for dialogs on Windows
2 parents 3142352 + 571126b commit 0fe23c2

File tree

4 files changed

+285
-86
lines changed

4 files changed

+285
-86
lines changed

shell/platform/windows/client_wrapper/flutter_window_controller.cc

+147-57
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,26 @@
88
#include <dwmapi.h>
99

1010
namespace {
11-
auto* const CHANNEL{"flutter/windowing"};
12-
auto const base_dpi{96.0};
11+
auto const* const kChannel{"flutter/windowing"};
12+
auto const kBaseDpi{static_cast<double>(USER_DEFAULT_SCREEN_DPI)};
13+
14+
// Encodes the attributes of a FlutterWindowCreationResult into an EncodableMap
15+
// wrapped in an EncodableValue.
16+
flutter::EncodableValue encodeWindowCreationResult(
17+
flutter::FlutterWindowCreationResult const& result) {
18+
return flutter::EncodableValue(flutter::EncodableMap{
19+
{flutter::EncodableValue("viewId"),
20+
flutter::EncodableValue(result.view_id)},
21+
{flutter::EncodableValue("parentViewId"),
22+
result.parent_id ? flutter::EncodableValue(*result.parent_id)
23+
: flutter::EncodableValue()},
24+
{flutter::EncodableValue("archetype"),
25+
flutter::EncodableValue(static_cast<int>(result.archetype))},
26+
{flutter::EncodableValue("width"),
27+
flutter::EncodableValue(result.size.width)},
28+
{flutter::EncodableValue("height"),
29+
flutter::EncodableValue((result.size.height))}});
30+
}
1331

1432
// Returns the origin point that will center a window of size 'size' within the
1533
// client area of the window identified by 'handle'.
@@ -19,7 +37,7 @@ auto calculateCenteredOrigin(flutter::Win32Window::Size size,
1937
POINT const target_point{frame.left, frame.top};
2038
auto* const monitor{
2139
MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST)};
22-
auto const dpr{FlutterDesktopGetDpiForMonitor(monitor) / base_dpi};
40+
auto const dpr{FlutterDesktopGetDpiForMonitor(monitor) / kBaseDpi};
2341
auto const centered_x{(frame.left + frame.right - size.width * dpr) / 2.0};
2442
auto const centered_y{(frame.top + frame.bottom - size.height * dpr) / 2.0};
2543
return {static_cast<unsigned int>(centered_x / dpr),
@@ -35,7 +53,7 @@ applyPositioner(flutter::FlutterWindowPositioner const& positioner,
3553
auto const& windows{flutter::FlutterWindowController::instance().windows()};
3654
auto const& parent_window{windows.at(parent_view_id)};
3755
auto const& parent_hwnd{parent_window->GetHandle()};
38-
auto const dpr{FlutterDesktopGetDpiForHWND(parent_hwnd) / base_dpi};
56+
auto const dpr{FlutterDesktopGetDpiForHWND(parent_hwnd) / kBaseDpi};
3957
auto const monitor_rect{[](HWND hwnd) -> RECT {
4058
auto* monitor{MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST)};
4159
MONITORINFO mi;
@@ -347,30 +365,9 @@ void handleCreateRegularWindow(
347365
static_cast<unsigned int>(*width),
348366
static_cast<unsigned int>(*height)};
349367

350-
// Window will be centered within the 'main window'
351-
auto const origin{[size]() -> flutter::Win32Window::Point {
352-
auto const& windows{
353-
flutter::FlutterWindowController::instance().windows()};
354-
return windows.find(0) != windows.end()
355-
? calculateCenteredOrigin(size, windows.at(0)->GetHandle())
356-
: flutter::Win32Window::Point{0, 0};
357-
}()};
358-
359-
if (auto const data{
360-
flutter::FlutterWindowController::instance()
361-
.createRegularWindow(L"regular", origin, size)}) {
362-
result->Success(flutter::EncodableValue(flutter::EncodableMap{
363-
{flutter::EncodableValue("viewId"),
364-
flutter::EncodableValue(data->view_id)},
365-
{flutter::EncodableValue("parentViewId"),
366-
data->parent_id ? flutter::EncodableValue(*data->parent_id)
367-
: flutter::EncodableValue()},
368-
{flutter::EncodableValue("archetype"),
369-
flutter::EncodableValue(static_cast<int>(data->archetype))},
370-
{flutter::EncodableValue("width"),
371-
flutter::EncodableValue(data->size.width)},
372-
{flutter::EncodableValue("height"),
373-
flutter::EncodableValue((data->size.height))}}));
368+
if (auto const data{flutter::FlutterWindowController::instance()
369+
.createRegularWindow(L"regular", size)}) {
370+
result->Success(encodeWindowCreationResult(data.value()));
374371
} else {
375372
result->Error("UNAVAILABLE", "Can't create window.");
376373
}
@@ -388,6 +385,68 @@ void handleCreateRegularWindow(
388385
}
389386
}
390387

388+
void handleCreateDialogWindow(
389+
flutter::MethodCall<> const& call,
390+
std::unique_ptr<flutter::MethodResult<>>& result) {
391+
auto const* const arguments{call.arguments()};
392+
if (auto const* const map{std::get_if<flutter::EncodableMap>(arguments)}) {
393+
auto const parent_it{map->find(flutter::EncodableValue("parent"))};
394+
auto const size_it{map->find(flutter::EncodableValue("size"))};
395+
396+
if (parent_it != map->end() && size_it != map->end()) {
397+
auto const* const parent{std::get_if<int>(&parent_it->second)};
398+
if (!parent) {
399+
result->Error("INVALID_VALUE",
400+
"Value for 'parent' must be of type int.");
401+
return;
402+
}
403+
404+
auto const* const size_list{
405+
std::get_if<std::vector<flutter::EncodableValue>>(&size_it->second)};
406+
if (size_list->size() != 2 ||
407+
!std::holds_alternative<int>(size_list->at(0)) ||
408+
!std::holds_alternative<int>(size_list->at(1))) {
409+
result->Error("INVALID_VALUE",
410+
"Values for 'size' must be of type int.");
411+
return;
412+
}
413+
auto const width{std::get<int>(size_list->at(0))};
414+
auto const height{std::get<int>(size_list->at(1))};
415+
flutter::Win32Window::Size const size{static_cast<unsigned int>(width),
416+
static_cast<unsigned int>(height)};
417+
418+
auto const origin{
419+
[&size, &parent]() -> std::optional<flutter::Win32Window::Point> {
420+
if (parent) {
421+
auto const& windows{
422+
flutter::FlutterWindowController::instance().windows()};
423+
if (windows.find(*parent) != windows.end()) {
424+
return calculateCenteredOrigin(
425+
size, windows.at(*parent)->GetHandle());
426+
}
427+
}
428+
return std::nullopt;
429+
}()};
430+
431+
if (auto const data{
432+
flutter::FlutterWindowController::instance().createDialogWindow(
433+
L"dialog", size, origin,
434+
*parent >= 0 ? std::optional<flutter::FlutterViewId>{*parent}
435+
: std::nullopt)}) {
436+
result->Success(encodeWindowCreationResult(data.value()));
437+
} else {
438+
result->Error("UNAVAILABLE", "Can't create window.");
439+
}
440+
} else {
441+
result->Error(
442+
"INVALID_VALUE",
443+
"Map does not contain all required keys: {'parent', 'size'}.");
444+
}
445+
} else {
446+
result->Error("INVALID_VALUE", "Value argument is not a map.");
447+
}
448+
}
449+
391450
void handleCreatePopupWindow(flutter::MethodCall<> const& call,
392451
std::unique_ptr<flutter::MethodResult<>>& result) {
393452
auto const* const arguments{call.arguments()};
@@ -540,18 +599,7 @@ void handleCreatePopupWindow(flutter::MethodCall<> const& call,
540599
if (auto const data{
541600
flutter::FlutterWindowController::instance().createPopupWindow(
542601
L"popup", origin, new_size, *parent)}) {
543-
result->Success(flutter::EncodableValue(flutter::EncodableMap{
544-
{flutter::EncodableValue("viewId"),
545-
flutter::EncodableValue(data->view_id)},
546-
{flutter::EncodableValue("parentViewId"),
547-
data->parent_id ? flutter::EncodableValue(*data->parent_id)
548-
: flutter::EncodableValue()},
549-
{flutter::EncodableValue("archetype"),
550-
flutter::EncodableValue(static_cast<int>(data->archetype))},
551-
{flutter::EncodableValue("width"),
552-
flutter::EncodableValue(data->size.width)},
553-
{flutter::EncodableValue("height"),
554-
flutter::EncodableValue((data->size.height))}}));
602+
result->Success(encodeWindowCreationResult(data.value()));
555603
} else {
556604
result->Error("UNAVAILABLE", "Can't create window.");
557605
}
@@ -591,12 +639,14 @@ namespace flutter {
591639
void FlutterWindowController::initializeChannel() {
592640
if (!channel_) {
593641
channel_ = std::make_unique<MethodChannel<>>(
594-
engine_->messenger(), CHANNEL, &StandardMethodCodec::GetInstance());
642+
engine_->messenger(), kChannel, &StandardMethodCodec::GetInstance());
595643
channel_->SetMethodCallHandler(
596644
[this](MethodCall<> const& call,
597645
std::unique_ptr<MethodResult<>> result) {
598646
if (call.method_name() == "createRegularWindow") {
599647
handleCreateRegularWindow(call, result);
648+
} else if (call.method_name() == "createDialogWindow") {
649+
handleCreateDialogWindow(call, result);
600650
} else if (call.method_name() == "createPopupWindow") {
601651
handleCreatePopupWindow(call, result);
602652
} else if (call.method_name() == "destroyWindow") {
@@ -614,22 +664,19 @@ void FlutterWindowController::setEngine(std::shared_ptr<FlutterEngine> engine) {
614664
initializeChannel();
615665
}
616666

617-
auto FlutterWindowController::createRegularWindow(
618-
std::wstring const& title,
619-
Win32Window::Point const& origin,
620-
Win32Window::Size const& size)
667+
auto FlutterWindowController::createRegularWindow(std::wstring const& title,
668+
Win32Window::Size const& size)
621669
-> std::optional<FlutterWindowCreationResult> {
622670
std::unique_lock lock(mutex_);
623671
if (!engine_) {
624-
std::cerr << "Cannot create window without an engine.\n";
672+
std::cerr << "Cannot create regular window without an engine.\n";
625673
return std::nullopt;
626674
}
627675
auto window{std::make_unique<FlutterWin32Window>(engine_)};
628676

629677
lock.unlock();
630-
if (!window->Create(title, origin, size, FlutterWindowArchetype::regular,
631-
nullptr)) {
632-
std::cerr << "Cannot create window due to a Win32 error.\n";
678+
if (!window->Create(title, size, FlutterWindowArchetype::regular,
679+
std::nullopt, std::nullopt)) {
633680
return std::nullopt;
634681
}
635682
lock.lock();
@@ -657,32 +704,75 @@ auto FlutterWindowController::createRegularWindow(
657704
return result;
658705
}
659706

660-
auto FlutterWindowController::createPopupWindow(
707+
auto FlutterWindowController::createDialogWindow(
661708
std::wstring const& title,
662-
Win32Window::Point const& origin,
663709
Win32Window::Size const& size,
710+
std::optional<Win32Window::Point> origin,
664711
std::optional<FlutterViewId> parent_view_id)
665712
-> std::optional<FlutterWindowCreationResult> {
666713
std::unique_lock lock(mutex_);
667714
if (!engine_) {
668-
std::cerr << "Cannot create window without an engine.\n";
715+
std::cerr << "Cannot create dialog without an engine.\n";
669716
return std::nullopt;
670717
}
671-
if (windows_.empty()) {
672-
std::cerr << "Cannot create this window as the first window.\n";
718+
719+
std::optional<HWND> const parent_hwnd{
720+
parent_view_id && windows_.find(*parent_view_id) != windows_.end()
721+
? std::optional<HWND>{windows_[*parent_view_id].get()->GetHandle()}
722+
: std::nullopt};
723+
auto window{std::make_unique<FlutterWin32Window>(engine_)};
724+
725+
lock.unlock();
726+
if (!window->Create(title, size, FlutterWindowArchetype::dialog, origin,
727+
parent_hwnd)) {
728+
return std::nullopt;
729+
}
730+
lock.lock();
731+
732+
auto const view_id{window->flutter_controller()->view_id()};
733+
windows_[view_id] = std::move(window);
734+
735+
cleanupClosedWindows();
736+
sendOnWindowCreated(FlutterWindowArchetype::dialog, view_id, parent_view_id);
737+
738+
FlutterWindowCreationResult result{
739+
.view_id = view_id,
740+
.parent_id = parent_view_id,
741+
.archetype = FlutterWindowArchetype::dialog,
742+
.size = getWindowSize(view_id)};
743+
744+
lock.unlock();
745+
746+
sendOnWindowResized(view_id);
747+
748+
return result;
749+
}
750+
751+
auto FlutterWindowController::createPopupWindow(
752+
std::wstring const& title,
753+
Win32Window::Point const& origin,
754+
Win32Window::Size const& size,
755+
std::optional<FlutterViewId> parent_view_id)
756+
-> std::optional<FlutterWindowCreationResult> {
757+
std::unique_lock lock(mutex_);
758+
if (!engine_) {
759+
std::cerr << "Cannot create popup without an engine.\n";
673760
return std::nullopt;
674761
}
675762

676763
auto* const parent_hwnd{parent_view_id && windows_.find(*parent_view_id) !=
677764
windows_.end()
678765
? windows_[*parent_view_id].get()->GetHandle()
679766
: nullptr};
767+
if (!parent_hwnd) {
768+
std::cerr << "Cannot create popup without a parent window.\n";
769+
return std::nullopt;
770+
}
680771
auto window{std::make_unique<FlutterWin32Window>(engine_)};
681772

682773
lock.unlock();
683-
if (!window->Create(title, origin, size, FlutterWindowArchetype::popup,
774+
if (!window->Create(title, size, FlutterWindowArchetype::popup, origin,
684775
parent_hwnd)) {
685-
std::cerr << "Cannot create window due to a Win32 error.\n";
686776
return std::nullopt;
687777
}
688778
lock.lock();
@@ -808,7 +898,7 @@ FlutterWindowSize FlutterWindowController::getWindowSize(
808898
}
809899

810900
// Convert to logical coordinates
811-
auto const dpr{FlutterDesktopGetDpiForHWND(hwnd) / base_dpi};
901+
auto const dpr{FlutterDesktopGetDpiForHWND(hwnd) / kBaseDpi};
812902
frame.left = static_cast<LONG>(frame.left / dpr);
813903
frame.top = static_cast<LONG>(frame.top / dpr);
814904
frame.right = static_cast<LONG>(frame.right / dpr);

shell/platform/windows/client_wrapper/include/flutter/flutter_window_controller.h

+6-1
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,14 @@ class FlutterWindowController {
4545

4646
void setEngine(std::shared_ptr<FlutterEngine> engine);
4747
auto createRegularWindow(std::wstring const& title,
48-
Win32Window::Point const& origin,
4948
Win32Window::Size const& size)
5049
-> std::optional<FlutterWindowCreationResult>;
50+
auto createDialogWindow(
51+
std::wstring const& title,
52+
Win32Window::Size const& size,
53+
std::optional<Win32Window::Point> origin,
54+
std::optional<FlutterViewId> parent_view_id = std::nullopt)
55+
-> std::optional<FlutterWindowCreationResult>;
5156
auto createPopupWindow(
5257
std::wstring const& title,
5358
Win32Window::Point const& origin,

shell/platform/windows/client_wrapper/include/flutter/win32_window.h

+3-2
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
#include <windows.h>
55

6+
#include <optional>
67
#include <set>
78
#include <string>
89

@@ -104,10 +105,10 @@ class Win32Window {
104105
// as appropriate for the default monitor. The window is invisible until
105106
// |Show| is called. Returns true if the window was created successfully.
106107
bool Create(std::wstring const& title,
107-
Point const& origin,
108108
Size const& size,
109109
FlutterWindowArchetype archetype,
110-
HWND parent);
110+
std::optional<Point> origin,
111+
std::optional<HWND> parent);
111112

112113
// Release OS resources associated with window.
113114
void Destroy();

0 commit comments

Comments
 (0)